Dynamic SQL and out field in oracle procedure - oracle

I get error when I use this:
PROCEDURE GET_BY_CRIT(vchFilter varchar2(500),
intCantTotal OUT INT,
curResult OUT sys_refcursor)
IS
BEGIN
OPEN curResult FOR
'SELECT COLUMN1,COLUMN2 FROM SOME_TABLE WHERE '||vchFilter
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM SOME_TABLE WHERE '||vchFilter
INTO intCantTotal
END
Error:
ORA-00936: missed expression
But when I execute each sentence by separate it run correcly.

The error you're getting doesn't seem to make sense. Oracle should be throwing a compilation error because parameters to functions don't have a length. vchFilter should be declared as a VARCHAR2, not a VARCHAR2(500).
Additionally, as Lolo pointed out in the comments, statements in a PL/SQL block need to be terminated with semicolons.
PROCEDURE GET_BY_CRIT(vchFilter varchar2,
intCantTotal OUT integer,
curResult OUT sys_refcursor)
IS
BEGIN
OPEN curResult FOR
'SELECT COLUMN1,COLUMN2 FROM SOME_TABLE WHERE '||vchFilter;
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM SOME_TABLE WHERE '||vchFilter
INTO intCantTotal;
END;
Be aware as well that there is no guarantee that second SQL statement will see the same COUNT that the first SQL statement did unless you can guarantee that SOME_TABLE is not being modified by any other sessions at the same time you're querying it. I'd generally be rather wary of a need to run a query and execute a separate count-- that generally indicates a more basic problem. If you need the COUNT to be consistent with the query you're running, you'd want to add an analytic COUNT to your query and let the caller fetch that column.
PROCEDURE GET_BY_CRIT(vchFilter varchar2,
curResult OUT sys_refcursor)
IS
BEGIN
OPEN curResult FOR
'SELECT COLUMN1,COLUMN2, COUNT(*) OVER () cnt FROM SOME_TABLE WHERE '||vchFilter;
END;

Related

EXECUTE IMMEDIATE using column names from dual

In my package, I have a procedure like this:
PROCEDURE sp_directUpdate(COL C%ROWTYPE) IS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO T1(SELECT COL.F0, COL.F1 FROM DUAL)';
END IF;
END;
for table T1, I need only two columns from dual: COL.F0 & COL.F1.
When I execute this statement, I get "COL"."F1" is an invalid identifier.
In the same procedure, for inserting values into table T2, my statement might look like this:
EXECUTE IMMEDIATE 'INSERT INTO T2(SELECT COL.F0, COL.F1, COL.F4 FROM
DUAL)';
I will run into a similar problem again. Can you suggest me a way to solve this problem without using INTO clause?
Firstly, the INSERT AS SELECT syntax does not have parentheses () around the query.
If you use EXECUTE IMMEDIATE, the statement is a string executed outside the context of the procedure so it cannot refer to the parameters. You would need to supply them as bind variables, e.g.:
PROCEDURE sp_directUpdate(COL C%ROWTYPE) IS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO T1 AS SELECT :1, :2 FROM DUAL'
USING COL.F0, COL.F1;
END;
However, I would question whether you need to use dynamic SQL at all - you can run the insert directly:
PROCEDURE sp_directUpdate(COL C%ROWTYPE) IS
BEGIN
INSERT INTO T1 AS SELECT COL.F0, COL.F1 FROM DUAL;
END;
In addition, in this case you could use a single row insert statement instead of running an "insert as select":
PROCEDURE sp_directUpdate(COL C%ROWTYPE) IS
BEGIN
INSERT INTO T1 VALUES (COL.F0, COL.F1);
END;
P.S. if this is supposed to do an insert, why is the procedure called "directUpdate"? :)

PL/SQL - Cursor. Can't iterate through table (varchar argument)

I created a procedure to calculate the hashcode of a record (complete line of a table) and then update a column with the calculated hashcode number.
Here's my code at this point (which is based on some info I manage to gather from Google):
CREATE OR REPLACE PROCEDURE calcHashCode (inputTableString IN varchar2) IS
c_data varchar2(3000); --QUERY
c_cursor sys_refcursor; --CURSOR
c_record inputTableString%rowtype; -- Problem is here
BEGIN
c_data := 'SELECT * FROM ' || inputTableString;
OPEN c_cursor for c_data;
LOOP
FETCH c_cursor INTO c_record;
EXIT WHEN c_cursor%notfound;
-- will do stuff here with the records
dbms_output.put_line('stuff');
END LOOP;
CLOSE c_cursor;
END;
/
SHOW ERRORS
4/13 PLS-00310: with %ROWTYPE attribute, 'INPUTTABELA' must name a table, cursor or cursor-variable
4/13 PL/SQL: Item ignored
11/25 PLS-00320: the declaration of the type of this expression is incomplete or malformed
11/5 PL/SQL: SQL Statement ignored
So, my idea (for the final stage of the procedure) is to iterate through out the records, build a string with and then calculate the hashcode. After that, I'll run the update instruction.
The thing is at this point using a varchar as an argument and I'm not being able to iterate through the table in order to get my concatenate records.
dynamic cursors are the ugliest...
the problem is with that section:
c_data varchar2(3000); --QUERY
c_cursor sys_refcursor; --CURSOR
c_record inputTableString%rowtype;
i used something like this:
TYPE t_data IS REF CURSOR;
cr_data t_data;
cr_data_rec inputTableString%ROWTYPE; --that table need to be exists in compile time
the rest are good i think
Have you considered pushing the whole declaration and loop into an anonymous block that will then get executed by EXECUTE IMMEDIATE? You can then simplify your looping construct to a simple FOR loop too.
I'm away from my database at the moment, so excuse any syntax glitches, but something like
CREATE OR REPLACE PROCEDURE calcHashCode (inputTableString IN varchar2) IS
c_data varchar2(30000); --QUERY
BEGIN
c_data := '
BEGIN
FOR aRec IN
(SELECT * FROM ' || inputTableString ||' )
LOOP
--do your stuff
END LOOP;
END';
execute immediate c_Data;
END;
/
It may not be pretty, and your "Stuff" may not easily be able to be fit into this construct, but it IS feasible
You can do all this kind of stuff with PL/SQL package DBMS_SQL, however it is definitely not for beginners and you should start with something easier.
Examples for DBMS_SQL

FORALL+ EXECUTE IMMEDIATE + INSERT Into tbl SELECT

I have got stuck in below and getting syntax error - Please help.
Basically I am using a collection to store few department ids and then would like to use these department ids as a filter condition while inserting data into emp table in FORALL statement.
Below is sample code:
while compiling this code i am getting error, my requirement is to use INSERT INTO table select * from table and cannot avoid it so please suggest.
create or replace Procedure abc(dblink VARCHAR2)
CURSOR dept_id is select dept_ids from dept;
TYPE nt_dept_detail IS TABLE OF VARCHAR2(25);
l_dept_array nt_dept_detail;
Begin
OPEN dept_id;
FETCH dept_id BULK COLLECT INTO l_dept_array;
IF l_dept_array.COUNT() > 0 THEN
FORALL i IN 1..l_dept_array.COUNT SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'INSERT INTO stg_emp SELECT
Dept,''DEPT_10'' FROM dept_emp'||dblink||' WHERE
dept_id = '||l_dept_array(i)||'';
COMMIT;
END IF;
CLOSE dept_id;
end abc;
Why are you bothering to use cursors, arrays etc in the first place? Why can't you just do a simple insert as select?
Problems with your procedure as listed above:
You don't declare procedures like Procedure abc () - for a standalone procedure, you would do create or replace procedure abc as, or in a package: procedure abc is
You reference a variable called "dblink" that isn't declared anywhere.
You didn't put end abc; at the end of your procedure (I hope that was just a mis-c&p?)
You're effectively doing a simple insert as select, but you're way over-complicating it, plus you're making your code less performant.
You've not listed the column names that you're trying to insert into; if stg_emp has more than two columns or ends up having columns added, your code is going to fail.
Assuming your dblink name isn't known until runtime, then here's something that would do what you're after:
create Procedure abc (dblink in varchar2)
is
begin
execute immediate 'insert into stg_emp select dept, ''DEPT_10'' from dept_emp#'||dblink||
' where dept_id in (select dept_ids from dept)';
commit;
end abc;
/
If, however, you do know the dblink name, then you'd just get rid of the execute immediate and do:
create Procedure abc (dblink in varchar2)
is
begin
insert into stg_emp -- best to list the column names you're inserting into here
select dept, 'DEPT_10'
from dept_emp#dblink
where dept_id in (select dept_ids from dept);
commit;
end abc;
/
There appears te be a lot wrong with this code.
1) why the execute immediate? Is there any explicit requirement for that? No, than don't use it
2) where is the dblink variable declared?
3) as Boneist already stated, why not a simple subselect in the insert statement?
INSERT INTO stg_emp SELECT
Dept,'DEPT_10' FROM dept_emp#dblink WHERE
dept_id in (select dept_ids from dept );
For one, it would make the code actually readable ;)

Oracle SQL Developer PL/SQL return an array

Devs,
I've searched everywhere I can, but I could not find solution to this simple problem.
Situation:
I need to write a procedure where it takes a column name as the input and return all the distinct values present in that column as output. And then I have to use this procedure in some c# code.
In MS server, it is very easy as it will directly give the set of results unlike in PL/SQL.
Script I could write (which is not giving me the result I need):
CREATE OR REPLACE
PROCEDURE GetCol(PARAM IN STRING, recordset OUT sys_refcursor)
AS
BEGIN
OPEN recordset FOR
SELECT DISTINCT(PARAM)
FROM my_table;
END
;
When I try to check the data in the recordset using this code:
DECLARE
l_cursor SYS_REFCURSOR;
l_sname VARCHAR2(50);
BEGIN
GetCol('col_name',l_cursor);
LOOP
FETCH l_cursor INTO l_sname;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_sname);
END LOOP;
CLOSE
Can someone help me with this code please.
You can also open a ref_cursor for a string value. Please take a look at this:
CREATE OR REPLACE PROCEDURE GetCol(PARAM IN VARCHAR2, recordset OUT sys_refcursor)
AS
QRY varchar2(100);
BEGIN
QRY := 'SELECT DISTINCT '||PARAM||' FROM my_table';
OPEN recordset FOR QRY;
END;
Then:
DECLARE
l_cursor SYS_REFCURSOR;
l_sname VARCHAR2(50);
BEGIN
GetCol('col_name',l_cursor);
LOOP
FETCH l_cursor INTO l_sname;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(l_sname);
END LOOP;
END;
Your problem is caused by ambiguity about what PARAM is in the procedure's SELECT statement:
CREATE OR REPLACE
PROCEDURE GetCol(PARAM IN STRING, recordset OUT sys_refcursor)
AS
BEGIN
OPEN recordset FOR
SELECT DISTINCT(PARAM) -- Ambiguity here
FROM my_table;
END;
Does PARAM refer to the table column or to the first parameter of the procedure? Oracle has assumed the parameter. You can explicitly say which like this:
SELECT DISTINCT(my_table.PARAM)
FROM my_table;
You could if appropriate (it probably isn't here) specify the procedure parameter instead:
SELECT DISTINCT(GetCol.PARAM)
FROM my_table;
Generally this is best avoided by:
always using table aliases in column references select statements, and
having a standard for parameter names that makes them less likely to clash e.g. P_PARAM.

Run sql stored as value in a table in oracle and return recordset in SSRS report

I've a query that creates a SQL Statement as a field. I want to execute this statement and return the recordset in SSRS report.
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID=' ||FILE_ID||';'
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID and prior FIELD_NUMBER=FIELD_NUMBER-1
This generates a SQL stetment - however I want this SQL to be executed.
This is an extension of this question.
I've tried to use execute immediate , cursors, dbms_sql but it does not produce output. Using it on toad. All it says is "PL/SQL procedure successfully completed"
Using the following
Declare
sql_stmt VARCHAR2(3000);
l_cursor SYS_REFCURSOR;
TYPE RefCurTyp IS REF CURSOR;
v_cursor RefCurTyp;
CURSOR c1 is
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID=' ||FILE_ID||';'
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID and prior FIELD_NUMBER=FIELD_NUMBER-1;
BEGIN
open c1;
FETCH C1 into sql_stmt ;
dbms_output.put_line(sql_stmt);
close c1;
EXECUTE IMMEDIATE sql_stmt;
open v_cursor for sql_stmt;
return l_cursor;
close l_cursor ;
END;
An anonymous PL/SQL block cannot return any data to the caller. If you want to return a SYS_REFCURSOR to the calling application, you would need to create a function (or a procedure). For example
CREATE OR REPLACE FUNCTION get_results
RETURN sys_refcursor
IS
l_sql_stmt VARCHAR2(3000);
l_cursor SYS_REFCURSOR;
BEGIN
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID = ' ||FILE_ID||';'
into l_sql_stmt
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER
start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID
and prior FIELD_NUMBER=FIELD_NUMBER-1;
dbms_output.put_line(l_sql_stmt);
open l_cursor for sql_stmt;
return l_cursor;
END;
I am assuming from your code that you expect your SELECT statement to return a single SQL statement-- your code is fetching only one row from a query that potentially returns multiple SQL statements. I'm assuming that you only fetch one because you only expect the SELECT statement to return one row. Otherwise, since your query lacks an ORDER BY, you are executing arbitrarily one of N SQL statements that your code is generating.
If you are regularly going to be calling this method, you would almost certainly want to use bind variables in your dynamic SQL statement for the file_id rather than generating non-sharable SQL statements. I haven't made that change here.
There is another StackOverflow thread on calling a stored function returning a sys_refcursor from SSRS.

Resources