Retrieve next value of Oracle sequence via stored procedure - oracle

I have created a SEQUENCE in Oracle11g which is working as expected. The challenge is that I need to retrieve the next value of the sequence via a stored procedure (because the application I'm using can only call a stored procedure not a sql statement).
I have looked all over and the closest I've found is this (here: https://community.oracle.com/thread/2216649):
CREATE OR REPLACE PROCEDURE CSDWH.CSDWH_RETURN_SEQ_NUM
(SEQ_NAME IN VARCHAR2,SEQUENCE_OUT OUT NUMBER)
IS
BEGIN
EXECUTE IMMEDIATE 'SELECT ' || SEQ_NAME || '.NEXTVAL from dual' INTO sequence_out;
END;
The issue with this is the way of execution:
declare
l_val number;
begin
csdwh_return_seq_num( 'FOO_SEQ', l_val );
dbms_output.put_line( l_val );
end;
The application I need to execute the stored procedure only have a connection string alias, a stored procedure name, and a return parameter field to be specified. The way to execute this stored procedure required 6 lines of code.
Is there any way to achieve the above with the EXECUTE command?
i.e. EXECUTE CSDWH.CSDWH_RETURN_SEQ_NUM
that would return the value?

just create a function instead of procedure
CREATE OR REPLACE FUNCTION GET_SEQ(SEQ_NAME IN VAECHAR2) AS
DECLARE
seq_out NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT ' || SEQ_NAME || '.NEXTVAL from dual' INTO seq_out;
RETURN seq_out;
END;
then you can use select get_seq('foo_seq') from dual

Related

SELECTing inside a stored procedure

I expected the code like:
create or replace procedure dmp(t in varchar2)
AS
BEGIN
EXECUTE IMMEDIATE 'SELECT * FROM ' || t;
END;
/
BEGIN
dmp('SOMETABLE');
END;
to be the same as SELECT * FROM SOMETABLE. However, calling the stored procedure does not actually output anything -- for any table, including the obviously non-empty ones... Why is that? How would I write a stored procedure, that would output result(s) of queries inside it?
Assuming that you are using a client like SQL*Plus or one that supports a subset of SQL*Plus commands like SQL Developer, you can do something like this (note that I am ignoring the potential for SQL injection attacks).
variable rc refcursor;
/
create or replace procedure get_cursor( p_tableName in varchar2,
p_rc out sys_refcursor )
as
begin
open p_rc for 'select * from ' || p_tableName;
end;
/
begin
get_cursor( 'dual', :rc );
end;
/
print rc;

Passing table name inside proc to store its row count into a variable

I want to write a simple pl/sql procedure which will take a table name as input and it will store the table row count into a variable . I have written the bellow code :
CREATE OR REPLACE procedure ATT_REP.proc_compare2(table_name IN varchar2)
is
cnt NUMBER(30);
begin
execute immediate 'select count(*) from '||table_name||' into '||cnt ;
dbms_output.put_line(cnt);
end;
/
while executing i am getting 'PLS-00357: Table,View Or Sequence reference 'CES_ODS.ENTITY' not allowed in this context' error.
Please suggest what am i doing wrong . How can i make it working .
Try the following:
CREATE OR REPLACE PROCEDURE ATT_REP.proc_compare2 (table_name IN VARCHAR2)
IS
cnt NUMBER (30);
sql_stmt VARCHAR2 (200);
BEGIN
sql_stmt := 'select count(*) from ' || table_name;
EXECUTE IMMEDIATE sql_stmt INTO cnt;
DBMS_OUTPUT.put_line (cnt);
END;
/

Execute immediate and missing invalid option

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.

How can I make an entire PL/SQL code block dynamic with bind variables?

Background
I'm trying to make a re-usable PL/SQL procedure to move data from one
database to another.
For this purpose, I'm using dynamic SQL.
The procedure executes perfectly if I use a REPLACE with placeholders.
However, for security reasons, I want to use bind variables.
Question
How can I make an entire PL/SQL code block dynamic (with bind
variables)? If I use a REPLACE instead of the bind variables, it works
fine.
How to replicate
To replicate this in your database, create the following procedure as it is:
create or replace procedure move_data(i_schema_name in varchar2, i_table_name in varchar2, i_destination in varchar2) as
l_sql varchar2(32767);
l_cursor_limit pls_integer := 500;
l_values_list varchar2(32767);
begin
select listagg('l_to_be_moved(i).' || column_name, ', ') within group (order by column_id)
into l_values_list
from all_tab_cols
where owner = i_schema_name and
table_name = i_table_name and
virtual_column = 'NO';
l_sql := q'[
declare
l_cur_limit pls_integer := :l_cursor_limit;
cursor c_get_to_be_moved is
select :i_table_name.*, :i_table_name.rowid
from :i_table_name;
type tab_to_be_moved is table of c_get_to_be_moved%rowtype;
l_to_be_moved tab_to_be_moved;
begin
open c_get_to_be_moved;
loop
fetch c_get_to_be_moved
bulk collect into l_to_be_moved limit l_cur_limit;
exit when l_to_be_moved.count = 0;
for i in 1.. l_to_be_moved.count loop
begin
insert into :i_table_name#:i_destination values (:l_values_list);
exception
when others then
dbms_output.put_line(sqlerrm);
l_to_be_moved.delete(i);
end;
end loop;
forall i in 1.. l_to_be_moved.count
delete
from :i_table_name
where rowid = l_to_be_moved(i).rowid;
for i in 1..l_to_be_moved.count loop
if (sql%bulk_rowcount(i) = 0) then
raise_application_error(-20001, 'Could not find ROWID to delete. Rolling back...');
end if;
end loop;
commit;
end loop;
close c_get_to_be_moved;
exception
when others then
rollback;
dbms_output.put_line(sqlerrm);
end;]';
execute immediate l_sql using l_cursor_limit, i_table_name, i_destination, l_values_list;
exception
when others then
rollback;
dbms_output.put_line(sqlerrm);
end;
/
And then you can execute the procedure with the following:
begin
move_data('MySchemaName', 'MyTableName', 'MyDatabaseLinkName');
end;
/
Due to many reasons(inability to generate an appropriate execution plan, security checking, etc.) Oracle does not allow identifiers binding (table names, schema names, column names and so on). So if it's really necessary, the only way is to hard code those identifiers after some sort of validation (to prevent SQL injection).
If I understand well, you could try a trick, by using a dynamic SQL inside a dynamic SQL.
setup:
create table tab100 as select level l from dual connect by level <= 100;
create table tab200 as select level l from dual connect by level <= 200;
create table tabDest as select * from tab100 where 1 = 2;
This will not work:
create or replace procedure testBind (pTableName in varchar2) is
vSQL varchar2(32000);
begin
vSQL := 'insert into tabDest select * from :tableName';
execute immediate vSQL using pTableName;
end;
But this will do the trick:
create or replace procedure testBind2 (pTableName in varchar2) is
vSQL varchar2(32000);
begin
vSQL := q'[declare
vTab varchar2(30) := :tableName;
vSQL2 varchar2(32000) := 'insert into tabDest select * from ' || vTab;
begin
execute immediate vSQL2;
end;
]';
execute immediate vSQL using pTableName;
end;
I think you can do it simpler.
create or replace procedure move_data(i_schema_name in varchar2, i_table_name in varchar2, i_destination in varchar2) as
l_sql varchar2(32767);
begin
select listagg('l_to_be_moved(i).' || column_name, ', ') within group (order by column_id)
into l_values_list
from all_tab_cols
where owner = i_schema_name and
table_name = i_table_name and
virtual_column = 'NO';
l_sql := 'insert into '||i_destination||'.'||i_table_name||' select * from '||i_schema_name||'.'||i_table_name;
execute immediate l_sql;
end;
If you are concerned about SQL-Injection, have a look at package DBMS_ASSERT. This PL/SQL package provides function to validate properties of input values.

Create nested procedure in Oracle 10g

I want to create a nested procedure where in the 1st procedure, I want to create a table dynamically with 2 columns and in the 2nd procedure, I want to insert values into that table.
Below is the code I am trying to use; what am I doing wrong?
CREATE or replace PROCEDURE mytable (tname varchar2)
is
stmt varchar2(1000);
begin
stmt := 'CREATE TABLE '||tname || '(sname varchar2(20) ,sage number (4))';
execute immediate stmt;
end;
create PROCEDURE mytable1 (emp_name varchar2,emp_age number,tname varchar2)
is
stmt1 varchar2(1000);
begin
stmt1 := 'insert into '||tname||' values ('Gaurav' ,27)';
execute immediate stmt1;
end;
There's no need to create a nested procedure here. You can do everything in a single procedure.
Note my use of bind variables in the execute immediate statement
create or replace procedure mytable (
Ptable_name in varchar2
, Pemp_name in varchar2
, Pemp_age in number
) is
begin
execute immediate 'create table ' || Ptable_name
|| ' (sname varchar2(20), sage number (4))';
execute immediate 'insert into ' || Ptable_name
|| ' values (:emp_name, :emp_age)'
using Pemp_name, Pemp_age;
end;
More generally, there's no need to use execute immediate at all; creating tables on the fly is indicative of a poorly designed database. If at all possible do not do this; create the table in advance and have a simple procedure to insert data, should you need it:
create or replace procedure mytable (
, Pemp_name in varchar2
, Pemp_age in number
) is
begin
insert into my_table
values (Pemp_name, Pemp_age);
end;
I would highly recommend reading Oracle's chapter on Guarding Against SQL Injection.
If you really feel like you have to do this as a nested procedure it would look like this; don't forget to call the nested procedure in the main one as the nested procedure isn't visible outside the scope of the first.
create or replace procedure mytable (
Ptable_name in varchar2
, Pemp_name in varchar2
, Pemp_age in number
) is
procedure myvalues (
Pemp_name in varchar2
, Pemp_age in number
) is
begin
execute immediate 'insert into ' || Ptable_name
|| ' values (:emp_name, :emp_age)'
using Pemp_name, Pemp_age;
end;
begin
execute immediate 'create table ' || Ptable_name
|| ' (sname varchar2(20), sage number (4))';
myvalues ( Pemp_name, Pemp_age);
end;
Please see Oracle's documentation on PL/SQL subprograms

Resources