Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Richard.

Asked: January 26, 2001 - 4:30 pm UTC

Last updated: June 19, 2008 - 1:56 pm UTC

Version: 8.1.6

Viewed 10K+ times! This question is

You Asked

The following is a code snippet obtained from a much larger stored procedure within a package. The error I am receiving both in this code snippet and when running the same code in the stored procedure is as follows:
DECLARE
*
ERROR at line 1:
ORA-01007: variable not in select list
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1099
ORA-06512: at "SYS.DBMS_SQL", line 333
ORA-06512: at line 19

Unless I am missing the obvious, I see no reason why this occurring. If you make the dynamic query SELECT into a static SELECT-INTO, removing the additional single quotes needed for the interpretation of the string to include a single quote, the query works. I also have additonal dynamic queries within other stored procedures within the package needing the additional single quotes which work so that should not be the issue. Below is the code snippet when run produces the error. The database user against which this was run has the necessary SELECT privileges on the necessary V_$ views. Also, since this is taking place in an Oracle8i I have hard-coded the vIncludePoolColumn variable to be TRUE as the V_$SGASTAT.POOL column exists.

DECLARE
vDummy NUMBER(38);
vDynamicCursor NUMBER(38);
vIncludePoolColumn BOOLEAN := TRUE;
vSharedPoolAvailable NUMBER(38);
vSharedPoolFree NUMBER(38);
BEGIN
vDynamicCursor := DBMS_SQL.OPEN_CURSOR;

IF (vIncludePoolColumn) THEN
DBMS_SQL.PARSE(vDynamicCursor,'SELECT DECODE(UPPER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),LENGTH(LTRIM(RTRIM(VP.VALUE))),1)),''K'',TO_NUMBER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),1,LENGTH(LTRIM(RTRIM(VP.VALUE))) - 1)) * (1024),''M'',TO_NUMBER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),1,LENGTH(LTRIM(RTRIM(VP.VALUE))) - 1)) * (1024 * 1024),TO_NUMBER(LTRIM(RTRIM(VP.VALUE)))),VS.BYTES FROM SYS.V_$SGASTAT VS,SYS.V_$PARAMETER VP WHERE UPPER(LTRIM(RTRIM(VS.POOL))) = ''SHARED POOL'' AND UPPER(LTRIM(RTRIM(VS.NAME))) = ''FREE MEMORY'' AND UPPER(LTRIM(RTRIM(VP.NAME))) = ''SHARED_POOL_SIZE''' || ' ',DBMS_SQL.NATIVE);
ELSE
DBMS_SQL.PARSE(vDynamicCursor,'SELECT DECODE(UPPER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),LENGTH(LTRIM(RTRIM(VP.VALUE))),1)),''K'',TO_NUMBER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),1,LENGTH(LTRIM(RTRIM(VP.VALUE))) - 1)) * (1024),''M'',TO_NUMBER(SUBSTR(LTRIM(RTRIM(VP.VALUE)),1,LENGTH(LTRIM(RTRIM(VP.VALUE))) - 1)) * (1024 * 1024),TO_NUMBER(LTRIM(RTRIM(VP.VALUE)))),VS.BYTES FROM SYS.V_$SGASTAT VS,SYS.V_$PARAMETER VP WHERE UPPER(LTRIM(RTRIM(VS.NAME))) = ''FREE MEMORY'' AND UPPER(LTRIM(RTRIM(VP.NAME))) = ''SHARED_POOL_SIZE''' || ' ',DBMS_SQL.NATIVE);
END IF;

vDummy := DBMS_SQL.EXECUTE(vDynamicCursor);

WHILE (DBMS_SQL.FETCH_ROWS(vDynamicCursor) > 0) LOOP
DBMS_SQL.COLUMN_VALUE(vDynamicCursor,1,vSharedPoolAvailable);
DBMS_SQL.COLUMN_VALUE(vDynamicCursor,2,vSharedPoolFree);
END LOOP;

DBMS_SQL.CLOSE_CURSOR(vDynamicCursor);
END;
/


and Tom said...

You are missing the necessary call to dbms_sql.define_column.

You must DEFINE all of your outputs -- tell us what datatypes you would like to fetch them into.

Here is a small example. In the first procedure, i comment out the call to dbms_sql.define_column. It fails with your ORA-1007. Simply uncomment the needed line and it works fine:

ops$tkyte@ORA8I.WORLD> create or replace
2 function get_row_cnts( p_tname in varchar2 ) return number
3 as
4 l_theCursor integer default dbms_sql.open_cursor;
5 l_columnValue number default NULL;
6 l_status integer;
7 begin
8 dbms_sql.parse( l_theCursor,
9 'select count(*) from ' || p_tname,
10 dbms_sql.native );
11
12 -- dbms_sql.define_column( l_theCursor, 1, l_columnValue );
13 l_status := dbms_sql.execute(l_theCursor);
14 if ( dbms_sql.fetch_rows(l_theCursor) > 0 )
15 then
16 dbms_sql.column_value( l_theCursor, 1, l_columnValue );
17 end if;
18 dbms_sql.close_cursor( l_theCursor );
19 return l_columnValue;
20 end;
21 /

Function created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> begin
2 dbms_output.put_line('Emp has this many rows ' ||
3 get_row_cnts('emp'));
4 end;
5 /
begin
*
ERROR at line 1:
ORA-01007: variable not in select list
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1099
ORA-06512: at "SYS.DBMS_SQL", line 333
ORA-06512: at "OPS$TKYTE.GET_ROW_CNTS", line 15
ORA-06512: at line 2


ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> create or replace
2 function get_row_cnts( p_tname in varchar2 ) return number
3 as
4 l_theCursor integer default dbms_sql.open_cursor;
5 l_columnValue number default NULL;
6 l_status integer;
7 begin
8 dbms_sql.parse( l_theCursor,
9 'select count(*) from ' || p_tname,
10 dbms_sql.native );
11
12 dbms_sql.define_column( l_theCursor, 1, l_columnValue );
13 l_status := dbms_sql.execute(l_theCursor);
14 if ( dbms_sql.fetch_rows(l_theCursor) > 0 )
15 then
16 dbms_sql.column_value( l_theCursor, 1, l_columnValue );
17 end if;
18 dbms_sql.close_cursor( l_theCursor );
19 return l_columnValue;
20 end;
21 /

Function created.

ops$tkyte@ORA8I.WORLD> begin
2 dbms_output.put_line('Emp has this many rows ' ||
3 get_row_cnts('emp'));
4 end;
5 /
Emp has this many rows 15

PL/SQL procedure successfully completed.


Rating

  (17 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

dynamic query doesn't return any rows

Leonardo Zacché, July 08, 2003 - 4:43 pm UTC

Tom, can you ever imagine WHY the exact same query returns 10 rows (perfect result) if I run it from SQL*Plus prompt, but if I run it as a (pseudo-)Dynamic query, the fetch_rows returns always 0 (zero)? I mean pseudo-Dynamic because I just put the select statement in a string and run it. It seems to be no mis-written, it parses ok, runs ok, but return zero rows. Can you imagine why?
Thanks!

Tom Kyte
July 08, 2003 - 6:19 pm UTC

hmm, without the code, no -- I cannot see your issue from here.  perhaps you are doing dynamic sql wrong.

it should look like this:

ops$tkyte@ORA920> select ename from emp;

ENAME
----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER

14 rows selected.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          type rc is ref cursor;
  3          l_cursor rc;
  4          l_ename  emp.ename%type;
  5  begin
  6          open l_cursor for 'select ename from emp';
  7          loop
  8                  fetch l_cursor into l_ename;
  9                  exit when l_cursor%notfound;
 10                  dbms_output.put_line( l_ename );
 11          end loop;
 12          close l_cursor;
 13  end;
 14  /
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER

PL/SQL procedure successfully completed. 

DBMS_SQL vs Native Dynamic SQL?

Kamal Kishore, October 03, 2003 - 3:09 pm UTC

Hi Tom,
At the following link:
</code> http://www.devx.com/gethelpon/10MinuteSolution/16610 <code>

The author indicates that:
<quote>
According to Oracle, Native Dynamic SQL provides 30 to 60 percent performance improvements over DBMS_SQL.
</quote>

Do you agree with this assertion?
Is the performance difference so significant so as to prompt the programmers to start re-writing already running code (which uses DBMS_SQL) to make use of the native dynamic sql features?

To me, it seems to suggest that DBMS_SQL should be used ONLY and ONLY in such extreme cases where Native Dynamic SQL can not perform what you are trying to acomplish.
What is your viewpoint?
Thanks,


Tom Kyte
October 03, 2003 - 3:41 pm UTC

for a single statment -- executed once, never to be executed again

yes.


for loading 100,000 rows into a table whose name you didn't know until run time?

no.



If you are going to execute the same statement over and over with different inputs -- dbms_sql. You can keep the statement open and parse it ONCE per session instead of ONCE per execution (as NDS would have you do)


If you are going to execute a statment once in your session, NDS is dandy.


But then again, if you just do it once, well -- 30%-60% of an already pretty small number is not very much so I probably would not rip code apart just for that.

Thank You

Kamal Kishore, October 05, 2003 - 11:07 pm UTC

Thanks Tom.
As always, great insight and to the point response.
Thanks,


DBMS_SQL Problem in executing Dynamic Sql

Shah Nawaz, March 28, 2008 - 5:08 am UTC

Hi TOM as i understand -->TheOracleMaster,
I have a table where table name and column name are stored
e.g.
------------------------------------------------------------------------------
T_name col1 col2 col3 col4 Col5............
-----------------------------------------------------------------------------
LOG_PR_DT PR_CODE Audit_trail_id Qty
LOG_PR_DT_LOC PR_CODE Audit_trail_id Qty loc_id Loc_name
LOG_PR_DT_DIS PR_CODE Audit_trail_id Qty Dis_id Status
.........................
.................................

I am writing a procedure which will read this table and
create a select list and where clause from above table based on
T_name,and corresponding columns then compare the last row
and second last row in the passed table and find out the
changes in above columns....
In following example
vDynamicCursor selects the last row
vDynamicCursor_1 selects the second last row
Then i need to compare them and store the result in Audit_Trail_Dt

Part of procedure-->
CREATE OR REPLACE PROCEDURE DYNAMIC_AUDIT_TRAIL_NEW
(
pTable_Name Varchar2,
pDocument_Code Varchar2,
pAudit_Trail_id Number
) is
declaration
:::::::::::::::::::
:::::::::::::::;;
Begin
Open vDynamicCursor For vQuery_New; --read the latest row
Loop
Fetch vDynamicCursor into pRec;


Open vDynamicCursor_1 For vQuery_Old; --read second last row
Loop
Fetch vDynamicCursor_1 into vRec;

vCompare_List:= 'Begin If pRec.PR_CODE <> vRec.PR_CODE Then Insert into Audit_Trail_Dt Values (pRec.Audit_Trail_Id,pRec.Audit_Trail_Id,'||''''||'LOG_PR_DT.PR_CODE'||''''||',vRec.PR_CODE,pRec.PR_CODE); End If; End;';

oc := dbms_sql.open_cursor;
dbms_sql.parse(oc, vCompare_List, dbms_sql.native);
ec := dbms_sql.execute(oc);
dbms_sql.close_cursor(oc);

Exit When vDynamicCursor_1%NotFound;
End Loop;
Exit When vDynamicCursor%NotFound;
End Loop;
End;

I am getting following error-->>

ORA-06550: line 1, column 119:
PL/SQL: ORA-00984: column not allowed here
ORA-06550: line 1, column 7:
PL/SQL: SQL Statement ignored
ORA-06512: at "SYS.DBMS_SYS_SQL", line 906
ORA-06512: at "SYS.DBMS_SQL", line 39
ORA-06512: at "ADMIN1.DYNAMIC_AUDIT_TRAIL_NEW", line 433
ORA-06512: at line 2

I tried the execute immediate but still facing issues.

Please help.

Tom Kyte
March 31, 2008 - 7:26 am UTC

...
In following example
vDynamicCursor selects the last row
vDynamicCursor_1 selects the second last row
Then i need to compare them and store the result in Audit_Trail_Dt
.....

oh no, you are going to invent the worlds slowest, most inefficient audit trail process ever? Is that your goal?


there is no such thing as "a last row", or "a second to last row"

Instead of posting code that doesn't work, please post a specification of what you really need to do.

and we will almost certainly tell you to abandon dynamic sql all together (I've no idea why you would use dynamic sql when static sql obviously will work) and abandon this generic code totally - you don't need it, you don't want it, you want to do things straight forward and simply.

Audit Trail

Shahnawaz, April 01, 2008 - 4:45 am UTC

Hi TOM (The Oracle Master),
Thanks for your help,
Let me explain my requirement in detail,
Sorry for writing so much, i understand you do not appreciate long updates but no choice,

Please spent some time to let me know if it is possible??

I have a table where table name and column name are stored
e.g.
---------------------------------------------------------------------------------
T_name col1 col2 col3 col4 Col5........
---------------------------------------------------------------------------------
LOG_PR_DT Pr_line_no Unit_Price Qty uom
LOG_PR_DT_LOC Pr_line_no Loc_id Qty Line_value status
LOG_PR_DT_DIS Pr_line_no Loc_id Dis_id Status
.........................
.................................

If table_name is passed to me, i can create a query from above table like parameters passed to me is table_name and pAudit_trail_id
like table_name = 'LOG_PR_DT' and pAudit_trail_id = 17382 (contains more than one line)

vQuery_New-->
Select Distinct a.PR_LINE_NO,a.UNIT_PRICE, a.QTY, a.UOM
From LOG_PR_DT a
Where a.PR_CODE = 'DEV002EPR08000382'
and a.Audit_Trail_Id = 17382
and a.rowid in (Select Max(b.rowid)
from LOG_PR_DT b
Where a.PR_CODE = b.PR_CODE
and a.PR_LINE_NO = b.PR_LINE_NO
and b.Audit_Trail_Id = 17382)
Order By 1

Then i need to search for "second last" audit_trail_id and
compare it with above records
second last Audit_trail_id = 17379
vQuery_Old-->
Select Distinct a.PR_LINE_NO,a.UNIT_PRICE, a.QTY, a.UOM
From LOG_PR_DT a
Where a.PR_CODE = 'DEV002EPR08000382'
and a.Audit_Trail_Id = 17379
and a.rowid in (Select Max(b.rowid)
from LOG_PR_DT b
Where a.PR_CODE = b.PR_CODE
and a.PR_LINE_NO = b.PR_LINE_NO
and b.Audit_Trail_Id = 17379)
Order By 1
Then i need to compare it based on line by line, (currently i am getting a 3*3 cross product as each cursor is of 3 records in this case). if any changes found then store the value in a table in format like table_name>audit_trail_id>Changed_Column>Pr_Line_No old_value new_value

---------------------------------------------------------------------------------
Begin --Dynamic Cursor
vQuery_New := vColumn_List||vWhere_Clause_1||vOrder_By_Clause;
vQuery_Old := vColumn_List||vWhere_Clause_Old||vOrder_By_Clause;

oc_n := dbms_sql.open_cursor; --Open last rows (contains 3 lines i.e Pr_line_no =1 , 2 and 3)
dbms_sql.parse(oc_n, vQuery_New, dbms_sql.native);
dbms_sql.describe_columns(oc_n, n_colCnt, n_descTbl );

oc := dbms_sql.open_cursor; --Open second last rows (contains 3 lines i.e Pr_line_no =1 , 2 and 3)
dbms_sql.parse(oc, vQuery_Old, dbms_sql.native);

/* I want to pass pr_line_no, loc_id from "oc_n" to "oc"
so that i have one to one record, currently i am getting
cross product of 3*3 lines from each cursor*/

dbms_sql.describe_columns(oc, l_colCnt, l_descTbl );

for i in 1 .. l_colCnt
loop
l_idx( l_descTbl(i).col_name ) := i; --Define column
dbms_sql.define_column( oc, i, l_columnValue, 4000 );

l_status := dbms_sql.execute(oc);
ec_n := dbms_sql.execute(oc_n);

if ( dbms_sql.fetch_rows(oc_n) > 0 ) --Open last row cursor
then
loop
exit when ( dbms_sql.fetch_rows(oc) <= 0 ); --open second last row cursor
for j in 1 .. l_colCnt
loop
If i = j Then
dbms_sql.column_value(oc, j, l_columnValue );
for m in 1 .. n_colCnt
loop
If i = m Then
If i = m and m=j and j=n then
dbms_sql.column_value(oc_n, m, n_columnValue );

If l_descTbl(i).col_name = l_descTbl(n).col_name
and l_columnValue <> n_columnValue then

Insert into Audit_Trail_Dt
Values(pAudit_Trail_id,pAudit_Trail_id,pTable_Name||'.'||l_descTbl(n).col_name,l_columnValue,n_columnValue);

End If;--If vRec.Item_Description <> pRec.Item_Description Then
End if; --i = m and m=j then
End If; -- If i = m Then
end loop;-- for m in 1 .. n_colCnt loop

End If; -- If i = j Then
end loop; --for i in 1 .. l_colCnt loop
end loop; --exit when ( dbms_sql.fetch_rows(oc) <= 0 );

End if; --if ( dbms_sql.fetch_rows(oc_n) > 0 )

end loop; --for i in 1 .. l_colCnt

end loop; --for n in 1 .. n_colCnt
dbms_sql.close_cursor(oc);


End; --Dynamic Cursor
--End Dynamic Cursor ===============================================

In above logic there is no performance problem although i am doing excess iterations of loops.

I am facing challenges in
1. passing parameters from cursor "oc" to "oc_n" if i could pass the parameters to inner cursor "oc_n" i can compare the values line by line, as currently it is comparing oc.pr_line_no 1 with oc_n.pr_line_no 1 , oc_n.pr_line_no 2 and oc_n.pr_line_no 3.
Please note that i can not hard code pr_line_no as the procedure will run for other tables also which has some other column but do not have pr_line_no.

2. do not have any idea what order to follow when using procedures of dbms_sql
Please, Please, Please help...

Thanks and regards
Shahnawaz


Tom Kyte
April 01, 2008 - 9:34 am UTC

I do not like this approach at all - sorry.

this is never going to scale
this is never going to perform
this will be rife with errors

you are free to spend your time writing gobs of code - I'm not. DBMS_SQL is well documented and there are literally hundreds of examples on this site alone - let alone on the internet as a whole.

but I recommend a complete redesign of this audit requirement from the ground up and a static implementation.

Result to compare

Shahnawaz, April 01, 2008 - 4:54 am UTC

Hi TOM,
Following is the data-->>
Select Distinct a.Audit_Trail_Id,a.PR_CODE,a.PR_LINE_NO,a.UNIT_PRICE, a.QTY, a.UOM
From LOG_PR_DT a
Where a.PR_CODE = 'DEV002EPR08000382'
and a.Audit_Trail_Id = 17382
and a.rowid in (Select Max(b.rowid)
from LOG_PR_DT b
Where a.PR_CODE = b.PR_CODE
and a.PR_LINE_NO = b.PR_LINE_NO
and b.Audit_Trail_Id = 17382)
union all
Select Distinct a.Audit_Trail_Id,a.PR_CODE,a.PR_LINE_NO,a.UNIT_PRICE, a.QTY, a.UOM
From LOG_PR_DT a
Where a.PR_CODE = 'DEV002EPR08000382'
and a.Audit_Trail_Id = 17379
and a.rowid in (Select Max(b.rowid)
from LOG_PR_DT b
Where a.PR_CODE = b.PR_CODE
and a.PR_LINE_NO = b.PR_LINE_NO
and b.Audit_Trail_Id = 17379)
Order By 1

Result

AUDIT_TRAIL_ID PR_CODE PR_LINE_NO UNIT_PRICE QTY UOM
17379 DEV002EPR08000382 1 15 2 EA
17379 DEV002EPR08000382 2 2 5 EA
17379 DEV002EPR08000382 3 3 4 PC
17382 DEV002EPR08000382 1 12 2 EA
17382 DEV002EPR08000382 2 2 3 EA
17382 DEV002EPR08000382 3 3 4 EA

Result

Shahnawaz, April 01, 2008 - 5:16 am UTC

Result would be like this

LOG_PR_DT.UNIT_PRICE.Line1 15 >1 2
LOG_PR_DT.UOM.Line3 PC > EA
LOG_PR_DT.QTY.Line2 5 > 3

In short,
Reading table_name and columns from a table or (data_dictionary) and comparing multiple but unique last and second_last records of the column combinations, and storing the column_name, old_value and new_value.

Is it achievable??

DBMS_SQL ROWCOUNT

Shah Nawaz, April 08, 2008 - 6:01 am UTC

Hi TOM,
Thanks for your expert comments.
I will review my strategy but i Need your urgent help on the following(Please note that i can not use execute immediate as i need to iterate with following example)

Please Note i have a query
Output of query:-
Select Distinct a.PR_LINE_NO, a.LOCATION_LINE_NO From LOG_PR_DT_LOCATION a Where a.audit_trail_id = 17382

PR_LINE_NO,LOCATION_LINE_NO
1,1
1,2
2,1
2,2
3,1
3,2
3,3
3,4

8 rows selected.

When I run this query in a dynamic cursor, cursor noi_c, it exit after two rows whereas it should exit after 8 rows.

Can you tel me where i am wrong????

Part Of procedure:--
t_colCnt number := 0;
t_columnValue varchar2(4000);
t_descTbl dbms_sql.desc_tab;

No_Of_It_Query:='Select Distinct a.PR_LINE_NO, a.LOCATION_LINE_NO From LOG_PR_DT_LOCATION a Where a.audit_trail_id = 17382';

noi_c := dbms_sql.open_cursor;

dbms_sql.parse(noi_c, No_Of_It_Query, dbms_sql.native);
dbms_sql.describe_columns(noi_c, t_colCnt, t_descTbl );

for no_of_col in 1 .. t_colCnt
loop

l_idx(t_descTbl(no_of_col).col_name ) := no_of_col;
dbms_sql.define_column( noi_c, no_of_col, t_columnValue, 4000 );
noi_status := dbms_sql.execute_and_fetch(noi_c);

Dbms_Output.Put_Line('noi_status '||noi_status);
dbms_sql.column_value(noi_c,no_of_col, t_columnValue );
Dbms_Output.Put_Line('Row '||no_of_col||' '||t_descTbl(no_of_col).col_name ||' - '||t_columnValue);

end loop; --for no_of_col in 1 .. t_colCnt

Dbms_Output.Put_Line('Exiting Procedure');
dbms_sql.close_cursor(noi_c);
End;

Output of Procedure-->>
noi_status 1
Row 1 PR_LINE_NO - 1
noi_status 1
Row 2 LOCATION_LINE_NO - 1
Exiting Procedure

When I run this query in a dynamic cursor, cursor noi_c, it exit after two rows whereas it should
exit after 8 rows.

Can you tel me where i am wrong????
Tom Kyte
April 09, 2008 - 2:06 pm UTC

for no_of_col in 1 .. t_colCnt

???? why are you fetching instead of that loop.

search this

you have gone wrong from just about the very beginning.

you

a) open
b) parse
c) define
d) THEN FETCH AND LOOP and fetch until no more data


search this site for dump_csv for an example

Hi Tom

Shah Nawaz, April 15, 2008 - 2:26 am UTC

Hi TOM,

You have been very helpful until now, This is a very good site and i learned a lot, while completing my Dynamic Procedure which i asked you in my earlier post "Audit Trail April 1, 2008 - 4am US/Eastern"

I am happy to learn a lot about DBMS_SQL through this site only.

I manage to write a code which i thought impossible initially. It takes the table name and column names from a table and compare them and store the result in a table. and my approach remains same as i posted initially.

Thanks again for all your help.

Regards
Shahnawaz


Dynamic SQL and dblinks

Shivdeep Modi, June 02, 2008 - 7:57 am UTC

Hi,

I need to setup a dynamic PL/SQL which will connect remotely using database links
and get some data.
I've simplified the dynamic sql for brevity.

NCLDBA AS NORMAL ON ncldb > desc TSREPORT_DBLIST
Name Null? Type
----------------------------------------- -------- ------------
DBNAME NOT NULL VARCHAR2(10)

NCLDBA AS NORMAL ON ncldb >

create or replace procedure timepass as
dummy_sql varchar2(2000);
dynamic_cursor number;
rows_processed number;
begin

dynamic_cursor := DBMS_SQL.OPEN_CURSOR;
dummy_sql:='select user from dual@:dblink';
--Trying to parse once.
DBMS_SQL.PARSE(dynamic_cursor, dummy_sql,dbms_sql.native);

for db in ( select dbname from TSREPORT_DBLIST)
loop

DBMS_SQL.BIND_VARIABLE(dynamic_cursor, ':dblink', 'EB00');
rows_processed := DBMS_SQL.EXECUTE(dynamic_cursor);

end loop;

DBMS_SQL.close_cursor(dynamic_cursor);
end;
/

The procedure gets created but on running I get the error:

*
ERROR at line 1:
ORA-01729: database link name expected
ORA-06512: at "SYS.DBMS_SYS_SQL", line 826
ORA-06512: at "SYS.DBMS_SQL", line 39
ORA-06512: at "NCLDBA.TIMEPASS", line 10
ORA-06512: at line 1

If I formulate the dynamic sql using: select user from dual@db.dbname, I am sure it would work
Using this we will get different sqls for all the values of the database link(:dblink) hence in principle,
the sqls cannot be shared.
I could probably use session_cached_cursors to improve performance.
What is the besy way forward?

Regards,
Shivdeep
Tom Kyte
June 02, 2008 - 11:35 am UTC

you cannot bind identifiers


you cannot "select * from :bind" which is in effect what you are trying to do.


session cached cursors likely will have no impact on this at all - because I would hope you would not be running this sql more than once in a session anyway - if your application relies on remote data constantly and continuously - you have done it wrong.

so, you'll be using 'select ...from t@' || dblink

Dynamic SQL and dblinks

Shivdeep Modi, June 03, 2008 - 6:32 am UTC

Hi,

You are right, indentifiers cannot be bound.
I am using table@dblink to make it work.
I am doing a scheduled data fetching activity every 1000 hours.

Regards,
Shivdeep
Tom Kyte
June 03, 2008 - 11:40 am UTC

so, 1000 hours, don't worry about sharing that sql, odds that it would be in the shared pool 1000 hours later are....

near 0% I would think

varying column list

vaibhav, June 10, 2008 - 6:08 am UTC

Hi...

Please help me out here.

GUI has a drop down select from where tthe user selects START_DATE and END_DATE.
I have to count the no. of errors occurred on those days.

EG;

If from_date = 10th June and to_date = 13th June, then i need to have o/p like this:

ERROR_CODE 10th 11th 12th 13th total
err1 5 0 5 0 10
err5 2 2 2 2 6
err3 10 10 10 0 30

This indicates that my SQL query would ahve dynamic columns. I have written the following proc
to get the above output but i think there could be a better way to write it.



PROCEDURE sps_daily_errors(p_start_date IN STRING,
p_end_date IN STRING,
o_cur_error_count OUT SYS_REFCURSOR)
AS

v_sql VARCHAR2(4000);
num_of_days NUMBER;
interval NUMBER(10) := 0;

BEGIN

num_of_days := trunc(to_number(substr((TO_TIMESTAMP(p_end_date, 'DD/MM/YYYY HH24:MI')-TO_TIMESTAMP(p_start_date, 'DD/MM/YYYY HH24:MI')),1,instr(TO_TIMESTAMP(p_end_date, 'DD/MM/YYYY HH24:MI')-TO_TIMESTAMP(p_start_date, 'DD/MM/YYYY HH24:MI'),' '))));

v_sql := ' SELECT me.error_code, '||CHR(10)||
' COUNT (CASE WHEN me.error_timestamp BETWEEN TO_TIMESTAMP('''||p_start_date||''', ''DD/MM/YYYY HH24:MI'') '||
' AND TRUNC(TO_TIMESTAMP('''||p_start_date||''', ''DD/MM/YYYY HH24:MI'') + 1 - (1/24*60*60)) THEN 1 ELSE NULL END) day1, '||CHR(10);

FOR i in 1..(num_of_days-1)
LOOP

v_sql := v_sql || ' COUNT (CASE WHEN TRUNC(me.error_timestamp) = TRUNC((TO_TIMESTAMP('''||p_start_date||''', ''DD/MM/YYYY HH24:MI'') + ' || (interval + 1) ||'))'||
' THEN 1 ELSE NULL END) day1, '||CHR(10);

interval := interval + 1;

END LOOP;

v_sql := v_sql || ' COUNT (CASE WHEN TRUNC(me.error_timestamp) BETWEEN TRUNC(TO_TIMESTAMP('''||p_end_date||''', ''DD/MM/YYYY HH24:MI'')) '||
' AND TO_TIMESTAMP('''||p_end_date||''', ''DD/MM/YYYY HH24:MI'') THEN 1 ELSE NULL END) day1, '||CHR(10);

v_sql := SUBSTR(v_sql, 1, LENGTH(v_sql)-2);

v_sql := v_sql ||CHR(10)|| ' SUM (COUNT(1)) OVER (PARTITION BY error_code ORDER BY error_code) total_count '||CHR(10)||
' FROM mhs_errors me'||CHR(10)||
' WHERE AND me.error_timestamp BETWEEN TO_TIMESTAMP('''||p_start_date||''', ''DD/MM/YYYY HH24:MI'') AND TO_TIMESTAMP('''||p_end_date||''', ''DD/MM/YYYY HH24:MI'') '||CHR(10)||
' GROUP BY me.error_code ';

OPEN o_cur_error_count FOR v_sql USING p_service_id;

END sps_daily_errors;


The worst part is i cannot even use bind variables since i dont kow how many times the loop would be executed.

Also, since i dont know the number of columns the query would return, i cannot even test it. all i did was use PUT_LINE
to display what sql string was getting generated.

declare
a sys_refcursor;
x varchar2(100);
y number;
begin

sps_daily_weekly_errors('09/06/2008 20:00', '09/06/2008 21:00', a);

end;

I get the follwoing sql string as the o/p:

SELECT me.error_code,
COUNT (CASE WHEN me.error_timestamp BETWEEN TO_TIMESTAMP('09/06/2008 20:00', 'DD/MM/YYYY HH24:MI') AND TRUNC(TO_TIMESTAMP('09/06/2008 20:00', 'DD/MM/YYYY HH24:MI') + 1 - (1/24*60*60)) THEN 1 ELSE NULL END) day1,
COUNT (CASE WHEN TRUNC(me.error_timestamp) = TRUNC((TO_TIMESTAMP('09/06/2008 20:00', 'DD/MM/YYYY HH24:MI') + 1)) THEN 1 ELSE NULL END) day1,
COUNT (CASE WHEN TRUNC(me.error_timestamp) = TRUNC((TO_TIMESTAMP('09/06/2008 20:00', 'DD/MM/YYYY HH24:MI') + 2)) THEN 1 ELSE NULL END) day1,
COUNT (CASE WHEN TRUNC(me.error_timestamp) = TRUNC((TO_TIMESTAMP('09/06/2008 20:00', 'DD/MM/YYYY HH24:MI') + 3)) THEN 1 ELSE NULL END) day1,
COUNT (CASE WHEN TRUNC(me.error_timestamp) = TRUNC((TO_TIMESTAMP('09/06/2008 20:00', 'DD/MM/YYYY HH24:MI') + 4)) THEN 1 ELSE NULL END) day1,
COUNT (CASE WHEN TRUNC(me.error_timestamp) = TRUNC((TO_TIMESTAMP('09/06/2008 20:00', 'DD/MM/YYYY HH24:MI') + 5)) THEN 1 ELSE NULL END) day1,
COUNT (CASE WHEN TRUNC(me.error_timestamp) BETWEEN TRUNC(TO_TIMESTAMP('15/06/2008 21:00', 'DD/MM/YYYY HH24:MI')) AND TO_TIMESTAMP('15/06/2008 21:00', 'DD/MM/YYYY HH24:MI') THEN 1 ELSE NULL END) day1,
SUM (COUNT(1)) OVER (PARTITION BY error_code ORDER BY error_code) total_count
FROM mhs_errors me
WHERE me.error_timestamp BETWEEN TO_TIMESTAMP('09/06/2008 20:00', 'DD/MM/YYYY HH24:MI') AND TO_TIMESTAMP('15/06/2008 21:00', 'DD/MM/YYYY HH24:MI')
GROUP BY me.error_code

How do i implement my functionality here?
Is there a way to achieve the above result where in the columns are dynamic?

I searched your site but coudnt find anything

Please help me out.......
Tom Kyte
June 10, 2008 - 8:29 am UTC

Here are two approaches, the first is bind as bind friendly (each date range would create a new sql) as the second (here there would be only as many sql's as number of days between ranges - that is, every 5 day range - regardless of start/end dates would use the same sql)


ops$tkyte%ORA10GR2> create or replace view error_data
  2  as
  3  select object_type error_code, created dt
  4    from all_objects
  5  /

View created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create or replace function get_report( p_sdate in date, p_edate in date ) return sys_refcursor
  2  as
  3      l_sql long := 'select error_code ';
  4      l_col long := ', count( decode( trunc(dt), to_date( ''$X'', ''yyyymmdd'' ), 1 ) ) "$X" ';
  5      l_cur sys_refcursor;
  6  begin
  7      for i in 1 .. p_edate - p_sdate + 1
  8      loop
  9          l_sql := l_sql || replace( l_col, '$X', to_char(p_sdate+i-1,'yyyymmdd') );
 10      end loop;
 11      l_sql := l_sql ||
 12               ' from error_data where dt >= :sdate and dt < :edate+1 ' ||
 13               ' group by error_code';
 14
 15      dbms_output.put_line( l_sql );
 16      open l_cur for l_sql using p_sdate, p_edate;
 17      return l_cur;
 18  end;
 19  /

Function created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> variable x refcursor
ops$tkyte%ORA10GR2> exec :x := get_report( trunc(sysdate-5), trunc(sysdate) )
select error_code , count( decode( trunc(dt), to_date( '20080605', 'yyyymmdd' ), 1 ) ) "20080605" , count( decode( trunc(dt), to_date( '20080606', 'yyyymmdd' ), 1 ) ) "20080606" , count( decode( trunc(dt), to_date( '20080607', 'yyyymmdd' ), 1 ) ) "20080607" , count( decode( trunc(dt), to_date( '20080608', 'yyyymmdd' ), 1 ) ) "20080608" , count( decode( trunc(dt), to_date( '20080609', 'yyyymmdd' ), 1 ) ) "20080609" , count( decode( trunc(dt), to_date( '20080610', 'yyyymmdd' ), 1 ) ) "20080610"  from error_data where dt >= :sdate and dt < :edate+1  group by error_code

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> print x

ERROR_CODE            20080605   20080606   20080607   20080608   20080609   20080610
------------------- ---------- ---------- ---------- ---------- ---------- ----------
PACKAGE BODY                 0          0          0          0          2          0
PACKAGE                      0          0          0          0          2          0
FUNCTION                     0          0          0          0          0          1
VIEW                         0          0          0          0          0          1
TABLE                        0          0          0          0          4          0

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create or replace context my_ctx using get_report
  2  /

Context created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create or replace function get_report( p_sdate in date, p_edate in date ) return sys_refcursor
  2  as
  3      l_sql long := 'select error_code, to_char( :sdate1, ''fmDDth Mon "thru "'' ) || to_char(:edate1, ''fmDDth Mon'' ) range ';
  4      l_col long := ', count( decode( trunc(dt), to_date( sys_context( ''my_ctx'', ''DAY_$X'' ), ''yyyymmdd'' ), 1 ) ) "Day $X" ';
  5      l_cur sys_refcursor;
  6  begin
  7      for i in 1 .. p_edate - p_sdate + 1
  8      loop
  9          dbms_session.set_context( 'my_ctx', 'day_' || i, to_char( p_sdate+i-1, 'yyyymmdd' ) );
 10          l_sql := l_sql || replace( l_col, '$X', i );
 11      end loop;
 12      l_sql := l_sql ||
 13               ' from error_data where dt >= :sdate2 and dt < :edate2+1 ' ||
 14               ' group by error_code';
 15
 16      dbms_output.put_line( l_sql );
 17      open l_cur for l_sql using p_sdate, p_edate, p_sdate, p_edate;
 18      return l_cur;
 19  end;
 20  /

Function created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> variable x refcursor
ops$tkyte%ORA10GR2> exec :x := get_report( trunc(sysdate-5), trunc(sysdate) )
select error_code, to_char( :sdate1, 'fmDDth Mon "thru "' ) || to_char(:edate1, 'fmDDth Mon' ) range , count( decode( trunc(dt), to_date( sys_context( 'my_ctx', 'DAY_1' ), 'yyyymmdd' ), 1 ) ) "Day 1" , count( decode( trunc(dt), to_date( sys_context( 'my_ctx', 'DAY_2' ), 'yyyymmdd' ), 1 ) ) "Day 2" , count( decode( trunc(dt), to_date( sys_context( 'my_ctx', 'DAY_3' ), 'yyyymmdd' ), 1 ) ) "Day 3" , count( decode( trunc(dt), to_date( sys_context( 'my_ctx', 'DAY_4' ), 'yyyymmdd' ), 1 ) ) "Day 4" , count( decode( trunc(dt), to_date( sys_context( 'my_ctx', 'DAY_5' ), 'yyyymmdd' ), 1 ) ) "Day 5" , count( decode( trunc(dt), to_date( sys_context( 'my_ctx', 'DAY_6' ), 'yyyymmdd' ), 1 ) ) "Day 6"  from error_data where dt >= :sdate2 and dt < :edate2+1  group by error_code

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> print x

ERROR_CODE          RANGE                       Day 1      Day 2      Day 3      Day 4      Day 5      Day 6
------------------- ---------------------- ---------- ---------- ---------- ---------- ---------- ----------
PACKAGE BODY        5TH Jun thru 10TH Jun           0          0          0          0          2          0
PACKAGE             5TH Jun thru 10TH Jun           0          0          0          0          2          0
FUNCTION            5TH Jun thru 10TH Jun           0          0          0          0          0          1
VIEW                5TH Jun thru 10TH Jun           0          0          0          0          0          1
TABLE               5TH Jun thru 10TH Jun           0          0          0          0          4          0




Simply Mind Blowing

Vaibhav, June 11, 2008 - 12:38 am UTC

Hi Tom

Thanks for that quick reply...

The solution is just too goooooood...

Coudn't ask for anything better...

Simply mindblowing

Thanks a TON!!!

Vaibhav, June 17, 2008 - 2:11 pm UTC

Hi tom...

Vaibhav is back again with a doubt...

In the above procedure, i didnt know the number of days for which i would be needing the error count. But now i have a proc wherein i kno how many columns would be there in the output but this time the problem is how do i generate the column name...

below is the query...

SELECT me.error_code,
COUNT (CASE when me.error_timestamp >= (TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI') - 3/24) AND me.error_timestamp < (TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI') - 2/24)
THEN 1 ELSE NULL END) "interval1",
COUNT (CASE when me.error_timestamp >= (TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI') - 2/24) AND me.error_timestamp < (TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI') - 1/24)
THEN 1 ELSE NULL END) "interval2",
COUNT (CASE when me.error_timestamp >= (TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI') - 1/24) AND me.error_timestamp < (TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI')
THEN 1 ELSE NULL END) "interval3",
SUM (COUNT(1)) OVER (PARTITION BY error_code ORDER BY error_code) total_count
FROM mhs_errors me
WHERE me.error_timestamp >= TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI:SS.FF') - 3/24 AND me.error_timestamp < TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI:SS.FF')
GROUP BY me.error_code

this query gives me the count of errors in the past 3 hours from p_error_date...

the o/p is...


error_code interval1 interval2 interval3 total_count
err1 10 8 2 20
err1 10 10 10 30
err1 10 10 20 40


say the user has passed p_error_date = '2008-06-18 12:30'

the o/p that i want is...

error_code 09:30-10:30 10:30-11:30 11:30-12:30 total_count
err1 10 8 2 20
err1 10 10 10 30
err1 10 10 20 40


My query is a static query...so will i have to write a dynamic one to get the
above column names using your $X$ technique
Tom Kyte
June 17, 2008 - 3:04 pm UTC

you would need to dynamically generate that, back to square one. column names must be known at parse time, you want unique column names, hence you need to generate the query.

or

p_range1 := TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD
HH24:MI') - 3/24) || '-' || (TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD HH24:MI') - 2/24) ;
p_range2 := ...
p_range3 := ...

select 1 order_col, 'error_code', p_range1, p_range2, p_range3, 'total count'
union all
select
SELECT 2 me.error_code,
to_char(COUNT (CASE when me.error_timestamp >= (TO_TIMESTAMP(p_error_date, 'YYYY-MM-DD
HH24:MI') - 3/24) AND me.error_timestamp < (TO_TIMESTAMP(p_error_date,
'YYYY-MM-DD HH24:MI') - 2/24)
THEN 1 ELSE NULL END)) "interval1",
.....
GROUP BY me.error_code
order by 1, 2;

and just have the first row be the "column heading"

Vaibhav, June 18, 2008 - 1:31 am UTC

your second method worked great...

tooooo Smartttttttttttttt

Thanks a ton

Vaibhav, June 19, 2008 - 12:41 pm UTC

Hi Tom,

Continuing the above question,

what if i want the vertical total as well... i mean the following o/p


error_code int1 int2 int3 total
err1 10 8 2 20
err1 10 10 10 30
err1 10 10 20 40
total 30 28 32 70

It is not a requirement as the java guys are handling it on their side...

i just wanted to know if its possible on our database end...

i scratched my head a lot over it...

i know we can use "compute sum..." in sql to generate report but what if i want that as o/p of the cursor itself

is there any analytic function that can help me here...i went through some functions that get me running total but they are not useful in my case i guess

also, i thought i can write individual queries that get me the error count irrespective of the code for all the intervals separately and then again use union all to append it to the o/p as the last row...

like,

select count(1)
from mhs_errors
where error_timestamp between (1st interval)

get the error count for all the intervals this way and then use union all

but it would definitely be a performance issue since i will have to run those queries again...

can you give me some inputs on this...

this is not a requirement as i already mentioned but just the curiosity to know if its possible ?
Tom Kyte
June 19, 2008 - 1:56 pm UTC

group by rollup does that.

scott%ORA9IR2> select deptno, sum(sal) from emp group by rollup(deptno);

    DEPTNO   SUM(SAL)
---------- ----------
        10       8750
        20      10875
        30       9400
                29025


vaibhav, June 19, 2008 - 11:29 pm UTC

Bravo !!!

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library