Oracle iteration and collections - oracle

How I can iterate over loop and collect information inside variable/collection?
Something like:
cursor cursor_c= select col1 from table1 where condition;
collection l;
foreach row in cursor_c
l.add (select col2 from table2 where col1=row);
end;
printout(l);
I want to run this as script not inside a procedure.
I have 0 experience with PL/SQL so any help will be appreciated!

You can duplicate the logic shown, but except in unusual circumstances I wouldn't recommend doing it that way. Collections are available and useful in PL/SQL, but printing them out is done by looping over the collection - so if all you're doing is collecting something in-memory to print it out, the better choice would be simply to print the items coming from the cursor when the cursor is iterated. In addition, doing a singleton SELECT inside a loop, where the data being selected in the inner SELECT is dependent on the outer SELECT, is equivalent to doing a JOIN - so do the join instead of pinging the database with a bunch of single-row SELECTs. Putting this together I suggest doing something like:
BEGIN
FOR aRow in (SELECT t2.COL2
FROM TABLE1 t1
INNER JOIN TABLE2 t2
ON t2.COL1 = t1.COL1
WHERE t1.WHATEVER = vSOMETHING_ELSE)
LOOP
DBMS_OUTPUT.PUT_LINE(aRow.COL2);
END LOOP;
END;
In PL/SQL the best choice is generally to use a cursor to get the data in the form you want it, rather than collecting data and then iterating over the collection to transform it. Your data resides in the database - learn to work with it there.
Best of luck.

If you simply want a column in a table matching a column from another table, you won't need a loop, simply use JOINS
SELECT t2.col2
FROM table1 t1
JOIN table2 t2 ON t2.col1 = t1.col1
WHERE '<your_where_conditon>'
If you do want to display them using dbms_output, you may simply use an implicit cursor loop ( #Bob Jarvis answer) or BULK COLLECT to load into a collection.
DECLARE
TYPE col2type is TABLE OF table2.col2%TYPE;
col2_t col2type;
BEGIN
SELECT t2.col2 BULK COLLECT INTO col2_t
FROM table1 t1
INNER JOIN table2 t2 ON t2.col1 = t1.col1
WHERE '<your_where_conditon>';
for i in 1..col2_t.count
loop
dbms_output.put_line(col2_t(i).col2);
end loop;
END;
/

Below code snippet will solve your problem.
set serveroutput on;
declare
type ty_tb_name is table of varchar2(20) index by pls_integer;
l_tb_name ty_tb_name;
cursor cur_acc_name is
select account_name from cust_account;
idx number := 1;
begin
for rec in cur_acc_name loop
l_tb_name(idx) := rec.account_name;
idx := idx+1;
exit when cur_acc_name%notfound;
end loop;
DBMS_OUTPUT.put_line('count:'||l_tb_name.count);
for i in l_tb_name.first..l_tb_name.count loop
DBMS_OUTPUT.put_line('name:'||l_tb_name(i));
end loop;
exception
when others then
DBMS_OUTPUT.put_line(SQLERRM);
end;
/

Related

PL/SQL Cursor whose query is based on a variable

I've got a PL/SQL block that's basically
DECLARE
PIDM NUMBER(8);
CLM_TEST_SCORE NUMBER(5);
CURSOR C_STUDENT IS
select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM;
CURSOR C_CLM_SCORES IS
select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST
where SORTEST_PIDM = pidm;
BEGIN
OPEN C_STUDENT;
LOOP
CLM_TEST_SCORE := '';
FETCH c_Student INTO pidm;
EXIT WHEN c_Student%notfound;
OPEN C_CLM_SCORES;
FETCH C_CLM_SCORES INTO CLM_TEST_SCORE;
CLOSE C_CLM_SCORES;
insert into some_table (CLM_TEST_SCORE)
values (CLM_TEST_SCORE);
END LOOP
END
As far as I'm aware, the pidm referred to in C_CLM_SCORES is the PIDM NUMBER(8) declared in line 2. That would mean that the query the cursor refers to mutates every iteration of the LOOP, depending on the current value of pidm. That doesn't jive with my understanding of cursors as a query-in-progress, as the underlying query changes every LOOP. Maybe it's the original author taking advantage of a clever DB algorithm?
This code works. I just have absolutely no idea why. What the heck is going on here?
You have an overly confusing block of code that is a nightmare to debug as you have:
SQL statements that refer to column name and local variables with the same identifier (PIDM and CLM_TEST_SCORE).
Cursors that change every iteration because they contain a bind variable referring to local variables (PIDM).
Highly inefficient use of loops.
If you want to make it clearer, you can rewrite the PL/SQL block so that you do not have duplicate identifiers and use a parameterised cursor:
DECLARE
v_PIDM SOSC.DW_ALL_COLLECTOR.PIDM%TYPE;
v_CLM_TEST_SCORE some_table.CLM_TEST_SCORE%TYPE;
CURSOR C_STUDENT IS
select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM;
CURSOR C_CLM_SCORES(p_pidm NUMBER) IS
select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST
where SORTEST_PIDM = p_pidm;
BEGIN
OPEN C_STUDENT;
LOOP
FETCH c_Student INTO v_pidm;
EXIT WHEN c_Student%notfound;
OPEN C_CLM_SCORES(v_pidm);
FETCH C_CLM_SCORES INTO v_CLM_TEST_SCORE;
CLOSE C_CLM_SCORES;
insert into some_table (CLM_TEST_SCORE)
values (v_CLM_TEST_SCORE);
END LOOP;
END;
/
However, that is still very inefficient as each iteration performs a SELECT and an INSERT and will generate log entries. You can make it much simpler and more efficient to rewrite the whole thing as a single SQL statement:
INSERT INTO some_table (clm_test_score)
SELECT ( select max(to_number(SORTEST_TEST_SCORE))
from SATURN.SORTEST s
where s.SORTEST_PIDM = c.pidm )
FROM SOSC.DW_ALL_COLLECTOR c;
db<>fiddle here
The code in the question is an advertisement for "Why should implicit cursors be used?". If you rewrite your code as below it becomes much easier to understand:
BEGIN
FOR rowStudent IN (select PIDM
from SOSC.DW_ALL_COLLECTOR
order by PIDM)
LOOP
FOR rowScores IN (select max(to_number(SORTEST_TEST_SCORE)) AS CLM_TEST_SCORE
from SATURN.SORTEST
where SORTEST_PIDM = rowStudent.PIDM)
LOOP
insert into some_table (CLM_TEST_SCORE)
values (rowScores.CLM_TEST_SCORE);
END LOOP; -- rowScores
END LOOP; -- rowStudent
END;
This eliminates all of the variables and cursor definitions, and all the code is right in front of you where you can see it at a glance.
If you wanted to tighten it up a bit further you could use a join to get down to just one cursor:
BEGIN
FOR rowStudent_scores IN (SELECT d.PIDM, MAX(TO_NUMBER(s.SORTEST_TEST_SCORE)) AS CLM_TEST_SCORE
FROM SOSC.DW_ALL_COLLECTOR d
INNER JOIN SATURN.SORTEST s
ON s.SORTEST_PIDM = d.PIDM
GROUP BY d.PIDM)
LOOP
insert into some_table (CLM_TEST_SCORE)
values (rowStudent_scores.CLM_TEST_SCORE);
END LOOP; -- rowStudent_scores
END;

Oracle put resultset into variable in FORALL

I have the following plsql block
declare
TYPE t_mds_ids IS TABLE OF mds.id%TYPE;
l_mds_ids t_mds_ids;
l_mds_parents t_mds_parents;
begin
SELECT id BULK COLLECT INTO l_mds_ids FROM mds;
FORALL indx IN l_mds_ids.FIRST .. l_mds_ids.LAST
select l_mds_ids(indx), ch.id_employee_parent
into l_mds_parents
FROM hierarchy_all ch
CONNECT BY ch.id_employee = prior ch.id_employee_parent
START WITH ch.id_employee = l_mds_ids(indx);
EXECUTE IMMEDIATE 'truncate table mds_hierarchy_all';
insert into mds_hierarchy_all
select * from l_mds_parents;
end;
t_mds_parents declared as
create or replace type r_mds_parents as object (
id_mds number(5,0),
id_employee number(5,0)
);
/
create or replace type t_mds_parents as table of r_mds_parents;
/
And I get an exception ORA-00947: not enough values
I really need to put the resultset of multiple rows into variable of TABLE TYPE on each iteration of FORALL loop. I can't use BULK COLLECT into l_mds_parents as it's restricted inside of FORALL.
Is there only solution to use temporary table instead of table variable?
I don't think you can do this with forall. You could use nested loops:
declare
TYPE t_mds_ids IS TABLE OF mds.id%TYPE;
l_mds_ids t_mds_ids;
l_mds_parents t_mds_parents;
begin
SELECT id BULK COLLECT INTO l_mds_ids FROM mds;
l_mds_parents := NEW t_mds_parents();
FOR indx IN l_mds_ids.FIRST .. l_mds_ids.LAST LOOP
FOR rec IN (
select l_mds_ids(indx) as id_employee, ch.id_employee_parent
FROM hierarchy_all ch
CONNECT BY ch.id_employee = prior ch.id_employee_parent
START WITH ch.id_employee = l_mds_ids(indx)
) LOOP
l_mds_parents.extend();
l_mds_parents(l_mds_parents.COUNT)
:= NEW r_mds_parents (rec.id_employee, rec.id_employee_parent);
END LOOP;
END LOOP;
EXECUTE IMMEDIATE 'truncate table mds_hierarchy_all';
insert into mds_hierarchy_all
select * from table(l_mds_parents);
end;
/
But you don't need to use PL/SQL at all; use a single hierarchical query, or probably more simply here, recursive subquery factoring:
insert into mds_hierarchy_all /* (id_mds, id_employee) -- better to list columns */
with rcte (id_mds, id_employee) as (
select m.id, ha.id_employee_parent
from mds m
join hierarchy_all ha on ha.id_employee = m.id
union all
select r.id_mds, ha.id_employee_parent
from rcte r
join hierarchy_all ha on ha.id_employee = r.id_employee
)
select * from rcte;
db<>fiddle with some made-up data.

PL/SQL %rowtype variable declaration represents inner join

In my PL/SQL script, how do I declare JUSTIFIC_REC when it represents a join?
SELECT *
INTO JUSTIFIC_REC
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
All I want is to insert into a TABLE3 a concatenated row:
INSERT INTO MOF_OUTACCDTL_REQ VALUES(
JUSTIFIC_rec.ENTRY_COMMENTS || ' ' || JUSTIFIC_rec.DESCRIPTION );
How should the declaration of JUSTIFIC_REC be like in the beginning of my script?
If it wasn't for the INNER JOIN , I would write something like:
JUSTIFIC_rec TABLE1%ROWTYPE;
If I understood correctly you can try with cursor rowtype like this (not sure if that is what you meant by declaring your variable type for the select with joins):
set serveroutput on;
declare
cursor cur is
SELECT ENTRY_COMMENTS, DESCRIPTION
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
justific_rec cur%ROWTYPE;
begin
open cur;
loop
fetch cur into justific_rec;
exit when cur%notfound;
dbms_output.put_line(justific_rec.entry_comments || ' ' || justific_rec.description);
end loop;
close cur;
end;
Answer to your question is itself in your question. You have to use the %row type
tow type attribute can be any of the below type:
rowtype_attribute :=
{cursor_name | cursor_variable_name | table_name} % ROWTYPE
cursor_name:-
An explicit cursor previously declared within the current scope.
cursor_variable_name:-
A PL/SQL strongly typed cursor variable, previously declared within the current scope.
table_name:-
A database table or view that must be accessible when the declaration is elaborated.
So code will looks like
DECLARE
CURSOR c1 IS
SELECT * FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
justific_rec c1%ROWTYPE;
BEGIN
open c1;
loop
fetch c1 into justific_rec;
exit when c1%notfound;
INSERT INTO MOF_OUTACCDTL_REQ VALUES(
JUSTIFIC_rec.ENTRY_COMMENTS || ' ' || JUSTIFIC_rec.DESCRIPTION );
end loop;
close c1;
END;
/
You need to use BULK COLLECT INTO
SELECT *
BULK COLLECT INTO JUSTIFIC_REC
FROM TABLE1 A
INNER JOIN TABLE2 B
ON A.ID_JUSTIFIC = B.ID_JUSTIFIC ;
The JUSTIFIC_REC is needed to be TABLE type with appropriate columns
If all you're wanting to do is insert into a table based on a select statement, then there's no need for PL/SQL (by which I mean there's no need to open a cursor, fetch a row, process the row and then move on to the next row) - that's row-by-row aka slow-by-slow processing.
Instead, you can do this all in a single insert statement, e.g.:
INSERT INTO mof_outaccdtl_rec (<column being inserted into>) -- please always list the columns being inserted into; this avoids issues with your code when someone adds a column to the table.
SELECT entry_comments || ' ' || description -- please alias your columns! How do we know which table each column came from?
FROM table1 a
inner join table2 b on a.id_justific = b.id_justific;
If you wanted to embed this insert statement in PL/SQL, all you need to do is add a begin/end around it, like so:
begin
INSERT INTO mof_outaccdtl_rec (<column being inserted into>) -- please always list the columns being inserted into; this avoids issues with your code when someone adds a column to the table.
SELECT entry_comments || ' ' || description -- please alias your columns! How do we know which table each column came from?
FROM table1 a
inner join table2 b on a.id_justific = b.id_justific;
end;
/
This is the preferred solution when working with databases - think in sets (where possible) not procedurally (aka row-by-row processing). It is easier to maintain, the code is simpler to read and write, and it'll be more performant since you're not having to switch between PL/SQL and SQL several times with each row.
Context switching is bad for performance - think in terms of a bath full of water - is it quicker to empty the bath with a spoon (row-by-row processing), with a jug (batched rows - by - batched rows) or by pulling the plug (set-based)?

Oracle FOR Loop inside a FOR loop

Here is my code. Please forgive me for not putting variables in declaration section as the editor was giving me tough time in formatting it before I could submit my issue.
I want the result variable (v_Var) to have value printed as
v_ID = :NEW.ID;
v_NAME = :NEW.NAME;
v_ENTITY_ID = :NEW.ENTITY_ID;
BUT, it is getting printed as
v_ID = :NEW.ID;
v_ID = :NEW.ID;
v_NAME = :NEW.NAME;
v_ENTITY_ID = :NEW.ENTITY_ID;
Since the table TEMP_TRG_CONSTRNT has 2 rows, it is working for v_ID also for two times.
I know the issue is with external FOR loop but I am not sure how to handle it.
DECLARE
CURSOR c1 IS
SELECT NAME, OCCUR_COUNT FROM IFMS_SYSTEMCONFIGURATION.TEMP_TRG_CONSTRNT;
BEGIN
v_TableName := 'MyTable';
EXECUTE IMMEDIATE 'TRUNCATE TABLE IFMS_SYSTEMCONFIGURATION.TEMP_TRG_CONSTRNT';
INSERT INTO IFMS_SYSTEMCONFIGURATION.TEMP_TRG_CONSTRNT (NAME, OCCUR_COUNT)
SELECT A.FKN, COUNT(A.FKN) AS OCCUR_COUNT FROM
(
SELECT A.CONSTRAINT_NAME AS FKN FROM ALL_CONSTRAINTS A
INNER JOIN ALL_CONS_COLUMNS B
ON A.CONSTRAINT_NAME = B.CONSTRAINT_NAME
WHERE A.CONSTRAINT_TYPE IN ('P', 'U') AND A.TABLE_NAME = 'MyTable'
)A
GROUP BY A.FKN;
--FOR CONSTR_NAME IN (SELECT NAME FROM IFMS_SYSTEMCONFIGURATION.TEMP_TRG_CONSTRNT)
FOR CONSTR_NAME IN c1
LOOP
--SELECT NAME, OCCUR_COUNT INTO v_Constr_Name, v_Index_Count FROM TEMP_TRG_CONSTRNT WHERE NAME = CONSTR_NAME.NAME;
FOR COL_NAME IN (SELECT COLUMN_NAME FROM ALL_CONS_COLUMNS WHERE CONSTRAINT_NAME = CONSTR_NAME.NAME)
LOOP
v_Var := v_Var || 'v_' || COL_NAME.COLUMN_NAME || ' = :NEW.' || COL_NAME.COLUMN_NAME || ';' || CHR(13);
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_Var);
END LOOP;
END;
There could be a couple of things causing your results:
There are tables in different schemas with the same name (you're missing a join on owner between the two tables)
There could be more than one constraint on the same table.
In addition to those, you've also managed to recreate a Nested Loop join, by doing the nested looping. In general, this is a bad idea - what if a Hash Join was more performant? You will have effectively hobbled Oracle by using your nested cursor for loops. At the very least, you could join your two cursors in a single sql statement before looping through it.
However, it looks like you're trying to generate a list of variables without having to type them. You can do this in a single SQL statement - no need for PL/SQL, like so:
SELECT DISTINCT con.constraint_name,
con.owner,
con.table_name,
col.column_name,
'v_'||col.column_name||' := :NEW.'||col.column_name||';'
FROM all_constraints con
inner JOIN all_cons_columns col ON con.constraint_name = col.constraint_name
AND con.owner = col.owner
AND con.constraint_type IN ('P', 'U')
--AND con.owner = 'SOME_OWNER' -- uncomment out and put in the correct owner name, esp if your table exists in more than one owner.
AND con.table_name = 'YOUR_TABLE'
ORDER BY con.owner, con.table_name;
Note that I've included extra columns, so that you can work out why you're getting the results you're getting, just in case that doesn't match what you're expecting to see. I included the DISTINCT keyword to take care of the case where you have multiple constraints returned for a single owner.table.
If you want to generate a list of variables for multiple tables at once, you might want to use the aggregate listagg function on the above query (meaning you could remove the DISTINCT) with a delimiter of CHR(10).

PL SQL batch processing with insert from select

I have to move the data from table A to table B (they have almost the same fields).
What I have now is a cursor, that iterates over the records that has to be moved, insert one record in the destination table and updates the is_processed field in the source table.
Something like:
BEGIN
FOR i IN (SELECT *
FROM A
WHERE A.IS_PROCESSED = 'N')
LOOP
INSERT INTO B(...) VALUES(i....);
UPDATE A SET IS_PROCESSED = 'Y' WHERE A.ID = i.ID;
COMMIT;
END LOOP;
END;
The questions is, how to do the same using INSERT FROM SELECT(without the loop) and then update IS_PROCESSED of all the moved rows?
There is no BULK COLLECT INTO for INSERT .. SELECT
May be you can try this. I don't think it's better than your LOOP.
DECLARE
TYPE l_src_tp IS TABLE OF t_source%ROWTYPE;
l_src_rows l_src_tp;
BEGIN
SELECT *
BULK COLLECT INTO l_src_rows
FROM t_source;
FORALL c IN l_src_rows.first .. l_src_rows.last
INSERT INTO t_dest (td_id, td_value)
VALUES (l_src_rows(c).ts_id, l_src_rows(c).ts_value);
FORALL c IN l_src_rows.first .. l_src_rows.last
UPDATE t_source
SET ts_is_proccesed = 'Y'
WHERE ts_id = l_src_rows(c).ts_id;
END;
If you reverse the order and first make update and then insert you can use:
DECLARE
ids sys.odcinumberlist;
BEGIN
UPDATE a SET is_processed = 'Y' WHERE is_processed = 'N' RETURNING id BULK COLLECT INTO ids;
INSERT INTO b (id) SELECT column_value id FROM TABLE(ids);
COMMIT;
END;
In the SELECT you can join the ids table and get other data from other tables you want to insert into b.
Hello I should prefer pure SQL rather than PLSQL. I don't know why another update statement is requires for this simpler task. Let me know if this helps.
INSERT INTO <new table>
SELECT col1,col2,
.....,'Y'
FROM <old_table>
WHERE processed_in = 'N';

Resources