This piece of code does not work but I dont know why :
set serveroutput on buffer 2560000
declare
requete varchar2(4000);
name_firm varchar2(35);
curseur_ref number;
response number;
begin
curseur_ref := dbms_sql.open_cursor;
requete := 'SELECT trim(myTable.myColumn) from myTable';
dbms_sql.parse(curseur_ref, requete, dbms_sql.native);
dbms_sql.define_column(curseur_ref, 1, name_firm, 35);
response := dbms_sql.execute(curseur_ref);
dbms_sql.column_value(curseur_ref, 1, name_firm);
dbms_output.put_line('NB enregs : ' || dbms_sql.fetch_rows(curseur_ref));
dbms_output.put_line('name_firm : ' || name_firm);
dbms_sql.close_cursor(curseur_ref);
end;
There is no plsql error
The result is :
NB enregs : 1 (it found one... which is correct)
name_firm : 'there is nothing but a name exists'
The request is good as I can send it alone and have the name of the firm
What is going on?
Thank you for answering
Note : the thing is that I saw this code work once....
You have your column_value call in the wrong place; it needs to be after the fetch:
...
response := dbms_sql.execute(curseur_ref);
-- dbms_sql.column_value(curseur_ref, 1, name_firm);
dbms_output.put_line('NB enregs : ' || dbms_sql.fetch_rows(curseur_ref));
dbms_sql.column_value(curseur_ref, 1, name_firm);
dbms_output.put_line('name_firm : ' || name_firm);
...
Which gives:
NB enregs : 1
name_firm : some val
From the documentation:
Call COLUMN_VALUE Procedure or COLUMN_VALUE_LONG Procedure to determine the value of a column retrieved by the FETCH_ROWS Function
for your query
It's also shown in their third example.
The only way i can see you'd have seen it working in the past is if it used to be in a loop fetching multiple rows, in which case you'd see a null value for the first one and the previous row's value for the rest.
Of course, there's no need to use dynamic SQL for this, but I assume this is a stripped down example to demonstrate the issue.
Related
let's see if somebody can help me, I need to delete rows from different tables and I did think to do it using an array so i wrote this :
DECLARE
TYPE mytype_a IS TABLE OF VARCHAR2(32) INDEX BY BINARY_INTEGER;
mytype mytype_a;
BEGIN
mytype(mytype.count + 1) := 'MYTABLE';
FOR i IN 1 .. mytype.count
LOOP
DELETE mytype(i) WHERE valid = 'N';
END LOOP;
END;
Trying to run this piece of code using sqldeveloper I get the ORA-00933 command not properly ended, if I put directly the table name it works, what am I doing wrong?
Thank you.
Thank you very much guys, it works perfectly.
This is not the correct approach. You have to use Dynamic SQL for this -
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar(500) := NULL;
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid =''N''';
EXECUTE IMMEDIATE stmt;
end loop;
END;
You would need to use dynamic SQL, concatenating the table name from the collection into the statement, inside your loop:
execute immediate 'DELETE FROM ' || mytype(i) || ' where valid = ''N''';
Or you can put the statement into a variable so you can display it for debugging purposes, and then execute that, optionally with a bind variable for the valid value:
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
... which I've made also display how many rows were deleted from each table. Note though that you shoudln't rely on the caller being able to see anything printed with dbms_output - it's up to the client whether it shows it.
The whole anonymous block would then be:
DECLARE
type mytype_a is table of varchar2(32) index by binary_integer;
mytype mytype_a;
stmt varchar2(4000);
BEGIN
mytype (mytype.count + 1) := 'MYTABLE';
for i in 1..mytype.count loop
stmt := 'DELETE FROM ' || mytype(i) || ' where valid = :valid';
dbms_output.put_line(stmt);
execute immediate stmt using 'N';
dbms_output.put_line('Deleted ' || sql%rowcount || ' row(s)');
end loop;
END;
/
You could use a built-in collection type to simplify it even further.
db<>fiddle showing some options.
Hopefully this doesn't apply, but if you might have any tables with quoted identifiers then you would need to add quotes in the dynamic statement, e.g.:
stmt := 'DELETE FROM "' || mytype(i) || '" where valid = :valid';
... and make sure the table name values in your collection exactly match the names as they appear in the data dictionary (user_tables.table_name).
I have a table that contains queries, for example:
select text from queries;
TEXT
1 select item from items where item_no between :low_item_no and :high_item_no and description <> :irellevant
The queries already contains the place holders for the bind variables.
The values themselves exists in variables table:
select * from vars;
ID NAME VALUE
1 1 low_item_no 100
2 2 high_item_no 300
3 3 irellevant_desc old
I have a package that takes the query and execute it with
execute immediate statement
but how do I bind those variables?
I don't know how much variables I have in such query, it's not static.
I wish to have a way to do something like that:
Execute immedaite my_query_str using v_array_of_vars;
Until now I don't know of a way to do something like that, only with list of variables for example:
Execute immedaite my_query_str using v_1, v_2, v_3;
Thanks!
I don't think you can do this with execute immediate as too much is unknown at compile time, so you'll have to use the dbms_sql package instead.
Here's a quick demo that gets the query and variables based on a common query ID. This assumes that the values in vars.name actually match the bind variable names in queries.text, and I haven't included any checks or error handling for that or other potential issues, or dealt with multiple select-list items or data types - just the basics:
declare
my_query_str queries.text%type;
my_cursor pls_integer;
my_result pls_integer;
my_col_descs dbms_sql.desc_tab2;
my_num_cols pls_integer;
my_item items.item%type;
begin
select text into my_query_str from queries where query_id = 42;
dbms_output.put_line(my_query_str);
-- open cursor
my_cursor := dbms_sql.open_cursor;
-- parse this query
dbms_sql.parse(my_cursor, my_query_str, dbms_sql.native);
-- bind all variables by name; assumes bind variables match vars.name
for r in (select name, value from vars where query_id = 42) loop
dbms_output.put_line('Binding ' || r.name || ' || with <' || r.value ||'>');
dbms_sql.bind_variable(my_cursor, r.name, r.value);
end loop;
my_result := dbms_sql.execute(my_cursor);
dbms_output.put_line('execute got: ' || my_result);
dbms_sql.describe_columns2(my_cursor, my_num_cols, my_col_descs);
dbms_sql.define_column(my_cursor, 1, my_item, 30); -- whatever size matches 'item'
-- fetch and do something with the results
while true loop
my_result := dbms_sql.fetch_rows(my_cursor);
if my_result <= 0 then
exit;
end if;
dbms_sql.column_value(my_cursor, 1, my_item);
dbms_output.put_line('Got item: ' || my_item);
end loop;
dbms_sql.close_cursor(my_cursor);
end;
/
You don't seem to really need an array; but if you wanted to you could create and populate an associative array as name/value pairs and then use that fir the binds.
This is just a starting point; you may have to deal with an unknown number and/or types of columns being returned, though if that's the case processing them meaningfully will be a challenge. Perhaps you need to return the result of the query as a ref cursor, which is even simpler; demo using the SQL*Plus variable and print commands:
var rc refcursor;
declare
my_query_str queries.text%type;
my_cursor pls_integer;
my_result pls_integer;
begin
select text into my_query_str from queries where query_id = 42;
dbms_output.put_line(my_query_str);
-- open cursor
my_cursor := dbms_sql.open_cursor;
-- parse this query
dbms_sql.parse(my_cursor, my_query_str, dbms_sql.native);
-- bind all variables by name; assumes bind variables match vars.name
for r in (select name, value from vars where query_id = 42) loop
dbms_output.put_line('Binding ' || r.name || ' || with <' || r.value ||'>');
dbms_sql.bind_variable(my_cursor, r.name, r.value);
end loop;
my_result := dbms_sql.execute(my_cursor);
dbms_output.put_line('execute got: ' || my_result);
:rc := dbms_sql.to_refcursor(my_cursor);
end;
/
print rc
Notice you don't close the cursor inside the PL/SQL block in this scenario.
You could also convert to a ref cursor and then fetch from that within your procedure - there's a bulk-collect example in the docs - but again you'd need to know the number and types of the select-list items to do that.
when I try the following code, I get a procedure completed with 0 compilation errors.. message instead of procedure successfully completed message.
what's wrong with this? and help me in correcting this error
CREATE OR REPLACE PROCEDURE omar_manager_report1 (
pi_co_id IN VARCHAR2, -- Company ID
pi_cntr_nbr IN NUMBER DEFAULT 0,
-- if Contract number is passed then Case 1: will be executed
pi_overdue_days IN NUMBER DEFAULT 0,
--No of days related to Over due of application passed.
po_var_ref OUT sys_refcursor
)
IS
lv_query VARCHAR2(400) ;
lv_co_id VARCHAR2 (200);
BEGIN
lv_co_id := REPLACE (pi_co_id, ',', ''',''');
--Default option for all group office
lv_query :=
'select distinct gue.co_id,
(SELECT event_descp FROM get_event WHERE co_id = gue.co_id AND event_cd = gue.event_cd) AS event_desc
FROM get_uwtg_event gue';
DBMS_OUTPUT.put_line ('lv_query');
OPEN po_var_ref FOR lv_query;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (SQLCODE);
END;
/
Executing a CREATE OR REPLACE PROCEDURE... statement compiles the procedure, but it does not call the procedure. You'll need to write a PL/SQL block similar to the following to invoke your procedure:
DECLARE
csrOut SYS_REFCURSOR;
BEGIN
omar_manager_report1(pi_co_id => '123456',
pi_cntr_nbr => 0,
pi_overdue_days => 0,
po_var_ref => csrOut);
-- Add code here to fetch from csrOut and use the results appropriately
-- Close the cursor opened by omar_manager_report1
CLOSE csrOut;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Exception: ' || SQLCODE || ' : ' || SQLERRM);
END;
Best of luck.
I've declared the below cursor and variables:
l_level VARCHAR2(100);
l_level_value VARCHAR2(100);
l_select_clause CLOB;
CURSOR l_data IS
SELECT LEVEL1, LEVEL2, LEVEL3
FROM LEVELS;
Then I loop through the cursor:
FOR c1line IN l_data
LOOP
CASE WHEN c1line.LEVEL1 IS NULL THEN l_level := 'c1line.LEVEL2'
WHEN c1line.LEVEL2 IS NULL THEN l_level := 'c1line.LEVEL3'
WHEN c1line.LEVEL3 IS NULL THEN l_level := 'c1line.LEVEL4'
ELSE l_level := NULL
END CASE;
END LOOP;
l_select_clause := 'SELECT ' || l_level || ' INTO l_level_value FROM dual;';
EXECUTE IMMEDIATE l_select_clause;
And then I have some other statements that execute depending on what is selected into the variable l_level_value
My problem is that when execute my procedure I get the following error:
ORA-00904: "C1LINE"."LEVEL2": invalid identifier
ORA-06512: at "MY_PROCEDURE", line 110
ORA-06512: at line 2
Does anyone know what I have done wrong?
Thanks
About the Actual Error c1line.LEVEL1 , you open the cursor dynamically? it seems valid in thee code you have shown
Then..
EXECUTE IMMEDIATE Accepts bind variable, whereas INTO should be after Execution only. To mention, Semicolon (;) is not needed in the Query String
l_select_clause := 'SELECT ' || l_level || ' FROM dual';
EXECUTE IMMEDIATE l_select_clause INTO l_level_value;
My Question is, do you really need dynamic script? I mean same operation can be performed as
FOR c1line IN l_data
LOOP
CASE WHEN c1line.LEVEL1 IS NULL THEN l_level_value := c1line.LEVEL2
WHEN c1line.LEVEL2 IS NULL THEN l_level_value := c1line.LEVEL3
WHEN c1line.LEVEL3 IS NULL THEN l_level_value := c1line.LEVEL4
ELSE l_level_value := NULL
END CASE;
END LOOP;
Why taking two steps? First assign the value to l_level and then assign same value to l_level_value? That too using dynamic scripts?
Always remember, dynamic script should be the last and last options. You should avoid it.
code 1:
OPEN P_CURSOR FOR V_STR_SQL2
USING P_USER_ID, P_USER_ID, V_NODEID, V_PROCID, V_ADDRESSE;
output : 0,0,0,0
when i change this code to :
V_STR_SQL2 := replace(V_STR_SQL2,':P_USER_ID',P_USER_ID);
V_STR_SQL2 := replace(V_STR_SQL2,':V_NODEID',V_NODEID);
V_STR_SQL2 := replace(V_STR_SQL2,':V_PROCID',V_PROCID);
V_STR_SQL2 := replace(V_STR_SQL2,':V_ADDRESSE',V_ADDRESSE);
OPEN P_CURSOR FOR V_STR_SQL2;
output : 1,0,0,0
second output is correct. what could be the reason? any idea?
datatype of bind variables are as below.
userid varchar2, input
nodeid,procid, number(10)
address varchar2(250);
-- Edited
When I debug my stored proc, this is the value of v_str_sql2 before executing open cursor. When I run this query by giving its vlaue in sqldeveloper it gives me correct result. but wrong result with open-for-using.
SELECT COUNT(DECODE(ZC.STATUS, NULL, 1)) "NEW",
COUNT(DECODE(ZC.STATUS, 'KEEP', 1)) "KEEP",
COUNT(DECODE(ZC.STATUS, 'LOCK', 1)) "LOCK",
COUNT(DECODE(ZC.STATUS,
'KEEP',
DECODE(UPPER(ZC.STATUS_BY), UPPER(:P_USER_ID), 1))) "PKEEP",
COUNT(DECODE(ZC.STATUS,
'LOCK',
DECODE(UPPER(ZC.STATUS_BY), UPPER(:P_USER_ID), 1))) "PLOCK"
FROM tab1 PARTITION(DCL_OTHERS) DCL,
tab2 ZC,
tab3 TC
WHERE DCL.NODE_ID = TC.NODE_ID
AND DCL.PROC_ID = TC.PROC_ID
AND DCL.CASE_REF_NO = TC.CLAIM_REF_NO
AND DCL.NODE_ID = ZC.NODE_ID(+)
AND DCL.PROC_ID = ZC.PROC_ID(+)
AND DCL.CASE_NAME = ZC.CASENUM(+)
AND DCL.USER_NAME = ZC.USERNAME(+)
AND DCL.NODE_ID = :V_NODEID
AND (DCL.PROC_ID = :V_PROCID)
AND (1 = 1)
AND DCL.USER_NAME = :V_ADDRESSE
Edit 2
I am using this syntax. Is that something wrong with this syntax?
CREATE OR REPLACE PROCEDURE USP_HTH_QUEUEPAGE(P_QUEUENAME VARCHAR2,
P_CURSOR OUT SYS_REFCURSOR,
P_REC_CNT OUT NUMBER) IS
BEGIN
-- CODE TO GENERATE DYNAMIC SQL
OPEN P_CURSOR FOR V_STR_SQL1
USING V_ADDRESSE, V_NODEID, V_PROCID, V_STATUS, V_USER_ID, P_EP, P_SP;
/*END IF;*/
EXECUTE IMMEDIATE V_STR_SQL2
INTO P_REC_CNT
USING V_NODEID, V_PROCID, V_USER_ID, V_STATUS, V_ADDRESSE;
END;
Kindly try the below
EXECUTE IMMEDIATE V_STR_SQL2
INTO P_REC_CNT
USING V_USER_ID,V_USER_ID,V_NODEID,V_PROCID, V_ADDRESSE;
The below link would give you more idea why
Dynamic SQL Statement
What concerns me is this line here:
V_STR_SQL2 := replace(V_STR_SQL2,':V_ADDRESSE',V_ADDRESSE);
This puts the contents of the VARCHAR2 variable V_ADDRESSE directly into the query, without any quoting.
Given the value of V_STR_SQL2 you obtained during debugging, the value of V_ADDRESSE must be something that is a valid SQL expression. If its value was something like 1 High Street, you would end up with V_STR_SQL2 containing something like
AND DCL_USER_NAME = 1 High Street
which isn't valid SQL. You'd get an error attempting to execute this.
So, what could V_ADDRESSE contain? Two possibilities suggest themselves to me:
The value of your variable V_ADDRESSE contains a ' at either end, such as '1 High Street'.
The value of your variable V_ADDRESSE contains a string such as 00012493 and you want this to match a value 12493.
What exactly does V_ADDRESSE contain?