Line Numbers for PL/SQL code

Hi Gregory,

I cannot get it to work as advertised. It appears as if you work for Quest? Would it be helpful if I set up a WebEx meeting, so that you can see my screen? I’d really like to get this problem solved, because it adds a lot of difficulty troubleshooting long PL/SQL procedures that are commented. Thus far, I have resorted to removing all comments from copies of various procedures, in order to get a line number indicated that is closer to the actual line number. It should not be that difficult.

Thank You,

Tom

Hi Tom,

yes, really! :wink:

In your PL/SQL block you have a loop, controlled by ‘x’, which extracts table_names from all_tables. The table names in all_tables are in upper case, unless, some low-life has decided to use a tool that adds double quotes around the table names, thus rendering them case sensitive.

Within the ‘x’ loop, you are setting up a query which uses the x.table_name. This is controlled by the variable ‘y’. The table_name in all_tab_columns, which y is selecting from, has the table_name column in the same letter case as in all_tables, so you do not need to use UPPER() to force the table_name to upper case. And, if that same low-life has already botched things by forcing t he table_name in all_tables to be case sensitive, then the same case sensitive name will be used in both all_tables and all_tab_columns, so your ‘y’ controlled cursor loop will fail to find any columns for the case sensitive table_names.

The two example queries you show are not quite correct. The first will work because under normal circumstances, Oracle will upper case your table_name and store it upper cased. So when you are specifically looking for a table_name, it must be in upper case, as per your first SELECT. The second SELECT has no chance of returning any rows ever, unless that afore mentioned low-life has botched things and executed something like:

create table “awsm_tlk_airplane” …

Which will force Oracle to store the name in exactly that letter case, ie lower, and so your first SELECT would return zero rows, while the second would return 1.

Moving on …

ALL_TABLES is a view, some of the data are taken from SYS.OBJ$, the TABLE_NAME column is one of those. SYS.OBJ$ has an index on the NAME column, which is what becomes TABLE_NAME in the ALL_TABLES view. So, using UPPER(TABLE_NAME) will prevent the index on the NAME column of SYS.OBJ$ from being used in queries, thus hurting performance. The same occurs whether you explicitly specify a function call on a table’s column, or leave it to Oracle to do it implicitly. For example:

select … from table_a where some_date_column = ‘01-Jan-2017’;

Here Oracle implicitly adds a TO_CHAR() to the some_date_columns so that it’s data type matches that of the string passed for comparison. Explicitly, it would look as follows:

select … from table_a where to_char(some_date_column, ‘dd-Mon-yyyy’) = ‘01-Jan-2017’;

Either way, if an index exists on some_date_column, it cannot be used. Unless it is a function based index on to_char(…) of course!

I’ll get back to you with more information on the main part of your query however, later.

By the way, “low-life” is a technical term and is the definition of someone who uses a tool that defaults to adding double quotes around any kind of name, so that anyone ever using the objects thus created must, always, use double quotes too. :wink:

Tom,

I’m not sure a webex would help in this since the error in the dialog comes directly from Oracle. We do monitor where in the editor that the error occurs and store that. For the upcoming release in June, we’ve added an error tab directly in the script output to further assist. Out of curiosity, are you letting the script finish or are you cancelling? If you’re cancelling, that could be a cause of the click not working since we really can’t know the state of things once you cancel. Also, once you’ve made any changes to the editor, that feature will no longer work since we don’t know the state of your text any longer.

You can also try a simple example to see if the feature is working (this is what I used earlier to verify)

select * from HR.EMP_DETAILS_VIEW;

select * from MyTableError;

select * from SCOTT.EMP;

select * from AnotherBadTable;

select * from SCOTT.DEPT;

Hi Tom,

first of all, I was right about that “and rownum = 1” that I mentioned earlier! Insert smug grin here! However, as you noted, you just get the same message over and obver until your session runs out of “unlimited” dbms_output buffer space.

What you need to do to fix this is to replace this:

open l_cursor for 'select ’ || l_cols || ‘else null end cname from LEEDS_CONFIG.’ ||
x.table_name || l_where || ‘) and rownum=1’;

With the following:

open l_cursor for 'select DISTINCT ’ || l_cols || ‘else null end cname from LEEDS_CONFIG.’ ||
x.table_name || l_where || ‘)’;

We are adding a DISTINCT and removing the afore mentioned “and rownum = 1”.

The “rownum = 1” clause forced the database to only return the first column in its list where there was a find. All the rest were ignored. Removing that clause means that we get EVERY columns that has a find. However, we get the column name once for each row in the table that has the search text in that column. Hence the DISTINCT.

The DISTINCT will reduce this to one row per column. So you only get a small list of columns rather than a huge one.

So I tried it out here on a small table, with only a few hundred rows, three columns have the desired search text.

With the “and rownum = 1” left in, and no DISTINCT, I only saw a single row. This contained only the first column that had the search text in it. This is what you are seeing.

With “and rownum = 1” removed, I got hundreds of rows returned, with numerous duplicates. This caused my “unlimited” dbms_output buffer to blow up. So much for unlimited then!

Adding DISTINCT reduced the output to that desired. Only a single mention of the columns and all the columns were listed. This is what I think you are looking for.

My test results were limited to a single table, and there were three columns that definitely had the search text in:

Time Start: 2017/04/19 13:31:13
Data Searched: ABCXYZ

FOUND: DATABASES - “COLUMN_ONE”
select * from NORM.DATABASES where “COLUMN_ONE” like ‘%ABCXYZ%’;

FOUND: DATABASES - “COLUMN_TWO”
select * from NORM.DATABASES where “COLUMN_TWO” like ‘%ABCXYZ%’;

FOUND: DATABASES - “COLUMN_THREE”
select * from NORM.DATABASES where “COLUMN_THREE” like ‘%ABCXYZ%’;

3 tables found.

PL/SQL procedure successfully completed.
Time End: 2017/04/19 13:31:14
Elapsed Time for Script Execution: 945 msecs

This script will runs for some time if you have a lot of tables and/or those tables have a large number of rows. There should be a manner of limiting the search that creates the list of columns, to a single hit, but with the various CASE statements, I’m not sure that it can be done. So we are hitting every row in the table to determine if there are any hits on the search text even when we have already found at least one row containing the search text. An exercise for the reader - as they say! :slight_smile:

By the way, I added the “FOUND” bit to the output for debugging, but I left it in the final script that I ran. Which is:

set serveroutput on size unlimited

declare
i_count number:=0;
l_cols long;
l_where long;
l_cursor sys_refcursor;
l_cname varchar2(4000);
p_string varchar2(100):=‘ABCXYZ’;

begin
dbms_application_info.set_client_info( ‘%’ || p_string || ‘%’ );
dbms_output.put_line(‘Data Searched: ’ || p_string);
dbms_output.put_line(’ ');

 for x in ( select table_name
     from all_tables
     where owner = 'NORM'
     order by table_name )
 loop
     l_cols := 'case when 1=0 then ''x'' ';
     l_where := ' where ( 1=0 ';

     for y in ( select '"' || column_name || '"' column_name
         from all_tab_columns
         where table_name = x.table_name
         and owner = 'NORM'
         and (data_type in ( 'CHAR', 'NVARCHAR2', 'VARCHAR2' ))
     ) loop
         l_cols := l_cols || ' when ' || y.column_name ||
         ' like sys_context(''userenv'',''client_info'') then ' ||
         ' ''' || y.column_name || '''';
         l_where := l_where || ' or ' || y.column_name || ' like sys_context(''userenv'',''client_info'') ';
     end loop;

     open l_cursor for 'select distinct ' || l_cols || 'else null end cname from NORM.' ||
     x.table_name || l_where || ')';

     loop
         fetch l_cursor into l_cname;
         exit when l_cursor%notfound;

        i_count:=i_count + 1;
         dbms_output.put_line('FOUND: ' || x.table_name || ' - ' || l_cname );
         dbms_output.put_line('select * from NORM.' || x.table_name || ' where ' || l_cname || ' like ''%' || p_string || '%'';');
         dbms_output.put_line(' ');

    end loop;

     close l_cursor;

 end loop;
 dbms_output.put_line(' ');
 dbms_output.put_line(i_count || ' tables found.');
 dbms_output.put_line(' ');

end;
/

I also added in the owner of the tables to the various SELECT scripts and cursors, it saves me having to be in that user. Hard coded in my test, but you could concatenate the username and a dot into the SQL so you only have to change it once, if you ever need to change the table owner. (NORM in the above.)

HTH

Hi Greg, Tom,

I’ve had a quick look at the inability to jump to the error line using Tom’s anonymous block. When I introduce an error and run the code with F5, I get the pop-up that tells me there’s an error at line 34 in my case.

In the editor, my cursor jumps to line 3, DECLARE, and highlights it. But this is line 3 and not line 34. Hmm.

If we go to the messages tab, after dismissing the pop-up, and click there, it causes the highlighted line to move back to line 3 (assuming I moved it of course).

Double clicking opens up the “jump to” edit area at the top of the screen.

I then select all and copy in the messages tab and past it into a editor. I see this:

[Error] ORA-00942: table or view does not exist
ORA-06512: at line 34
(3: 0): declare

Error at line 3

… SQL here …

ORA-00942: table or view does not exist
ORA-06512: at line 34

So that’s the problem, I think. Oracle is reporting that the error is actually at line 3 as well as at line 34. Line 34 doesn’t match a code line, in my case, because I have comments and blank lines to make the code readable. I suspect, but I’m guessing, that Toad picks up the bit with the (3: 0) in it, to get the line and column where the error occurred.

This could simply be a problem with an anonymous block of course, I haven’t done any rigorous testing to determine what circumstances Toad/Oracle get it wrong! :wink:

HTH

Anonymous blocks are always fun since technically Oracle sees them as a single statement (making debugging them nearly impossible) so where the error actually occurs in the "script" is at the start of the AB. Even though Oracle is telling you which line in the AB it is occurring. It's one of those fun "quirks" when dealing with Oracle.

On Apr 19, 2017, at 7:35 AM, Norm [TeamT] bounce-NormTeamT@toadworld.com wrote:

RE: Line Numbers for PL/SQL code

Reply by Norm [TeamT]
Hi Tom,

yes, really! :wink:

In your PL/SQL block you have a loop, controlled by 'x', which extracts table_names from all_tables. The table names in all_tables are in upper case, unless, some low-life has decided to use a tool that adds double quotes around the table names, thus rendering them case sensitive.

Within the 'x' loop, you are setting up a query which uses the x.table_name. This is controlled by the variable 'y'. The table_name in all_tab_columns, which y is selecting from, has the table_name column in the same letter case as in all_tables, so you do not need to use UPPER() to force the table_name to upper case. And, if that same low-life has already botched things by forcing t he table_name in all_tables to be case sensitive, then the same case sensitive name will be used in both all_tables and all_tab_columns, so your 'y' controlled cursor loop will fail to find any columns for the case sensitive table_names.

The two example queries you show are not quite correct. The first will work because under normal circumstances, Oracle will upper case your table_name and store it upper cased. So when you are specifically looking for a table_name, it must be in upper case, as per your first SELECT. The second SELECT has no chance of returning any rows ever, unless that afore mentioned low-life has botched things and executed something like:

create table "awsm_tlk_airplane" ...

Which will force Oracle to store the name in exactly that letter case, ie lower, and so your first SELECT would return zero rows, while the second would return 1.

Moving on ...

ALL_TABLES is a view, some of the data are taken from SYS.OBJ$, the TABLE_NAME column is one of those. SYS.OBJ$ has an index on the NAME column, which is what becomes TABLE_NAME in the ALL_TABLES view. So, using UPPER(TABLE_NAME) will prevent the index on the NAME column of SYS.OBJ$ from being used in queries, thus hurting performance. The same occurs whether you explicitly specify a function call on a table's column, or leave it to Oracle to do it implicitly. For example:

select ... from table_a where some_date_column = '01-Jan-2017';

Here Oracle implicitly adds a TO_CHAR() to the some_date_columns so that it's data type matches that of the string passed for comparison. Explicitly, it would look as follows:

select ... from table_a where to_char(some_date_column, 'dd-Mon-yyyy') = '01-Jan-2017';

Either way, if an index exists on some_date_column, it cannot be used. Unless it is a function based index on to_char(...) of course!

I'll get back to you with more information on the main part of your query however, later.

To reply, please reply-all to this email.

Stop receiving emails on this subject.

Or Unsubscribe from Toad for Oracle Forum notifications altogether.

Toad for Oracle - Discussion Forum

Flag this post as spam/abuse.

LIne 3, relative to the start of the script, is DECLARE…

Line 34, relative to the DECLARE line, even counting the blank lines and comments (if there were any), is …

“open l_cursor for 'select distinct '…”

which makes perfect sense for a table-or-view-does-not-exist error.

Hi Norm,

yes, really! :wink:

In your PL/SQL block you have a loop, controlled by ‘x’, which extracts table_names from all_tables. The table names in all_tables are in upper case, unless, some low-life has decided to use a tool that adds double quotes around the table names, thus rendering them case sensitive.

I certainly agree with you that using functions in WHERE clauses can render a query non-Sargable. The use of the upper function came from Tom Kyte’s posting, which was the source of my code. If you search this page: https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:37336026927381#4242608375763 for the word “upper”, you will see that he used it as “where table_name = upper(x.table_name)”.

Being the curious type, I tried two text searches, on our Production server (11gR2), with and without the use of the upper function, e.g. one line or the other commented out (but not both) for testing. I’m just showing a snippet of the original code below:

select ‘"’ || column_name || ‘"’ column_name
from all_tab_columns
–where table_name = upper(x.table_name)
where table_name = x.table_name – Without UPPER function call ----> sargable query
and owner = p_schema
and (data_type in ( ‘CHAR’, ‘NVARCHAR2’, ‘VARCHAR2’ ))

One search involved an engineer’s last name, and the other search involved a 4-character work_statement value (‘H91A’). I do see a significant improvement in the search that involved the engineer’s last name. I only ran one test of each condition, so I realize my data is rather limited. Here are the results:

Search Term
Tables Found
With Upper (min.)
Without Upper (min.)
An engineer’s Last Name
28
3:30
2:04
H91A
49
1:53
1:49

So, we shaved over a minute off the search that involves an engineer’s last name, but the other search only saved about 4 seconds. Still, very good observation and I will go without the use of the upper function, since all tables (currently 302 listed by TOAD) are uppercase.

By the way, “low-life” is a technical term and is the definition of someone who uses a tool that defaults to adding double quotes around any kind of name, so that anyone ever using the objects thus created must, always, use double quotes too. :wink:

I like that term. Indeed, we have several views that some low-life rendered case sensitive, but I’m only searching tables in this code.

Thank You,

Tom

Hi Gregory,

I'm not sure a webex would help in this since the error in the dialog comes directly from Oracle.

My thought for the WebEx was so that you could see that when I click the message in the Messages tab, nothing happens. If I understood you correctly from before (?), you said that it should cause the cursor to move to the offending line of code. I wish I could get that to work, if that's the behavior that has been designed into the software.

Hopefully, the error tab that you are describing in an upcoming June release will help. Any idea what the version number will be?

Out of curiosity, are you letting the script finish or are you cancelling?

I tried both Ignore and Cancel. It does not change the result. I did not make any changes in the editor -- just tried to click on the error displayed in the Messages tab, to get taken to the offending line, since I cannot trust the line numbers.

Thanks,

Tom

Hi Norm,

Norm [TeamT] wrote the following post at 19 Apr 2017 1:02 PM:

Hi Greg, Tom,

I’ve had a quick look at the inability to jump to the error line using Tom’s anonymous block. When I introduce an error and run the code with F5, I get the pop-up that tells me there’s an error at line 34 in my case.

In the editor, my cursor jumps to line 3, DECLARE, and highlights it. But this is line 3 and not line 34. Hmm.

I’m glad you’ve been able to repro my issue!

If there is any fix available in a future release, that would be great! I really need TOAD’s help here to find actual errors, especially when loooong procedures go on for 20 printed pages.

Thank You!

Tom

Hi Norm,

I'm replying to this message:

Norm [TeamT] / 19 Apr 2017 at 12:43pm

I need to try your suggestions tomorrow, as I'm running out of time tonight. It looks like you may have solved this challenge of only finding the first occurrence in a table with questionable normalization!

This script will run for some time if you have a lot of tables and/or those tables have a large number of rows.

That's fine, as long as it helps me find all occurrences of data. As I said, we have a very poorly documented database, with "lots-o-wide" tables, and filled with plenty of "SQL Smells". Sometimes, we need to ferret out all locations especially when we are told that a value needs to be updated, and the web-based application does not provide this capability.

A thought for the TOAD team: Is there any way that they could provide a DATA search capability in a future version, similar to the screen displayed by clicking on Search > Object Search? I don't care if such a search takes 20 minutes (so far, all searches I've done are under 4 minutes), because when you need to find all the cubby holes....

Tom

Hi Gregory,

Reply by Gregory Liss

Anonymous blocks are always fun since technically Oracle sees them as a single statement (making debugging them nearly impossible) so where the error actually occurs in the “script” is at the start of the AB. Even though Oracle is telling you which line in the AB it is occurring. It’s one of those fun “quirks” when dealing with Oracle.

I did not know that detail. I am relatively new to both Oracle and PL/SQL, having come from the toy* database, Microsoft Access, with its programming language, VBA.

Thank You. Every little bit helps.

Tom

  • PS. For the record, I like my toys! :slight_smile:

Morning Tom,

Being the curious type, I tried two text searches, on our Production server (11gR2), with and without the use of the upper function, e.g. one line or the other commented out (but not both) for testing. I’m just showing a snippet of the original code below:

The use or not of UPPER() is not meant to save time, it’s meant to be correct! While I have the utmost respect for Tom Kyte and his abilities, in this case, he is potentially introducing a problem that is not necessary. Image your friendly, local low-life (!) has created a table named “TableWithMixedCaseName” then that is exactly what will appear in USER/ALL/DBA_TABLES and also in USER/ALL/DBA_TAB_COLUMNS. This will no longer work:

select …

from user_tab_columns

where table_name = upper(x.table_name)

because x.table_name is ‘TableWithMixedCaseName’ and that’s what is in user_tab_columns too, if we match on upper(‘TableWithMixedCaseName’) we will not get a hit, so that particular table will never be searched - because there are no table_names, in user_tab_columns, which are stored as ‘TABLEWITHMIXEDCASENAME’.

This will help identify if you have a problem:

select table_name from user_tables

where table_name <> upper(table_name);

HTH

Cheers,

Norm. [TeamT]

This might be of some use. It’s a small package containing a pipelined function to search given tables in a specific user, for some text. It takes 4 parameters:

  • OWNER - the owner of the tables. ALL_TABLES will be searched for this owner’s tables to search.
  • SEARCHTEXT - what you are looking for in the tables. This is case sensitive so an exact match is made in any textual column which contains the supplied text.
  • TABLEFILTER - something that will be used to filter the list of tables to be searched. The default is all tables owned by the supplied owner.
  • DEBUG - set to Y to get a lot of DBMS_OUTPUT to help track down what might be going wrong when results are not as you expect.

Calling:

  • select * from table(normSearch.textSearch(‘USER_A’, ‘FIND_ME’,)); - Search all tables owned by USER_A for the text ‘FIND_ME’ in upper case.
  • select * from table(normSearch.textSearch(‘USER_A’, ‘FIND_ME’,’%LIKE_THIS%’)); - As above, but only tables which have ‘LIKE_THIS’ in their name, in upper case.
  • select * from table(normSearch.textSearch(‘USER_A’, ‘FIND_ME’, pDEBUG=>‘Y’)); - As above, but with debugging information sent to DBMS_OUTPUT.
    Yes, that’s how you call a pipelined function!

Package Code:

Hopefully, this will format nicely!

create or replace package normSearch as

-- Pipelined functions need a table type to return results.

type tSqlStatement is table of varchar2(1024);



-- Text search function. Only CHAR, VARCHAR2 or NVARCHAR2 columns

-- are searched.

--

-- CALLING CONVENTIONS:

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY'));

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY'), '%LIKE_ME%');

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY', '%LIKE_ME%', 'Y'));

--

-- NOTES:

--

-- ALL_TABLES is searched for table_names. ALL_TAB_COLUMNS is searched

-- for column names, so you can search another user's tables, however, 

-- your calling user must have SELECT privilege granted otherwise they

-- won't be found in ALL_TABLES/ALL_TAB_COLUMNS. (At least, SELECT that

-- is.)

--

-- OWNER is NOT case sensitive. It will be uppercased by the code.

--       It cannot be NULL. If it is, error code -20000 will be raised.

--

-- SEARCHFOR cannot be NULL. It IS case sensitive. Any textual

--       column in the database that CONTAINS this text will be

--       returned. Error code -20001 is returned if the search text

--       is NULL.

--

-- TABLEFILTER is NOT case sensitive, in case you have lower and/or

--       mixed case table names. Sigh! This is not a wildcard filter

--       whatever you type here will be used to filter table names

--       so if you want a wildcard, then you supply the '%' as you

--       reguire. The default is ALL tables.

--

-- DEBUG is NOT case sensitive but it must be Y or N or NULL. NULL is

--       considered to be the same as N. Setting this to Y will cause

--       lots of useful(?) debugging information to be output using

--       DBMS_OUTPUT. Up to 1 million chracters are permitted.

--       -20002 is returned if the value is not Y or N.

--       The default is no debugging (N).



function textSearch(    

    pOwner in all_tables.owner%type,

    pSearchFor in varchar2,

    pTableFilter in varchar2 default '%',

    pDebug in varchar2 default 'N'

) return tSqlStatement pipelined;    

end;

/


Package Body Code:

create or replace package body normSearch as

function textSearch(

    

    pOwner in all_tables.owner%type,

    pSearchFor in varchar2,

    pTableFilter in varchar2 default '%',

    pDebug in varchar2 default 'N'

    

) return tSqlStatement pipelined as



    -- Owner of the tables to be searched.        

    vOwner all_tables.owner%type;    

    

    -- List of textual columns in each table.

    vColumns clob;

    

    -- WHERE clause for the search.

    vWhereClause clob;

    

    -- A cursor to list columns where the text is found.

    vSearchCursor sys_refcursor;

    

    -- A single column where the text was found.

    vColumnName varchar2(4000);

    

    -- A filter to restrict tables to be searched.

    vTableFilter varchar2(1024);

    

    -- Are we debugging?

    vDebug boolean;

    

    -- A dynamic SQL statement.

    -- Change the size if something barfs!

    vSQL varchar2(4096);

begin

    -- Validation first.

    -- Table owner cannot be NULL.The owner might not actually

    -- exist, but that's your problem! ;-)

    if (pOwner is null) then

        raise_application_error(-20000, 'Owner cannot be NULL');

    end if;

    vOwner := upper(pOwner);

        

    -- The search text cannot be NULL either.

    if (pSearchFor is null) then

        raise_application_error(-20001, 'Search text cannot be NULL');

    end if;

    

    -- The table name filter can be NULL though.

    -- We DO NOT uppercase the tablename filter in case we have

    -- case sensitive table names!

    vTableFilter := pTableFilter;

    if (vTableFilter is null) then

        vTableFilter := '%';

    end if;

    

    -- Debugging shouldn't be NULL.

    if (pDebug is null) then

        vDebug := false;

    else            

        if (upper(pDebug) not in ('Y', 'N')) then

            raise_application_error(-20002, 'Debug must be ''Y'' or ''N''.');

        end if;

        

        if (upper(pDebug) = 'Y') then

            vDebug := true;

        end if;        

    end if;

    

    -- Validation is ok, carry on and search. I suspect Tom Kyte is

    -- showing off here! ;-) What's wrong with a plain old VARCHAR2?           

    dbms_application_info.set_client_info( '%' || pSearchFor || '%' );

    if (vDebug) then

        dbms_output.enable(1000000);

        dbms_output.put_line('SEARCH TEXT: ''%' || pSearchFor || '%''');

        dbms_output.put_line('TABLE OWNER: ''' || pOwner || '%''');

        dbms_output.put_line('TABLE NAME FILTER: ''' || vTableFilter || '''');

        dbms_output.put_line(' ');

    end if;

    

    -- Get a list of tables owned by the requested owner.

    for x in ( select   table_name 

               from     all_tables 

               where    owner = vOwner

               and      table_name like vTableFilter

               order by table_name ) 

    loop 

        -- Loop around each table, building a list of column names

        -- which are of a textual nature, and build a SQL statement to

        -- search each one for the supplied text. This is case sensitive.

        vColumns := q'<case when 1=0 then 'x' >';

        vWhereClause := ' where ( 1=0 ';

        

        if (vDebug) then

            dbms_output.put_line('TABLE: ' || x.table_name);

        end if;

        for y in ( select   column_name 

                   from     all_tab_columns

                   where    table_name = x.table_name

                   and      owner = vOwner

                   and      (data_type in ( 'CHAR', 'NVARCHAR2', 'VARCHAR2' )))

        loop 

            vColumns := vColumns || ' when ' || y.column_name ||

            q'< like sys_context('userenv','client_info') then >' ||

            q'< '>' || y.column_name || q'<'>';

            vWhereClause := vWhereClause || ' or ' || y.column_name || 

                           q'< like sys_context('userenv','client_info') >';

            

            if (vDebug) then

                dbms_output.put_line('    COLUMN: ' || y.column_name);

            end if;

            

        end loop;

        

        -- Build the SQL...

        vSQL := 'select distinct ' || vColumns || 

                ' else null end cname from ' || vOwner || '.' ||

                x.table_name || vWhereClause || ')'; 

        

        if (vDebug) then

            dbms_output.put_line('        QUERY: ' || vSQL);

        end if;

        

         

        -- We have SQL, execute it in a cursor to grab a list of any

        -- column in the current table, which has the required search

        -- text present in it.

        open vSearchCursor for vSQL; 

        -- Loop around the results, returning a single column name 

        -- each time. If we have any results, return an SQL statement

        -- along the lines of:

        -- SELECT * FROM USER.TABLE_NAME WHERE COLUMN_NAME LIKE SEARCH_TEXT;

        loop

            fetch vSearchCursor into vColumnName;

            exit when vSearchCursor%notfound;

            -- This is the bit I love, return the statement as a table row!

            pipe row('select * from "' || vOwner || '"."' || x.table_name || '" where "' ||

                      vColumnName || q'<" like '%>' || pSearchFor || q'<%';>');

        end loop;

        close vSearchCursor;

        

        if (vDebug) then

            dbms_output.put_line(' ');

        end if;

    end loop;

end;

end;

/

Have fun!

Cheers,

Norm.

Hi Norm,

Norm [TeamT] wrote the following post at 20 Apr 2017 9:28 AM:

Morning Tom,

Being the curious type, I tried two text searches, on our Production server (11gR2), with and without the use of the upper function, e.g. one line or the other commented out (but not both) for testing. I’m just showing a snippet of the original code below:

The use or not of UPPER() is not meant to save time, it’s meant to be correct!

Okay, but this is NOT the first point you made about the use of the UPPER() function. If you refer back to your earlier reply, Norm [TeamT] / 18 Apr 2017 at 8:01am, you wrote:

“You don’t need to upper case the table name. You can use the table name returned from all/user_tables as is, when looking for columns, as it is already correct. Using upper() will stop oracle from using an index, if there is one, on table_name, in all/user_tab_columns.”

In fact, you didn’t even use the word error or imply incorrect result. While I say “Thank You” for this correction to Mr. Kyte’s code, I think you could have said something like "gee, I missed an important point earlier…The use or not of UPPER() is not meant to save time, it’s meant to be correct because…


This will help identify if you have a problem:

select table_name from user_tables
where table_name <> upper(table_name);

In my case, I have no user_tables at all in our Production database, so the above query returns zero rows without the WHERE clause. I had to modify it slightly, by querying all_tables and adding an owner criteria:

select table_name from all_tables
where owner = 'APDBMS_SOR'
  and table_name <> upper(table_name);

The result is still zero rows (there are 4 system tables, owned by SYS, if I remove the above owner criteria). So, I think I am good in either case, but like I said yesterday, I have removed the call to the UPPER function. Incidentally, this UNION query returns zero Tables and (62) Views--that is our friendly "low-life" at work!

-- Look for case-sensitive table and view names:
select table_name as objectname, 'Table' as objecttype
  from all_tables
  where owner = 'APDBMS_SOR' and table_name <> upper(table_name)
union
select view_name as objectname, 'View' as objecttype
  from all_views
  where owner = 'APDBMS_SOR' and view_name <> upper(view_name)
order by objecttype, objectname;

 

Tom

Hi Norm,

In response to: Norm [TeamT] / 20 Apr 2017 at 11:36am

This might be of some use. It’s a small package containing a pipelined function….

Thank You. The code formatted nicely in both the email I received and the web page.

But, I still have a nagging problem….I’m not allowed to create any objects in our Production server. The IT person wanted me to run procedures as an anonymous block.

I do have a “sandbox” test environment, that is just for me so I can try it out there. If something like this could be built into a future release of TOAD (?), would it allow us to search for data without having to run code as an anonymous block?

Tom

From: Norm [TeamT] [mailto:bounce-NormTeamT@toadworld.com]

Sent: Thursday, April 20, 2017 4:37 AM

To: toadoracle@toadworld.com

Subject: RE: [Toad for Oracle - Discussion Forum] Line Numbers for PL/SQL code

RE: Line Numbers for PL/SQL code

Reply by Norm [TeamT]

This might be of some use. It’s a small package containing a pipelined function to search given tables in a specific user, for some text. It takes 4 parameters:

  • OWNER - the owner of the tables. ALL_TABLES will be searched for this owner’s tables to search.
  • SEARCHTEXT - what you are looking for in the tables. This is case sensitive so an exact match is made in any textual column which contains the supplied text.
  • TABLEFILTER - something that will be used to filter the list of tables to be searched. The default is all tables owned by the supplied owner.
  • DEBUG - set to Y to get a lot of DBMS_OUTPUT to help track down what might be going wrong when results are not as you expect.

Calling:

  • select * from table(normSearch.textSearch(USER, ‘FIND_ME’,)); - Search all tables for the text ‘FIND_ME’ in upper case.
  • select * from table(normSearch.textSearch(USER, ‘FIND_ME’,’%LIKE_THIS%’)); - As above, but only tables which have ‘LIKE_THIS’ in their name, in upper case.
  • select * from table(normSearch.textSearch(USER, ‘FIND_ME’, pDEBUG=>‘Y’)); - As above, but with debugging information sent to DBMS_OUTPUT.
    Yes, that’s how you call a pipelined function!

Package Code:

Hopefully, this will format nicely!

create or replace package normSearch as

-- Pipelined functions need a table type to return results.

type tSqlStatement is table of varchar2(1024);



-- Text search function. Only CHAR, VARCHAR2 or NVARCHAR2 columns

-- are searched.

--

-- CALLING CONVENTIONS:

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY'));

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY'), '%LIKE_ME%');

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY', '%LIKE_ME%', 'Y'));

--

-- NOTES:

--

-- ALL_TABLES is searched for table_names. ALL_TAB_COLUMNS is searched

-- for column names, so you can search another user's tables, however, 

-- your calling user must have SELECT privilege granted otherwise they

-- won't be found in ALL_TABLES/ALL_TAB_COLUMNS. (At least, SELECT that

-- is.)

--

-- OWNER is NOT case sensitive. It will be uppercased by the code.

--       It cannot be NULL. If it is, error code -20000 will be raised.

--

-- SEARCHFOR cannot be NULL. It IS case sensitive. Any textual

--       column in the database that CONTAINS this text will be

--       returned. Error code -20001 is returned if the search text

--       is NULL.

--

-- TABLEFILTER is NOT case sensitive, in case you have lower and/or

--       mixed case table names. Sigh! This is not a wildcard filter

--       whatever you type here will be used to filter table names

--       so if you want a wildcard, then you supply the '%' as you

--       reguire. The default is ALL tables.

--

-- DEBUG is NOT case sensitive but it must be Y or  or NULL. NULL is

--       considered to be the same as N. Setting this to Y will cause

--       lots of useful(?) debugging information to be output using

--       DBMS_OUTPUT. Up to 1 million chracters are permitted.

--       -20002 is returned if the value is not Y or N.

--       The default is no debugging (N).



function textSearch(    

    pOwner in all_tables.owner%type,

    pSearchFor in varchar2,

    pTableFilter in varchar2 default '%',

    pDebug in varchar2 default 'N'

) return tSqlStatement pipelined;    

end;

/


Package Body Code:

create or replace package body normSearch as

function textSearch(

    

    pOwner in all_tables.owner%type,

    pSearchFor in varchar2,

    pTableFilter in varchar2 default '%',

    pDebug in varchar2 default 'N'

    

) return tSqlStatement pipelined as



    -- Owner of the tables to be searched.        

    vOwner all_tables.owner%type;    

    

    -- List of textual columns in each table.

    vColumns clob;

    

    -- WHERE clause for the search.

    vWhereClause clob;

    

    -- A cursor to list columns where the text is found.

    vSearchCursor sys_refcursor;

    

    -- A single column where the text was found.

    vColumnName varchar2(4000);

    

    -- A filter to restrict tables to be searched.

    vTableFilter varchar2(1024);

    

    -- Are we debugging?

    vDebug boolean;

    

    -- A dynamic SQL statement.

    -- Change the size if something barfs!

    vSQL varchar2(4096);

begin

    -- Validation first.

    -- Table owner cannot be NULL.The owner might not actually

    -- exist, but that's your problem! ;-)

    if (pOwner is null) then

        raise_application_error(-20000, 'Owner cannot be NULL');

    end if;

    vOwner := upper(pOwner);

        

    -- The search text cannot be NULL either.

    if (pSearchFor is null) then

        raise_application_error(-20001, 'Search text cannot be NULL');

    end if;

    

    -- The table name filter can be NULL though.

[View:/cfs-file/__key/communityserver-discussions-components-files/10/NORMSEARCH.pks.txt:0:0]

Hi Norm,

In response to: Norm [TeamT] / 20 Apr 2017 at 11:36am

This might be of some use. It’s a small package containing a pipelined function….

I created the package and package body in my “sandbox” test environment. The code compiles fine:

However, when I try running it, I get the following error (in both cases, with and without the debug switch):

There are no debugging results shown. My PL/SQL skills are just not there yet, as far as attempting to debug. It could very well be a PEBKAC error! [:^)]

I have attached a file created by using TOAD’s “Load in Editor” function, from the Schema Browser. Remove the .txt file extension.

The schema and search term (H91A) are valid. By comparison, the existing anonymous block procedure that I have, based on Tom Kyte’s code, returns (11) tables in my test sandbox environment (I don’t have near the table count that the production database has in this environment, but I do have several of the main tables):

Time Start: 4/20/2017 7:08:27 PM

Data Searched: H91A

 :

 :

:

11 tables found.

PL/SQL procedure successfully completed.

Time End: 4/20/2017 7:08:45 PM

Elapsed Time for Script Execution: 18 secs

Thank You,

Tom

From: Norm [TeamT] [mailto:bounce-NormTeamT@toadworld.com]
Sent: Thursday, April 20, 2017 4:37 AM
To: toadoracle@toadworld.com
Subject: RE: [Toad for Oracle - Discussion Forum] Line Numbers for PL/SQL code

RE: Line Numbers for PL/SQL code

Reply by Norm [TeamT]

This might be of some use. It’s a small package containing a pipelined function to search given tables in a specific user, for some text. It takes 4 parameters:

  • OWNER - the owner of the tables. ALL_TABLES will be searched for this owner’s tables to search.
  • SEARCHTEXT - what you are looking for in the tables. This is case sensitive so an exact match is made in any textual column which contains the supplied text.
  • TABLEFILTER - something that will be used to filter the list of tables to be searched. The default is all tables owned by the supplied owner.
  • DEBUG - set to Y to get a lot of DBMS_OUTPUT to help track down what might be going wrong when results are not as you expect.

Calling:

  • select * from table(normSearch.textSearch(USER, ‘FIND_ME’,)); - Search all tables for the text ‘FIND_ME’ in upper case.
  • select * from table(normSearch.textSearch(USER, ‘FIND_ME’,’%LIKE_THIS%’)); - As above, but only tables which have ‘LIKE_THIS’ in their name, in upper case.
  • select * from table(normSearch.textSearch(USER, ‘FIND_ME’, pDEBUG=>‘Y’)); - As above, but with debugging information sent to DBMS_OUTPUT.
    Yes, that’s how you call a pipelined function!

Package Code:

Hopefully, this will format nicely!

create or replace package normSearch as

-- Pipelined functions need a table type to return results.

type tSqlStatement is table of varchar2(1024);



-- Text search function. Only CHAR, VARCHAR2 or NVARCHAR2 columns

-- are searched.

--

-- CALLING CONVENTIONS:

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY'));

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY'), '%LIKE_ME%');

--

--      select * from table(normsearch.textsearch('USER1', 'ABXZY', '%LIKE_ME%', 'Y'));

--

-- NOTES:

--

-- ALL_TABLES is searched for table_names. ALL_TAB_COLUMNS is searched

-- for column names, so you can search another user's tables, however, 

-- your calling user must have SELECT privilege granted otherwise they

-- won't be found in ALL_TABLES/ALL_TAB_COLUMNS. (At least, SELECT that

-- is.)

--

-- OWNER is NOT case sensitive. It will be uppercased by the code.

--       It cannot be NULL. If it is, error code -20000 will be raised.

--

-- SEARCHFOR cannot be NULL. It IS case sensitive. Any textual

--       column in the database that CONTAINS this text will be

--       returned. Error code -20001 is returned if the search text

--       is NULL.

--

-- TABLEFILTER is NOT case sensitive, in case you have lower and/or

--       mixed case table names. Sigh! This is not a wildcard filter

--       whatever you type here will be used to filter table names

--       so if you want a wildcard, then you supply the '%' as you

--       reguire. The default is ALL tables.

--

-- DEBUG is NOT case sensitive but it must be Y or  or NULL. NULL is

--       considered to be the same as N. Setting this to Y will cause

--       lots of useful(?) debugging information to be output using

--       DBMS_OUTPUT. Up to 1 million chracters are permitted.

--       -20002 is returned if the value is not Y or N.

--       The default is no debugging (N).



function textSearch(    

    pOwner in all_tables.owner%type,

    pSearchFor in varchar2,

    pTableFilter in varchar2 default '%',

    pDebug in varchar2 default 'N'

) return tSqlStatement pipelined;    

end;

/


Package Body Code:

create or replace package body normSearch as

function textSearch(

    

    pOwner in all_tables.owner%type,

    pSearchFor in varchar2,

    pTableFilter in varchar2 default '%',

    pDebug in varchar2 default 'N'

    

) return tSqlStatement pipelined as



    -- Owner of the tables to be searched.        

    vOwner all_tables.owner%type;    

    

    -- List of textual columns in each table.

    vColumns clob;

    

    -- WHERE clause for the search.

    vWhereClause clob;

    

    -- A cursor to list columns where the text is found.

    vSearchCursor sys_refcursor;

    

    -- A single column where the text was found.

    vColumnName varchar2(4000);

    

    -- A filter to restrict tables to be searched.

    vTableFilter varchar2(1024);

    

    -- Are we debugging?

    vDebug boolean;

    

    -- A dynamic SQL statement.

    -- Change the size if something barfs!

    vSQL varchar2(4096);

Hi Tom,

Strange, it works fine here with a schema containing over 2500 tables, some of them very wide indeed!

I suspect you might be overflowing the vSQL variable if a table has too many columns. However, to find out, run it with debugging on, as per your email, in toad, then when it bars, go to the dbms_output tab in the lower part of the screen, and click the refresh button.

Toad doesn’t seem to automatically refresh that tab when an exception has been raised.

You will see a table name, a list of columns and a query at the end which will help diagnose the problem. If you do not see a query for the final table and columns, then we have too small a vSQL buffer for your table in question.

If it is a huge query, then change the difinition of vSQL from varchar2 (4096) to clob. That should allow massive queries to be generated.

Cheers,

Norm.

Sent from my Android device with K-9 Mail. Please excuse my brevity.

Hi Tom,

your IT person needs a sever talking to, s/he is obstruction your ability to do work by the sound of things. However…

You have the ability to create a package in your sandbox I see. Once you have demonstrated that it is ok, and not virus/trojan ridden or likely to do harm to production etc, you can hopefully request that it be applied to production via the usual change control procedures - perhaps after being examined and/or demonstrated to said IT person. Good luck!

Hi Tom,

You said:

Okay, but this is NOT the first point you made about the use of the UPPER() function. If you refer back to your earlier reply, Norm [TeamT] / 18 Apr 2017 at 8:01am, you wrote:

“You don’t need to upper case the table name. You can use the table name returned from all/user_tables as is, when looking for columns, as it is already correct. Using upper() will stop oracle from using an index, if there is one, on table_name, in all/user_tab_columns.”

In fact, you didn’t even use the word error or imply incorrect result. While I say “Thank You” for this correction to Mr. Kyte’s code, I think you could have said something like "gee, I missed an important point earlier…The use or not of UPPER() is not meant to save time, it’s meant to be correct because…

You’ll have to forgive me, English isn’t my first language, I’m Scottish!

However, your observation is correct, I didn’t mention that the use of UPPER() as supplied by Tom K, would result in incorrect results if and only if, your friendly local “low-life” has indeed created tables with lower or mixed case names. I forgot that after my mini-rant on the use of UPPER() causing any existing indexes on the NAME column from being used in the query.

I sit corrected. :wink:

And, I never say “gee” - for anything! I’m usually a lot more profane that that!!!