Call stored procedure for each Row with/without using a cursor - sql

I want to a run a stored procedure for almost 1000 records (P_SHIPMENT_GID) in one go and below is the pseudo code.
DECLARE
P_SHIPMENT_GID VARCHAR2(200);
BEGIN
P_SHIPMENT_GID := NULL;
ULE_PKG_UNPLANNED_ICT_CALC.UNPLANNED_ICT_CALC(
P_SHIPMENT_GID => P_SHIPMENT_GID
);
END;
How can I achieve this with or without using cursors?

It is not all that clear what you want to do (where are the 1000 records from?) but here is a "pattern" I am pretty sure you can use :
BEGIN
FOR i IN (SELECT table_name, status FROM user_tables) LOOP
dbms_output.put_line('name : ' || i.table_name ||
' status : ' || i.status);
END LOOP;
END;
This creates a loop on an implicit cursor and allows you to use the returned rows/column in a readable way.

You can write this anonymous block for your requirement. Although its not clear from where you are storing your SHIPMENT_GID values which you wanted to pass to your procedure/pkg.
BEGIN
FOR rec IN ( --Assuming your shipmentid are stored in a table
SELECT SHIPMENT_GID
FROM Your_TABLE)
LOOP
ULE_PKG_UNPLANNED_ICT_CALC.UNPLANNED_ICT_CALC (
P_SHIPMENT_GID => rec.SHIPMENT_GID);
END LOOP;
END;

Related

Oracle stored procedure: FOR LOOP over a column from a variable

I want to save a list of column values into the variable input_cols and then loop over the values
CREATE OR REPLACE PROCEDURE map_values (mapping_table VARCHAR2(64)) AS
TYPE col_names IS TABLE OF VARCHAR2(64);
input_cols col_names;
BEGIN
EXECUTE IMMEDIATE
'SELECT COLUMN_NAME FROM SYS.DBA_TAB_COLUMNS
WHERE TABLE_NAME = '' ' || mapping_table || ' '' '
BULK COLLECT INTO (input_cols);
FOR in_col IN input_cols
LOOP
dbms_output.put_line ('test');
END LOOP;
END;
I am getting the error
PLS-00103: Encountered the symbol "LOOP" when expecting one of the following: * & - + / at mod remainder rem .. <an exponent (**)> ||
Although you can construct dynamic queries by concatenating values, it's generally better to use bind variables where possible, for example:
execute immediate
'select column_name from user_tab_columns where table_name = :b1'
bulk collect into input_cols
using p_table;
I recommend getting into the habit of anchoring types in your code to the corresponding database object, when there is one. For example, this:
mapping_table dba_tab_columns.table_name%type
instructs the compiler to look up the type of dba_tab_columns.table_name and use that. However, I would generally avoid the dba_ views in procedures like this and stick to user_ views, e.g. user_tab_columns, to limit them to objects you own. If you must use dba_ views, you should also include the table owner, as there may be more than one table with the same name.
I also prefer to name my parameters in a way that separates them from column names etc. There are various conventions (camelCase, prefixing with i_ for in or p_ for parameter, prefixing with the procedure name e.g. map_values.mapping_table), so pick one you like.
Putting that together, you get something like this:
create or replace procedure map_values
( p_table user_tab_columns.table_name%type )
as
type col_names is table of user_tab_columns.column_name%type;
input_cols col_names;
begin
execute immediate
'select column_name from user_tab_columns where table_name = :b1 order by column_id'
bulk collect into input_cols
using p_table;
for i in 1..input_cols.count loop
dbms_output.put_line(input_cols(i));
end loop;
end map_values;
Or, if you don't specifically need a collection and just want to loop through a result set:
create or replace procedure map_values
( p_table user_tab_columns.column_name%type )
as
columns_cur sys_refcursor;
colname user_tab_columns.column_name%type;
begin
open columns_cur for
'select column_name from user_tab_columns where table_name = :b1 order by column_id'
using p_table;
loop
fetch columns_cur into colname;
exit when columns_cur%notfound;
dbms_output.put_line(colname);
end loop;
close columns_cur;
end;
As Koen pointed out in the comments, though, there is no need for dynamic SQL in this example, so a much simpler version could be just:
create or replace procedure map_values
( p_table user_tab_columns.column_name%type )
as
begin
for r in (
select column_name from user_tab_columns
where table_name = p_table
order by column_id
)
loop
dbms_output.put_line(r.column_name);
end loop;
end map_values;
Word of advice: use a tool like SQL Developer to create your procedures. They show the compilation errors in a much clearer way. If you're new to PL/SQL, start with the very basics (empty procedure), compile, fix error if any and add code. There are 3 blocking issues in your code - debugging that is pretty hard.
I added a comment for each of the errors
create or replace PROCEDURE map_values
(mapping_table VARCHAR /* just define the datatype, not the precision */
)
AS
TYPE col_names IS TABLE OF VARCHAR2(64) INDEX BY BINARY_INTEGER;
input_cols col_names;
BEGIN
EXECUTE IMMEDIATE
'SELECT COLUMN_NAME FROM SYS.DBA_TAB_COLUMNS
WHERE TABLE_NAME = ''' ||mapping_table|| ''' '
BULK COLLECT INTO input_cols; /* no brackets needed */
dbms_output.put_line ('test:');
FOR in_col IN 1 .. input_cols.COUNT /* this is not a implicit cursor but a collection - you need to iterate over it.*/
LOOP
dbms_output.put_line ('test:'||input_cols(in_col));
END LOOP;
END;
/
Please do not use dynamic SQL for this use case, static SQL is sufficient and will be more efficient.
For looping, you have to use indexes and get the element from collection by index. There is no functionality that will enable you to directly assign collection element to a variable (like you are trying).
create or replace procedure map_values(mapping_table sys.dba_tab_columns%table_name)
as
type col_names is table of sys.dba_tab_columns.column_name%type;
input_cols col_names;
begin
select column_name
bulk collect into input_cols
from sys.dba_tab_columns
where table_name = mapping_table;
for i in 1 .. input_cols.count
loop
dbms_output.put_line(input_cols(i));
end loop;
end;

Is there a way to loop through selected columns in plsql

I have a table TestTable with columns of col_test1, col_test2, col_test3 ...
and I want to create a loop that accesses each of these columns individually and find the max value and place it in the variable made in the declare block and simply dbms.out.put it.
Declare
my_array sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('col_test1','col_test2','col_test2');
v_test number(8,0);
Begin
for r in my_array.first..my_array.last
loop
select max(my_array(r)) into v_test from TestTable;
dbms_output.put_line(v_test);
end loop;
End;
/
The output I get is just the string 'col_test1'which should be 50.
This is done through oracle SQL. Is there any way to achieve this?
You could use dynamic SQL for this
Declare
my_array sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('col_test1','col_test2','col_test2');
v_test number(8,0);
Begin
for r in my_array.first..my_array.last
loop
execute immediate 'select max(' || my_array(r) || ') from TestTable'
into v_test;
dbms_output.put_line(v_test);
end loop;
End;
If you're going to resort to dynamic SQL, however, it would generally make more sense to build a single SQL statement that took that max of all three columns in one pass rather than potentially doing three separate table scans on the same table.

Using variables in oracle as table names for insert command oracle

Trying to create a procedure that will either insert or update a certain table that its name is stored in another table with more info.
CREATE OR REPLACE PROCEDURE LIMPAR_TAB_proc IS
--stmt VARCHAR2(1000);
n_tab sii_bck_cfg_tab.nome_tab%type;
prefix sii_bck_cfg_tab.pref_tab_bck%type;
max_reg sii_bck_cfg_tab.max_reg_bck%type;
id_fk sii_bck_cfg_tab.id_bck_cfg_tab%type;
n_tab2 sii_bck_tab.nome_tab%type;
testes VARCHAR2(500);
CURSOR c1 IS
SELECT ID_BCK_CFG_TAB,Nome_tab, pref_tab_bck, max_reg_bck FROM
sii_bck_cfg_tab WHERE desativado_em IS NULL OR desativado_em<=SYSDATE AND
n_dias_reten>0 ORDER BY criado_em;
CURSOR c2 IS
SELECT sii_bck_tab.ID_BCK_CFG_TAB , sii_bck_tab.nome_tab from
sii_bck_tab,sii_bck_cfg_tab WHERE
sii_bck_cfg_tab.id_bck_cfg_tab=sii_bck_tab.id_bck_cfg_tab and dt_fecho is
NULL ;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO id_fk,n_tab,prefix,max_reg;
EXIT WHEN c1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Nome Tabela = ' || id_fk ||' '|| n_tab ||' '|| prefix
||' '|| max_reg);
OPEN c2;
LOOP
FETCH c2 INTO id_fk, n_tab2;
EXIT WHEN c2%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('chave aqui = ' || id_fk || n_tab2);
IF c2%FOUND THEN
testes:= 'INSERT INTO ' || n_tab2 || 'select * from ' || n_tab;
EXECUTE IMMEDIATE testes;
END IF;
END LOOP;
CLOSE c2;
END LOOP;
CLOSE c1;
so i will try to explain my final objective, i want to go through my cursor1 and when i find a table that passes through the verification then i go into my cursor2. During my second loop i will want to verify if there is a table associated with a table on my cursor1 (not implemented ), then if i find one that is not associated i will need to create one with the same fields as the original(which is why im trying to save the table names in a variable). In case it exists and its dt_fim(date end) is null then i will need to insert all the data from the table from cursor1 (n_tab) into the table found on cursor2(n_tab2).
I will try to explain any doubts further, its still confusing to me, just getting started.
Thank you for any advice/help.
This is the right syntax, but not recommended for such a simple operation
testes:= 'INSERT INTO ' || n_tab2 ||' SELECT * FROM ' || n_tab;
EXECUTE IMMEDIATE testes;
Because It is preferable(and safer) to explicitly specify the column names in an insert, for which you need extra blocks if you want to do it dynamically.
INSERT INTO tab2(col1,col2,col3) SELECT col1,col2,col3 FROM tab;
By the way, any reason why you've put table names in variable instead of doing a direct insert?
Try this below block to pass tablename as variables:
declare
table_1 varchar2(10):='N_tab';
table_2 varchar2(10):='N_tab2';
test varchar2(1000);
begin
test:= 'INSERT all into ' || table_2 || ' SELECT * FROM ' ||table_1;
EXECUTE IMMEDIATE test;
dbms_output.put_line (test);
end;

PL/SQL Procedure Use String Select Statement in for loop

I am building a procedure, where I`m first creating a select statement and store it in an VARCAHR variable.
I now want to execute that query and store the whole result set in an variable to loop through it or use directly in a for loop.
I only find examples where the Select is hard written in the for loop definition.
How do i exchange the Select statement with my variable that holds my select statement?
for r IN (SELECT ... FROM ...)
loop
--do sth;
end loop;
how i want to use it :
statement := 'SELECT .... FROM ...';
for r IN (statement) -- HOW TO DO THIS
loop
--do sth;
end loop;
For a dynamic ref cursor, you need to define everything explicitly:
declare
sqlstring long := 'select 123 as id, ''demo'' as somevalue from dual where dummy = :b1';
resultset sys_refcursor;
type demo_rectype is record
( id integer
, somevalue varchar2(30) );
demorec demo_rectype;
begin
open resultset for sqlstring using 'X';
loop
fetch resultset into demorec;
exit when resultset%notfound;
dbms_output.put_line('id=' || demorec.id || ' somevalue=' || demorec.somevalue);
end loop;
close resultset;
end;
You can parse the cursor and figure out the column names and datatypes with DBMS_SQL. Example here: www.williamrobertson.net/documents/refcursor-to-csv.shtml

Looping on values, creating dynamic query and adding to result set

I have the following problem. I am an experienced Java programmer but am a bit of a n00b at SQL and PL/SQL.
I need to do the following.
1 Pass in a few arrays and some other variables into a procedure
2 Loop on the values in the arrays (they all have the same number of items) and dynamically create an SQL statement
3 Run this statement and add it to the result set (which is an OUT parameter of the procedure)
I already have experience of creating an SQL query on the fly, running it and adding the result to a result set (which is a REF CURSOR) but I'm not sure how I'd loop and add the results of each call to the query to the same result set. I'm not even sure if this is possible.
Here's what I have so far (code edited for simplicity). I know it's wrong because I'm just replacing the contents of the RESULT_SET with the most recent query result (and this is being confirmed in the Java which is calling this procedure).
Any and all help would be greatly appreciated.
TYPE REF_CURSOR IS REF CURSOR;
PROCEDURE GET_DATA_FASTER(in_seq_numbers IN seq_numbers_array, in_values IN text_array, in_items IN text_array, list IN VARCHAR2, RESULT_SET OUT REF_CURSOR) AS
query_str VARCHAR2(4000);
seq_number NUMBER;
the_value VARCHAR2(10);
the_item VARCHAR2(10);
BEGIN
FOR i IN 1..in_seq_numbers.COUNT
LOOP
seq_number := in_seq_numbers(i);
the_value := trim(in_values(i));
the_item := trim(in_items(i));
query_str := 'SELECT distinct '||seq_number||' as seq, value, item
FROM my_table ai';
query_str := query_str || '
WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
AND ai.param = ''BOOK''
AND ai.prod in (' || list || ');
OPEN RESULT_SET FOR query_str;
END LOOP;
EXCEPTION WHEN OTHERS THEN
RAISE;
END GET_DATA_FASTER;
A pipelined table function seems a better fit for what you want, especially if all you're doing is retrieving data. See http://www.oracle-base.com/articles/misc/pipelined-table-functions.php
What you do is create a type for your output row. So in your case you would create an object such as
CREATE TYPE get_data_faster_row AS OBJECT(
seq NUMBER(15,2),
value VARCHAR2(10),
item VARCHAR2(10)
);
Then create a table type which is a table made up of your row type above
CREATE TYPE get_data_faster_data IS TABLE OF get_data_faster_row;
Then create your table function that returns the data in a pipelined manner. Pipelined in Oracle is a bit like a yield return in .net (not sure if you're familiar with that). You find all of the rows that you want and "pipe" them out one at a time in a loop. When your function completes the table that's returned consists of all the rows you piped out.
CREATE FUNCTION Get_Data_Faster(params) RETURN get_data_faster_data PIPELINED AS
BEGIN
-- Iterate through your parameters
--Iterate through the results of the select using
-- the current parameters. You'll probably need a
-- cursor for this
PIPE ROW(get_data_faster_row(seq, value, item));
LOOP;
LOOP;
END;
EDIT: Following Alex's comment below, you need something like this. I haven't been able to test this but it should get you started:
CREATE FUNCTION Get_Data_Faster(in_seq_numbers IN seq_numbers_array, in_values IN text_array, in_items IN text_array, list IN VARCHAR2) RETURN get_data_faster_data PIPELINED AS
TYPE r_cursor IS REF CURSOR;
query_results r_cursor;
results_out get_data_faster_row := get_data_faster_row(NULL, NULL, NULL);
query_str VARCHAR2(4000);
seq_number NUMBER;
the_value VARCHAR2(10);
the_item VARCHAR2(10);
BEGIN
FOR i IN 1..in_seq_number.COUNT
LOOP
seq_number := in_seq_numbers(i);
the_value := trim(in_values(i));
the_item := trim(in_items(i));
query_str := 'SELECT distinct '||seq_number||' as seq, value, item
FROM my_table ai';
query_str := query_str || '
WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
AND ai.param = ''BOOK''
AND ai.prod in (' || list || ');
OPEN query_results FOR query_str;
LOOP
FETCH query_results INTO
results_out.seq,
results_out.value,
results_out.item;
EXIT WHEN query_results%NOTFOUND;
PIPE ROW(results_out);
END LOOP;
CLOSE query_results;
END LOOP;
END;
Extra info from Alex's comment below useful for the answer:
you can have multiple loops from different sources, and as long as the
data from each be put into the same object type, you can just keep
pumping them out with pipe row statements anywhere in the function.
The caller sees them as a table with the rows in the order you pipe
them. Rather than call a procedure and get a result set as an output
parameter, you can query as select seq, value, item from
table(package.get_data_faster(a, b, c, d)), and of course you can
still have an order by clause if the order they're piped isn't what
you want.