how can i do if my select dont come with result using SYS_REFCURSOR ?
what i have try so far is using NO_DATA_FOUND, but its not working, my STATUS keep returning me as = 1
code:
...
MYVARIABLE IN OUT SYS_REFCURSOR
...
OPEN MYVARIABLE FOR
SELECT NAME FROM TABLE WHERE COD = 1;
STATUS := 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
STATUS := 0;
any ideias ?
thanks!
the real sql is something like this:
Edit
V_SQL := 'SELECT SUM(T1.VLRLIQ) VALOR,T1.CODCLI,T1.NOMCLI
,(SELECT METCLI FROM WEB_CRM_CLIVEN T2
WHERE T2.CODCLI = T1.CODCLI AND T2.CODVEN = '|| P_CODVEN ||'
AND T2.MES = '|| V_MES ||' AND T2.ANO = '|| V_ANO ||') META
FROM SAPIENS.USU_VRESNFV T1,SAPIENS.E085CLI T2
WHERE T1.CODVEN = '|| P_CODVEN ||'
AND TO_CHAR(T1.DATEMI,''YYYY'') = '|| V_ANO ||'
AND TO_CHAR(T1.DATEMI,''MM'') = '|| V_MES ||'
AND T1.VENFAT = ''S''
'|| V_CGCCPF ||'
'|| V_NOMCLI ||'
AND T2.CODCLI = T1.CODCLI
AND T1.CODEMP = 1
GROUP BY T1.CODCLI,T1.NOMCLI
UNION
SELECT SUM(''0'') VALOR,CODCLI,NOMCLI,(SELECT METCLI FROM WEB_CRM_CLIVEN T3
WHERE T3.CODCLI = T2.CODCLI AND T3.CODVEN = '|| P_CODVEN ||'
AND T3.MES = '|| V_MES ||' AND T3.ANO = '|| V_ANO ||') META
FROM SAPIENS.E085CLI T2
WHERE
CODCLI IN (SELECT CODCLI FROM WEB_CRM_VEN_CARTEIRA
WHERE CODVEN = '|| P_CODVEN ||' AND MES = '|| V_MES ||' AND ANO = '|| V_ANO ||')
'|| V_CGCCPF ||'
'|| V_NOMCLI ||'
GROUP BY CODCLI,NOMCLI
ORDER BY VALOR DESC';
STATUS := 1;
OPEN RESULTADO FOR V_SQL;
In your code, you are just opening the cursor but not fetching from it. When you open a cursor, PL/SQL executes the query for that cursor. It also identifies the rows that meet the criteria in the WHERE clause and join conditions. The OPEN does not actually retrieve any of these rows; that action is performed by the FETCH statement. You would then use cursor attributes to check if the result set is empty; if it is, then the following cursor attributes would have these values: %FOUND = FALSE, %NOTFOUND = TRUE, and %ROWCOUNT = 0.
Here is an example:
SQL> DECLARE
2 l_cur SYS_REFCURSOR;
3 l_col VARCHAR2 (10);
4 BEGIN
5 OPEN l_cur FOR
6 SELECT 'Hi there' col
7 FROM DUAL
8 WHERE 1 = 0;
9
10 DBMS_OUTPUT.put_line ('Opened cursor');
11
12 FETCH l_cur INTO l_col;
13
14 DBMS_OUTPUT.put_line ('Fetched from cursor');
15
16 IF l_cur%NOTFOUND
17 THEN
18 DBMS_OUTPUT.put_line ('Oops! No data found. Raising exception...');
19 RAISE NO_DATA_FOUND;
20 END IF;
21
22 CLOSE l_cur;
23 EXCEPTION
24 WHEN NO_DATA_FOUND
25 THEN
26 DBMS_OUTPUT.put_line ('Exception raised.');
27 END;
28 /
Opened cursor
Fetched from cursor
Oops! No data found. Raising exception...
Exception raised.
PL/SQL procedure successfully completed.
To slightly modify #Eddie Awad's answer, the usual code pattern I use for fetching from a cursor variable is as follows:
DECLARE
l_cur SYS_REFCURSOR;
l_col VARCHAR2 (10);
BEGIN
OPEN l_cur FOR
SELECT 'Hi there' col
FROM DUAL
WHERE 1 = 0;
DBMS_OUTPUT.PUT_LINE('Opened cursor');
<<cursor_loop>>
LOOP
FETCH l_cur INTO l_col;
DBMS_OUTPUT.PUT_LINE('Fetched from cursor');
EXIT cursor_loop WHEN l_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Process data fetched from cursor');
END LOOP; -- cursor_loop
CLOSE l_cur;
DBMS_OUTPUT.PUT_LINE('Closed cursor');
END;
The idea is to open the cursor variable (or get it back from a procedure), then loop until all rows have been fetched from the cursor.
Share and enjoy.
Related
I created a function and it uses a dynamic sql:
create function check_ref_value
(
table_name varchar2,
code_value number,
code_name varchar2
) return number is
l_query varchar2(32000 char);
l_res number;
begin
l_query := '
select sign(count(1))
into :l_res
from '|| table_name ||'
where '|| code_name ||' = :code_value
';
execute immediate l_query
using in code_value, out l_res;
return l_res;
end;
But when I try to use it I get an exception "ORA-00933: SQL command not properly ended"
What is wrong with this code?
You can use EXECUTE IMMEDIATE ... INTO ... USING ... to get the return value and DBMS_ASSERT to raise errors in the case of SQL injection attempts:
create function check_ref_value
(
table_name varchar2,
code_value number,
code_name varchar2
) return number is
l_query varchar2(32000 char);
l_res number;
begin
l_query := 'select sign(count(1))'
|| ' from ' || DBMS_ASSERT.SIMPLE_SQL_NAME(table_name)
|| ' where ' || DBMS_ASSERT.SIMPLE_SQL_NAME(code_name)
|| ' = :code_value';
execute immediate l_query INTO l_res USING code_value;
return l_res;
end;
/
Which, for the sample data:
CREATE TABLE abc (a, b, c) AS
SELECT 1, 42, 3.14159 FROM DUAL;
Then:
SELECT CHECK_REF_VALUE('abc', 42, 'b') AS chk FROM DUAL;
Outputs:
CHK
1
And:
SELECT CHECK_REF_VALUE('abc', 42, '1 = 1 OR b') AS chk FROM DUAL;
Raises the exception:
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 160
ORA-06512: at "FIDDLE_UVOFONEFDEHGDQJELQJL.CHECK_REF_VALUE", line 10
As for your question:
What is wrong with this code?
Using SELECT ... INTO is only valid in an SQL statement in a PL/SQL block and when you run the statement via EXECUTE IMMEDIATE it is executed in the SQL scope and not a PL/SQL scope.
You can fix it by wrapping your dynamic code in a BEGIN .. END PL/SQL anonymous block (and reversing the order of the bind parameters in the USING clause):
create function check_ref_value
(
table_name varchar2,
code_value number,
code_name varchar2
) return number is
l_query varchar2(32000 char);
l_res number;
begin
l_query := '
BEGIN
select sign(count(1))
into :l_res
from '|| DBMS_ASSERT.SIMPLE_SQL_NAME(table_name) ||'
where '|| DBMS_ASSERT.SIMPLE_SQL_NAME(code_name) ||' = :code_value;
END;
';
execute immediate l_query
using out l_res, in code_value;
return l_res;
end;
/
(However, that is a bit more of a complicated solution that just using EXECUTE IMMEDIATE ... INTO ... USING ....)
db<>fiddle here
I have a procedure which receive as input parameter a record with 170 columns (it is based on the structure of a table).
In the procedure I want to call a debugging procedure one of whose parameters is a text string containing all the field names and values of this record.
For example:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN table_name%ROWTYPE) as
text VARCHAR2(10000) := NULL;
BEGIN
...
text := 'pi_record.column1 = ' || pi_record.column1 || CHR(13) ||
'pi_record.column2 = ' || pi_record.column2 || CHR(13) ||
...
'pi_record.column170 = ' || pi_record.column170;
logging_procedure (text);
...
END;
Is there any simple way to achieve this in a dynamic way (looping through record fields names and values) without enumerating all of them?
Maybe something like this:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN table_name%ROWTYPE) as
text VARCHAR2(10000) := NULL;
BEGIN
...
LOOP in pi_record.columns
text := text || CHR(13) || pi_record.column.name || ' : ' || pi_record.column.value
END LOOP
logging_procedure (text);
...
END;
Many thanks,
Here's one way to do that. A package spec contains a variable whose type matches the one we'll use in a procedure.
SQL> set serveroutput on
SQL> create or replace package pkg_xxx
2 as
3 dept_rec dept%rowtype;
4 end;
5 /
Package created.
SQL> create or replace procedure xxx (pi_record in dept%rowtype)
2 as
3 text varchar2 (10000) := null;
4 l_str varchar2 (200);
5 l_var varchar2 (200);
6 begin
7 pkg_xxx.dept_rec := pi_record;
8
9 for cur_r in ( select column_name
10 from user_tab_columns
11 where table_name = 'DEPT'
12 order by column_id)
13 loop
14 l_str :=
15 'begin '
16 || ':x := to_char(pkg_xxx.dept_rec.'
17 || cur_r.column_name
18 || '); '
19 || 'end; ';
20
21 execute immediate l_str using out l_var;
22
23 text := text || chr (10) || cur_r.column_name || ' = ' || l_var;
24 end loop;
25
26 dbms_output.put_line (text);
27 end;
28 /
Procedure created.
Now, let's pass something to the procedure and see what happens:
SQL> declare
2 cursor c1
3 is
4 select *
5 from dept
6 where deptno = 10;
7
8 c1r c1%rowtype;
9 begin
10 open c1;
11 fetch c1 into c1r;
12 close c1;
13
14 xxx (c1r);
15 end;
16 /
DEPTNO = 10
DNAME = ACCOUNTING
LOC = NEW YORK
PL/SQL procedure successfully completed.
SQL>
Huh, kind of works (if that's what you asked). Of course, it is just an example, you'll have to modify it if you want to get something really smart (hint: DATE columns).
The only idea I have is to insert the record into a TEMP table:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN TABLE_NAME%ROWTYPE) AS
TEXT VARCHAR2(10000) := NULL;
item VARCHAR2(1000);
TABLE_DOES_NOT_EXIST EXCEPTION;
PRAGMA EXCEPTION_INIT(TABLE_DOES_NOT_EXIST, -942);
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE TABLE_NAME_TMP';
EXCEPTION
WHEN TABLE_DOES_NOT_EXIST then null;
END;
EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE TABLE_NAME_TMP AS SELECT * FROM TABLE_NAME WHERE ROWNUM = 0';
DELETE FROM TABLE_NAME_TMP;
INSERT INTO TABLE_NAME_TMP VALUES pi_record;
FOR aCol IN (SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE table_name = 'TABLE_NAME' ORDER BY COLUMN_ID) LOOP
EXECUTE IMMEDIATE 'SELECT '||aCol.COLUMN_NAME||' FROM TABLE_NAME_TMP' INTO item;
TEXT := TEXT || CHR(13) || aCol.COLUMN_NAME || ' : ' || item;
END LOOP;
DBMS_OUTPUT.PUT_LINE ( TEXT );
END;
In case table TABLE_NAME has static attributes then you should skip dynamic DROP TABLE ... and CREATE GLOBAL TEMPORARY TABLE ... and create the TEMP table only once.
everyone!
I got a different approach to get the difference between records dynamically:
You just have to create the global variables on the package header as bellow:
v_NAME_OF_TABLE_new NAME_OF_TABLE%rowtype;
v_NAME_OF_TABLE_old NAME_OF_TABLE%rowtype;
then create the function on your pkg body that return a boolean even if a field is different:
function is_different(p_old NAME_OF_TABLE%rowtype, p_new NAME_OF_TABLE%rowtype)
return boolean
is
cursor cols is
select tb.COLUMN_NAME
from all_tab_columns tb
where tb.OWNER = 'DW'
and tb.TABLE_NAME = 'NAME_OF_TABLE'
order by tb.COLUMN_ID;
l_sql varchar2(4000);
l_new varchar2(4000);
l_old varchar2(4000);
begin
pkg_NAME.v_NAME_OF_TABLE_new := p_new;
pkg_NAME.v_NAME_OF_TABLE_old := p_old;
for reg in cols loop
l_sql := '
begin
:x := pkg_NAME.v_NAME_OF_TABLE_new.'||reg.COLUMN_NAME||';'||'
end;';
execute immediate l_sql using out l_new;
l_sql := '
begin
:x := pkg_NAME.v_NAME_OF_TABLE_old.'||reg.COLUMN_NAME||';'||'
end;';
execute immediate l_sql using out l_old;
--- dbms_output.put_line(l_new||' - '||l_old);
if nvl(l_new,'NULO') <> nvl(l_old,'NULO') then
return true;
end if;
end loop;
return false;
end;
Atention: This can turn your process heavier and slower.
That's all!
Hope this can be helpful!
can't make this work... have a multiple tables names which i need to pass into select. Each select will return multiple records. Resultset should be printed to the user on a screen .
SQL> set serveroutput on;
SQL> DECLARE
vs_statement VARCHAR2 (1000);
my_var1 VARCHAR2(100);
my_var2 VARCHAR2(100);
CURSOR c1 IS
SELECT table_name
FROM all_tables
WHERE table_name LIKE Upper('redit_1%');
BEGIN
FOR table_rec IN c1 LOOP
vs_statement :=
'select a.userinfo, a.userstatus into my_var1, my_var12 from '
|| table_rec.table_name
|| ' A, FILES b where A.objectid = B.id order by 1';
EXECUTE IMMEDIATE vs_statement INTO my_var1,
my_var2;
dbms_output.Put_line(my_var1
||' '
|| my_var2);
END LOOP;
END;
/
This line should look like the following (i.e. remove INTO clause):
vs_statement :=
'select a.userinfo, a.userstatus from '
|| table_rec.table_name
|| ' A, FILES b where A.objectid = B.id order by 1';
Also, make sure that this SELECT returns only 1 row, otherwise you'll end up with the TOO-MANY-ROWS error.
Other than that, I guess it should work.
[EDIT, regarding fear of TOO-MANY-ROWS]
Huh, I'd create a whole new PL/SQL BEGIN-END block, containing a loop which would do the job, displaying all rows returned by SELECT statement.
As I don't have your tables, I used HR's. Have a look:
SQL> DECLARE
2 vs_statement VARCHAR2 (1000);
3 my_var1 VARCHAR2(100);
4 my_var2 VARCHAR2(100);
5 CURSOR c1 IS
6 SELECT table_name
7 FROM all_tables
8 WHERE table_name LIKE Upper('%departments%');
9 BEGIN
10 FOR table_rec IN c1 LOOP
11 vs_statement := 'begin for cur_r in ( '
12 || Chr(10)
13 || 'select distinct a.department_id, a.department_name from '
14 || Chr(10)
15 || table_rec.table_name
16 ||
17 ' A, employees b where A.department_id = B.department_id order by 1) loop'
18 || Chr(10)
19 || ' dbms_output.put_line(cur_r.department_name); '
20 || Chr(10)
21 || ' end loop; end;';
22
23 EXECUTE IMMEDIATE vs_statement; -- into my_var1, my_var2;
24 END LOOP;
25 END;
26 /
Administration
Marketing
Purchasing
Human Resources
Shipping
IT
Public Relations
Sales
Executive
Finance
Accounting
PL/SQL procedure successfully completed.
SQL>
"Each select will return multiple records...Resultset should be printed to the user on a screen"
Open a ref cursor for each generated statement. Loop round that and display the values.
DECLARE
rc sys_refcursor;
vs_statement VARCHAR2 (1000);
my_var1 VARCHAR2(100);
my_var2 VARCHAR2(100);
CURSOR c1 IS
SELECT table_name
FROM all_tables
WHERE table_name LIKE Upper('redit_1%');
BEGIN
<< tab_loop >>
FOR table_rec IN c1 LOOP
vs_statement :=
'select a.userinfo, a.userstatus from '
|| table_rec.table_name
|| ' A, FILES b where A.objectid = B.id order by 1';
open rc for vs_statement;
dbms_output.put_line('records for '||table_rec.table_name);
<< rec_loop >>
loop
fetch rc into my_var1,my_var2;
exit when rc%notfound;
dbms_output.Put_line(my_var1
||' '
|| my_var2);
end loop rec_loop;
close rc;
END LOOP tab_loop;
END;
/
I am trying to get all the tables where bank_id is 01.
I have written the following block
DECLARE
cursor cBankId is
select owner||'.'||table_name from all_tab_columns where column_name = 'BANK_ID';
v_table all_tab_columns.table_name%TYPE;
vcount varchar2(50);
BEGIN
open cBankId;
loop
fetch cBankId into v_table;
exit when cBankId%notfound;
execute immediate 'select count(*) from ' || v_table into vcount || ' where bank_id = 01';
IF vcount > 0 THEN
DBMS_OUTPUT.PUT_LINE (v_table);
END IF;
end loop;
close cBankId;
END;
I want to know how to put where clause in the execute immediate statement .
I am getting the error
ORA-06550: line 15, column 67:
PLS-00103: Encountered the symbol "|" when expecting one of the following:
. ( , % ; return returning using
You only need to change the order you're writing the parts of your query.
When using dynamic SQL, you need something like this:
SQL> declare
2 v_table varchar2(30);
3 vCount number;
4 begin
5 v_table := 'dual';
6 execute immediate 'select count(*) from ' || v_table || ' where 1=1' into vcount;
7 --
8 dbms_output.put_line('vCount: ' || vCount);
9 end;
10
11 /
vCount: 1
PL/SQL procedure successfully completed.
That is: execute immediate 'select ... from ... where ...' INTO ...;
You can't dynamically use a variable for the table name, instead use:
DECLARE
cursor cBankId is
select owner||'.'||table_name from all_tab_columns where column_name = 'BANK_ID';
v_table all_tab_columns.table_name%TYPE;
vcount varchar2(50);
v_sql varchar2(1000);
BEGIN
open cBankId;
loop
fetch cBankId into v_table;
exit when cBankId%notfound;
v_sql := 'select count(*) from ' || v_table || ' into vcount where bank_id = 01';
execute immediate v_sql;
IF vcount > 0 THEN
DBMS_OUTPUT.PUT_LINE (v_table);
END IF;
end loop;
close cBankId;
END;
I have this set of code, it didn't return error when I compiled it.
create or replace PROCEDURE PARAM_CHECK
(
PARAM_CODE IN VARCHAR2,
TABLE_NAME IN VARCHAR2,
ISEXIST OUT INTEGER
)
IS
SQLSTMT VARCHAR2(200);
V_COUNT NUMBER;
BEGIN
SQLSTMT := 'SELECT COUNT(*) INTO '|| V_COUNT ||' FROM '|| TABLE_NAME ||' WHERE ID = '''||PARAM_CODE||''' AND ROWNUM = 1';
EXECUTE IMMEDIATE SQLSTMT;
COMMIT;
IF
V_COUNT = 0 THEN
ISEXIST := 0;
ELSE
ISEXIST := 1;
END IF;
END PARAM_CHECK;
But when after I try to run the procedure, it return this error:
Connecting to the database SAA.
ORA-00936: missing expression
ORA-06512: at "SAA.PARAM_CHECK", line 12
ORA-06512: at line 9
Process exited.
Disconnecting from the database SAA.
I have no idea why it return this error, plus when I compile everything is okay. I did search for the specific ORA error but didn't help much. Please help me. Thanks in advance.
Vcount is uninitialised at the time of the inclusion of its value into the sql statement so it produces a sytactically wrong blank at this position.
If it was feasible at all you'd have to write
SQLSTMT := 'SELECT COUNT(*) INTO V_COUNT FROM '|| TABLE_NAME ||' WHERE ID = '''||PARAM_CODE||''' AND ROWNUM = 1';
but this won't work either iirc since execute immediate opens a new scope.
You have to resort to
SQLSTMT := 'SELECT COUNT(*) FROM '|| TABLE_NAME ||' WHERE ID = '''||PARAM_CODE||''' AND ROWNUM = 1';
EXecute immediate sqlstmt into v_count;