This block determines all columns in the table, loops through them in dynamic SQL and checks if they are null, then constructs a DBMS output query of the non-null query.
All you have to do is run the returned query.
I've included the exclusion of PKs and BLOB columns. Obviously, this is quite slow as going through columns one by one, and it's not going to be great for very hot tables, as data may change too quickly, but this works for me as I control traffic in dev env.
DECLARE l_table_name VARCHAR2(255) := 'XXXX'; l_counter NUMBER; l_sql CLOB; BEGIN FOR r_col IN (SELECT * FROM user_tab_columns tab_col WHERE table_name = l_table_name AND data_type NOT IN ('BLOB') AND column_name NOT IN (SELECT column_name FROM user_cons_columns con_col JOIN user_constraints cons ON con_col.constraint_name = cons.constraint_name AND con_col.table_name = cons.table_name WHERE con_col.table_name = tab_col.table_name AND constraint_type = 'P') ORDER BY column_id) LOOP EXECUTE IMMEDIATE 'SELECT COUNT(1) FROM '||l_table_name||' WHERE '||r_col.column_name||' IS NOT NULL' INTO l_counter; IF l_counter > 0 THEN IF l_sql IS NULL THEN l_sql := r_col.column_name; ELSE l_sql := l_sql||','||r_col.column_name; END IF; END IF; END LOOP; l_sql := 'SELECT '||l_sql||CHR(10) ||'FROM '||l_table_name; ---------- DBMS_OUTPUT.put_line(l_sql); END;