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"? :)
Related
I have a procedure that does a validation and inserts a record in a table. The procedure is breaking right after the INSERT statement when I try the following code:
EXECUTE IMMEDIATE V_SOME_STRNG || ' returning SOME_ID into :NEW_ID' returning into V_TRGT_ID;
I am trying to execute my INSERT statement which is stored in V_SOME_STRNG and assign the new record's ID to V_TRGT_ID. However, I am running into the following error:
ORA-00933: SQL command not properly ended
Any thoughts?
You don't need to repeat the returning into part, you need a using clause for your bind variable:
EXECUTE IMMEDIATE V_SOME_STRNG || ' returning SOME_ID into :NEW_ID' using out V_TRGT_ID;
Demo using a basic trigger to provide the ID:
create table t42 (some_id number, dummy varchar2(1));
create sequence s42 start with 42;
create trigger tr42 before insert on t42 for each row
begin
:new.some_id := s42.nextval;
end;
/
set serveroutput on
declare
v_some_strng varchar2(200) := 'insert into t42 (dummy) values (''X'')';
v_trgt_id number;
begin
EXECUTE IMMEDIATE V_SOME_STRNG || ' returning SOME_ID into :NEW_ID' using out V_TRGT_ID;
dbms_output.put_line('Returned ID: ' || v_trgt_id);
end;
/
which shows:
Returned ID: 42
PL/SQL procedure successfully completed.
You can only use returning into with the insert .. values ... pattern, not with insert ... select ...; so for instance changing the code above to use;
v_some_strng varchar2(200) := 'insert into t42 (dummy) select ''X'' from dual';
will get the error you originally reported:
ORA-00933: SQL command not properly ended
ORA-06512: at line 6
While you don't need to use returning into part, the OP problem most likely results from an error in the not shown content of the V_SOME_STRNG variable. Because you definitely can use returning into with execute immediate. Here is an example strait from the documentation:
sql_stmt := 'UPDATE emp SET sal = 2000 WHERE empno = :1 RETURNING sal INTO :2';
EXECUTE IMMEDIATE sql_stmt USING emp_id RETURNING INTO salary;
I stress the point again: it works. So if you have any troubles here check you dynamically generated SQL statement more thoroughly.
My test_queries table consist of 2 columns:fid and query_text.I want insert new row. And I return fid I inserted because I use it next question. But the code give me error .
select max(a.fid) into max_fid from test_queries a;
execute immediate 'insert into test_queries values (:1,:2) returning fid into :a' using max_fid+1,query_text,c;
I want to create procedure, that will use cursor, which is the same for arbitrary tables. My current one looks like this:
create or replace
PROCEDURE
some_name(
p_talbe_name IN VARCHAR2,
p_chunk_size IN NUMBER,
p_row_limit IN NUMBER
) AS
CURSOR v_cur IS
SELECT common_column,
ora_hash(substr(common_column, 1, 15), p_chunk_size) as chunk_number
-- Here it can find the table!
FROM p_table_name;
TYPE t_sample IS TALBE OF v_cur%rowtype;
v_sample t_sample;
BEGIN
OPEN v_cur;
LOOP FETCH v_cur BULK COLLECT INTO v_sample LIMIT p_row_limit;
FORALL i IN v_sample.first .. v_sample.last
INSERT INTO chunks VALUES v_sample(i);
COMMIT;
EXIT WHEN v_cur%notfound;
END LOOP;
CLOSE v_cur;
END;
The problem is that it cannot find the table named p_table_name which I want to parametrize. The thing is that I need to create chunks based on hashes for common_column which exists in all intended tables. How to deal with that problem? Maybe there is the equivalent oracle code that will do the same thing? Then I need the same efficiency for the query. Thanks!
I would do this as a single insert-as-select statement, complicated only by the fact you're passing in the table_name, so we need to use dynamic sql.
I would do it something like:
CREATE OR REPLACE PROCEDURE some_name(p_table_name IN VARCHAR2,
p_chunk_size IN NUMBER,
p_row_limit IN NUMBER) AS
v_table_name VARCHAR2(32); -- 30 characters for the tablename, 2 for doublequotes in case of case sensitive names, e.g. "table_name"
v_insert_sql CLOB;
BEGIN
-- Sanitise the passed in table_name, to ensure it meets the rules for being an identifier name. This is to avoid SQL injection in the dynamic SQL
-- statement we'll be using later.
v_table_name := DBMS_ASSERT.ENQUOTE_LITERAL(p_table_name);
v_insert_sql := 'insert into chunks (common_column_name, chunk_number)'||CHR(10)|| -- replace the column names with the actual names of your chunks table columns.
'select common_column,'||CHR(10)||
' ora_hash(substr(common_column, 1, 15), :p_chunk_size) AS chunk_number'||CHR(10)||
'from '||v_table_name||CHR(10)||
'where rownum <= :p_row_limit';
-- Used for debug purposes, so you can see the definition of the statement that's going to be run.
-- Remove before putting the code in production / convert to proper logging code:
dbms_output.put_line(v_insert_sql);
-- Now run the statement:
EXECUTE IMMEDIATE v_insert_sql USING p_chunk_size, p_row_limit;
-- I've included the p_row_limit in the above statement, since I'm not sure if your original code loops through all the rows once it processes the
-- first p_row_limit rows. If you need to insert all rows from the p_table_name into the chunks table, remove the predicate from the insert sql and the extra bind variable passed into the execute immediate.
END some_name;
/
By using a single insert-as-select statement, you are using the most efficient way of doing the work. Doing the bulk collect (which you were using) would use up memory (storing the data in the array) and cause extra context switches between the PL/SQL and SQL engines that the insert-as-select statement avoids.
I have a question about "dynamic using clause" in execute immediate statement. I need to set dynamically the "execute immediate statement" and the using clause as well. I don't know the table structure, but I know only the name of the table, and I need to do an operation update on it.
So I wrote a function (through user_tab_columns and user user_constraints tables) to set a variable with the update statement and the bind_variable but now I need to set the using clause with the list of variable.
Example:
CREATE TABLE table1
(
rec1 VARCHAR2(10 BYTE) NULL,
rec2 DATE NULL,
rec3 number(9) not null
);
declare
TYPE cur_type IS REF CURSOR;
cur cur_type;
table_list table1%ROWTYPE;
sqlstring varchar2(400);
begin
OPEN cur FOR sqlstring;
LOOP
FETCH cur INTO table_list;
EXIT WHEN cur%NOTFOUND;
sqlstring:=function1('table1');
-- that returns sqlstring:='update table1 set rec1=:1 , rec2=:2 , rec3=:3 where rec_id=:c4';
execute immediate sqlstring using table_list.rec1, table_list.rec2, table_list.rec3, table_list.rec_id;
END LOOP;
close cur;
end;
I need to implement dynamically the list of variables of the cursor table_list.
"execute immediate sqlstring using table_list.rec1, table_list.rec2, table_list.rec3, table_list.rec_id"
Does anybody know how to solve this problem?
Thanks a lot for your replies.
The problem is that I'm assuming I don't know the table's structure and so the list of variables of the cursor table_list table1%ROWTYPE.
So I can't explicit table_list.rec1, table_list.rec2 ... in the using clause.
If I use only table_list as variable
begin
OPEN cur FOR sqlstring;
LOOP
FETCH cur INTO table_list;
EXIT WHEN cur%NOTFOUND;
sqlstring:=function1('table1');
execute immediate sqlstring using table_list;
END LOOP;
close cur;
I got the error:" 00457 Expressions have to be of SQL types"
http://psoug.org/oraerror/PLS-00457.htm
Error Cause:
An expression of wrong type is in USING or dynamic RETURNING clause. In USING or dynamic RETURNING clause, an expression cannot be of non-SQL types such as BOOLEAN, INDEX TABLE, and record.
I need a way to retrive not only the values but also the list of variables of the cursor table_list first.
But maybe it's impossible and I have to find a work around.
If I will find something interesting I will post.
Thankyou.
Try to replace your execute immediate to full use of dbms_sql.
http://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_sql.htm#i996891
And usefull for you will be bind_array function from this package.
Use dynamic PL/SQL, unless you can re-factor the original statement and just plug the values into it.
declare
v_string constant varchar2(32767) := 'update test1 set a = :1, b = :2';
v_using_string varchar2(32767);
begin
--Create dynamic using string.
--For example, let's say you want to pass in the values "1" for each NUMBER column.
select listagg(1, ',') within group (order by null)
into v_using_string
from user_tab_columns
where table_name = 'TEST1'
and data_type = 'NUMBER';
--Execute the original dynamic SQL, adding the USING string.
execute immediate '
begin
execute immediate '''||v_string||''' using '||v_using_string||';
end;
';
end;
/
You can either use DBMS_SQL package:
open a cursor using dbms_sql.open_cursor
parse the statement using dbms_sql.parse
bind variables in a loop using dbms_sql.bind_variable
execute the statement using dbms_sql.execute
and finally close the cursor using dbms_sql.close_cursor
Or EXECUTE IMMEDIATE of anonymous PL/SQL block, which performs a dynamically created EXECUTE IMMEDIATE (this approach is not suitable for returning data). See Answer of #JonHeller.
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 ;)
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;