Simulate a where clause on a “long” type column from SQLPlus

As a DBA you sometimes have to take time to look for tricks to accomplish tasks that seem simple at first glance. A few days ago, I was asked to identify tables that had sequences prefixed by the schema name as a default value in the columns. You may think: it’s an easy task, just filter on the “data_default” column of the “dba_tab_columns” view, but unfortunately it’s not that simple using SQLPlus. The “data_default” column is in “longdatatype, so you cannot include it directly in the “where” clause of a query.

In my examples, I will simulate the search for a default column with the value “systimestamp” in the tables of the “SYS” schema.

First tries

For my first try, I just tried to add the column “data_default” (“long” datatype) in the “where” clause of my query

SQL> select owner, table_name, column_name, data_default from dba_tab_columns where owner = 'SYS' and data_default ='systimestamp';
select owner, table_name, column_name, data_default from dba_tab_columns where owner = 'SYS' and data_default ='systimestamp'
                                                                                                 *
ERROR at line 1:
ORA-00997: illegal use of LONG datatype

The message doesn’t say much, and neither does the Oracle documentation:

# oerr ora 00997
00997, 00000, "illegal use of LONG datatype"
// *Cause:
// *Action:

Using the “like” operator instead of “=” doesn’t work either, but returns a different error:

SQL> select owner, table_name, column_name, data_default from dba_tab_columns where owner = 'SYS' and data_default like '%systimestamp%';
select owner, table_name, column_name, data_default from dba_tab_columns where owner = 'SYS' and data_default like '%systimestamp%'
                                                                                                 *
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected CHAR got LONG

Once again, the message doesn’t say much, and neither does the Oracle documentation:

# oerr ora 00932
00932, 00000, "inconsistent datatypes: expected %s got %s"
// *Cause:
// *Action:

At this point, I notice that it is not possible to filter on “long” columns, so I have to find a different approach.





Filter on “long” columns using PL/SQL

In order to achieve my goal, I decided to use a PL/SQL anonymous block.
It may not be the only available solution, but it works and is relatively simple to use.

I use a cursor that will fetch all the results of my query “select owner, table_name, column_name, data_default from dba_tab_columns where owner = ‘SYS’“, and then return only those containing the text I’m looking for using comparaison operator “record.data_default = ‘systimestamp’“. This also works when replacing “=” with “like”.

set serveroutput on
set long 999999999

BEGIN
	FOR record IN (select owner, table_name, column_name, data_default from dba_tab_columns where owner = 'SYS') LOOP
		IF record.data_default = 'systimestamp' THEN
			dbms_output.put_line(record.owner||';'||record.table_name||';'||record.column_name||';'||record.data_default);
		END IF;
	END LOOP;
END;
/
Output:
SYS;SYSDBIMFS$;CREATED;systimestamp
SYS;SYSDBIMFS$;LASTREAD;systimestamp

PL/SQL procedure successfully completed.

For my needs, the output was in CSV format, but it can be formatted by modifying the PL/SQL block a bit. For example, if you want it to look like the output of a select on a table:

set serveroutput on
set long 999999999
set lines 180
BEGIN
	dbms_output.Put_line(Rpad('OWNER', 30)||Rpad('TABLE_NAME', 30)||Rpad('COLUMN_NAME', 30)|| Rpad('DATA_DEFAULT', 30));
	dbms_output.Put_line(Rpad(' ', 30,'-')||Rpad(' ', 30,'-')||Rpad(' ', 30,'-')|| Rpad(' ', 30,'-'));
	FOR record IN (select owner, table_name, column_name, data_default from dba_tab_columns where owner = 'SYS') LOOP
		IF record.data_default like '%systimestamp%' THEN
			dbms_output.Put_line(Rpad(record.OWNER, 30)||Rpad(record.TABLE_NAME, 30)||Rpad(record.COLUMN_NAME, 30)|| Rpad(record.DATA_DEFAULT, 30));
		END IF;
	END LOOP;
END;
/

Output:

OWNER                         TABLE_NAME                    COLUMN_NAME                   DATA_DEFAULT
----------------------------- ----------------------------- ----------------------------- -----------------------------
SYS                           SYSDBIMFS$                    CREATED                       systimestamp
SYS                           SYSDBIMFS$                    LASTREAD                      systimestamp




This method works but has a few drawbacks:

  • It is not optimized from a performance point of view: by filtering directly on the PL/SQL cursor, the Oracle optimizer is bypassed. The filter on the “long” column is not taken into account in the calculation of the query execution plan. This is why it is necessary to add as many filters as possible in the initial query, in order to reduce the number of lines fetched by the cursor.
  • The “long” column on which you want to filter, must be part of your initial select clause.
  • Formatting the result takes longer than a simple “select / from / where”.




I hope you find this post useful. Stay tuned for more DBA stuff!

Leave a Reply

Your email address will not be published. Required fields are marked *