Hi there I am trying to build a function to reset a sequence to synch with table ID's which have gotten out of synch with the sequence. Function is as follows:
create or replace
FUNCTION P_FNC_SEQUENCERESET(sourceTable IN VARCHAR2, idField IN VARCHAR2, seqname VARCHAR2) RETURN NUMBER
IS
ln NUMBER;
ib NUMBER;
maxId NUMBER;
newValue NUMBER;
diffValue NUMBER;
interimValue NUMBER;
sqlStmt VARCHAR2(2000);
BEGIN
-- Get the maximum of the id field
EXECUTE IMMEDIATE 'SELECT MAX(' || idField || ') INTO ' || maxId || ' FROM ' || sourceTable;
...code continues...
My understanding of the EXECUTE IMMEDIATE statement leads me to believe that this should be possible, however when executed I get this error:
ORA-00936: missing expression
ORA-06512: at "PSALERT_ADMIN.P_FNC_SEQUENCERESET", line 16
ORA-06512: at line 11
It would need to look something like this:
EXECUTE IMMEDIATE 'SELECT ' || idField ||' FROM ' || sourceTable into maxid;
the keyword "into" is not part of the string that is dynamically executed, but it is part of the syntax of the "execute immediate" statement
https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/executeimmediate_statement.htm
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
The procedure is for cloning a table. It receives the names of the two tables in parameters, and when called it should clone the table.
CREATE OR REPLACE PROCEDURE CLONE_TABLE(
table_source VARCHAR2,
table_destination VARCHAR2)
is
begin
execute immediate 'create table ' || table_destination || 'as select* from '
|| table_source;
end;
But when i call the procedure, I receive error ORA-00922: Missing or invalid option.
BEGIN
CLONE_TABLE('example','example_new')
END
I don't realise what's the problem and how should I fix it.
Writing dynamic SQL is hard, because what should be compilation errors become runtime errors. It is therefore crucial to develop a cool eye when looking at your own code. You have to be the compiler.
It helps to assemble the statement as a variable first. That way you can display the statement in the case of error, which makes debugging easier.
CREATE OR REPLACE PROCEDURE CLONE_TABLE(
table_source VARCHAR2,
table_destination VARCHAR2)
is
stmt varchar2(32767);
begin
stmt := 'create table ' || table_destination || 'as select* from '
|| table_source;
execute immediate stmt;
exception
when others then
dbms_output.put_line(stmt);
raise;
end;
If you had done that if would have been obvious that you were missing a space in front of the as. So your executed statement was this:
create table example_newas select* from example
The space between select and * is optional, but code looks better with one.
There are only 2 problem and couple of suggestions for your code.
Missing space before as select...
Calling of procedure, use exec or call statements.
Suggestions:
Make sure to do proper error handling (so if a table doesn't exists, or if a table destination already exists, you should get proper return message.
Also separate each keyword with space as other answers suggested, like between select and *. But not doing it will not give you error.
CREATE OR REPLACE PROCEDURE CLONE_TABLE(
table_source VARCHAR2,
table_destination VARCHAR2)
is
begin
execute immediate 'create table ' || table_destination || ' as select* from '
|| table_source;
end;
EXEC CLONE_TABLE('example','example_new')
Use this:
CREATE OR REPLACE PROCEDURE CLONE_TABLE (table_source VARCHAR2,
table_destination VARCHAR2)
IS
v_sql varchar2(1000);
BEGIN
v_sql:= 'create table '
|| table_destination
|| ' as select * from '
|| table_source;
dbms_output.put_line(v_sql);
EXECUTE IMMEDIATE v_sql;
END;
The best way to handle issue like you got is to display first what you are trying to execute immediate. You get to know your fault.
There is should be one space before as select.. and in between select and * near select*....
SQL> CREATE OR REPLACE PROCEDURE CLONE_TABLE(
table_source VARCHAR2,
table_destination VARCHAR2)
is
begin
execute immediate 'create table ' || table_destination || ' as select * from ' || table_source;
end;
/ 2 3 4 5 6 7 8
Procedure created.
SQL> create table example(id number);
Table created.
SQL> exec CLONE_TABLE('example','example_new');
PL/SQL procedure successfully completed.
Following is an oracle procedure
create or replace
PROCEDURE INSERT_COMMON(
ENTITY_NAME IN VARCHAR2
, INSERT_QUERY IN varchar2
)
AS
NEW_ID NUMBER;
BEGIN
-- execute insert
DBMS_OUTPUT.PUT_LINE('INSERT_QUERY: ' || INSERT_QUERY);
-- execute IMMEDIATE INSERT_QUERY returning ID into NEW_ID;
-- above gives me a syntax error so using as below
execute IMMEDIATE INSERT_QUERY || ' returning ID into NEW_ID';
DBMS_OUTPUT.PUT_LINE('NEW_ID: ' || NEW_ID);
END INSERT_COMMON;
and Pl/SQL I am using
DECLARE
ENTITY_NAME VARCHAR2(200);
INSERT_QUERY VARCHAR2(200);
BEGIN
ENTITY_NAME := 'company';
INSERT_QUERY := 'INSERT INTO COMPANY (NAME) VALUES (''A Company 2'')';
INSERT_COMMON(ENTITY_NAME,INSERT_QUERY);
END;
This gives me following error
Error report:
ORA-00905: missing keyword
ORA-06512: at "SYSTEM.INSERT_COMMON", line 20
ORA-06512: at line 8
00905. 00000 - "missing keyword"
However, I have tested and following works fine
DECLARE
NEW_ID NUMBER;
BEGIN
INSERT INTO COMPANY (NAME) VALUES ('A Company 2') returning ID into NEW_ID;
DBMS_OUTPUT.PUT_LINE('NEW_ID: ' || NEW_ID);
END;
You need to specify a bind variable you're returning into in the dynamic sql statement, but you also then need to add the returning into <variable> clause to the execute immediate statement.
I believe (untested, since you didn't provide the statements to set up your table and associated triggers) that the following should sort your issue:
create or replace procedure insert_common (entity_name in varchar2,
insert_query in varchar2)
as
new_id number;
begin
-- execute insert
dbms_output.put_line ('INSERT_QUERY: ' || insert_query);
-- above gives me a syntax error so using as below
execute immediate insert_query || ' returning ID into :NEW_ID' returning into new_id;
dbms_output.put_line ('NEW_ID: ' || new_id);
end insert_common;
/
I'm trying to create a generic procedure to synchronize sequences.
I want to call the procedure and pass name of table, column and sequence but my procedure won't run due to an error.
Procedure:
CREATE OR REPLACE PROCEDURE INCREMENT_SEQ(table_name in varchar2 , id_column in varchar2 , sequence_name in varchar2)
AS
current_value number;
seq_val number := -1;
begin
EXECUTE IMMEDIATE 'select max(' || table_name || '.' || id_column || ') into current_value from ' || table_name ;
WHILE current_value >= seq_val
LOOP
EXECUTE IMMEDIATE 'select ' || sequence_name || '.nextval into seq_val from dual';
end loop;
end;
when I run the script I'm having the following error:
Error at line 2
ORA-00905: missing keyword
ORA-06512: at "TGC100_DEV.INCREMENT_SEQ", line 6
ORA-06512: at line 1
Script Terminated on line 16.
But I have no idea how to solve. Any advice would be helpfull.
You should put INTO clause outside the query:
CREATE OR REPLACE PROCEDURE INCREMENT_SEQ(table_name in varchar2 , id_column in varchar2 , sequence_name in varchar2)
AS
current_value number;
seq_val number := -1;
begin
EXECUTE IMMEDIATE 'select max(' || table_name || '.' || id_column || ') from ' || table_name into current_value;
WHILE current_value >= seq_val
LOOP
EXECUTE IMMEDIATE 'select ' || sequence_name || '.nextval from dual' into seq_val;
end loop;
end;
EXECUTE IMMEDIATE 'select max(' || table_name || '.' || id_column || ') into current_value from ' || table_name ;
It is syntactically incorrect. The INTO clause should be outside of EXECUTE IMMEDIATE statement.
Something like,
EXECUTE IMMEDIATE 'your SQL statement' INTO variable USING value;
UPDATE It is better to have the dynamic SQL as a variable to avoid confusions with so many single quotes and concatenation in the EXECUTE IMMEDIATE statement itself.
The other answer by Aramilo was posted before my answer, but I got confused to see the INTO clause already outside the statement.
For developers, it is always a good practice to first check the dynamic SQL using DBMS OUTPUT before actually executing it. Thus, it saves a lot of time to debug the whole bunch of PL/SQL code. Once confirmed that the dynamic SQL formed is correct, remove the DBMS_OUTPUT and execute the PL/SQL code.
Alright. I copied this code from class, but I must have been half asleep at the time because it doesn't actually work.
Create or replace procedure display_row is
(p_itemid IN item.itemid%TYPE) is
v_itemid Number;
v_itemdesc varchar2(30);
v_category varchar2(30);
Begin
Select *
Into v_itemid, v_itemdesc, v_category
From item
Where itemid = p_itemid;
Dbms_output.put_line(v_itemid || ' ' || v_itemdesc || ' ' || v_category);
End;
How can I fix this procedure so that it actually works?
Here's the user errors:
LINE POSITION TEXT
2 6 PLS-00103: Encountered the symbol "(" when expecting one of
the following:
begin function package pragma procedure subtype type use
<an identifier> <a double-quoted delimited-identifier> fo
rm
current cursor external language
2 37 PLS-00103: Encountered the symbol "IS" when expecting one of
the following:
return
Remove the is operator from first sentence
Create or replace procedure display_row is
<--Here
Your procedure should look like
Create or replace procedure display_row
(p_itemid IN item.itemid%TYPE) is
v_itemid Number;
v_itemdesc varchar2(30);
v_idemdesc varchar2(30);
Begin
Select *
Into v_itemid, v_itemdesc, v_category
From item
Where itemid = p_itemid;
Dbms_output.put_line(v_itemid || ' ' || v_itemdesc || ' ' || v_category);
End;