Loop through list of values in Oracle query results - sql

In Oracle, is it possible to loop through values that are returned from a query and use those in a nested query?
For example:
For each A
In Table1
Where B = C
(Select D
From Table2
Where D = A)
Loop
End;

Just use SQL:
SELECT D
FROM Table2
WHERE D IN ( SELECT A FROM Table1 WHERE B = C )
Or you can use collections:
CREATE TYPE int_list IS TABLE OF NUMBER(10,0);
then:
DECLARE
a_array int_list;
d_array int_list;
BEGIN
SELECT a
BULK COLLECT INTO a_array
FROM table1
WHERE b = c;
SELECT d
BULK COLLECT INTO d_array
FROM Table2
WHERE d MEMBER OF a_array;
FOR i IN 1 .. d_array.COUNT LOOP
NULL;
END LOOP;
END;
/

You can use a for loop using PL/SQL to accomplish your goal. The general syntax follows. Replace NULL; with whatever logic you wish in the inner for loop.
BEGIN
FOR r IN (
SELECT a
FROM table1
WHERE b = c)
LOOP
FOR s IN (
SELECT d
FROM table2
WHERE d = a)
LOOP
NULL;
END LOOP;
END LOOP;
END;

Related

Emulating SELECT MAX FROM TABLE with a cursor without using MAX function and ORDER BY

i need to emulate this without using MAX function and ORDER BY just using a cursor. I think i have to use FETCH WHILE and FETCH or FOR but didn't have success. I'll aprecciatte some help!
SELECT MAX(field) FROM table;
The fields are just 4 random letters (A,B,C,D)
Table fields are:
A |
B |
C |
D |
If you are looking to loop over your value set, you would just keep a running maximum value. The following pattern can be adapted for your purposes:
DECLARE
l_cMax my_table.field%TYPE;
l_bFirstPass BOOLEAN := TRUE;
CURSOR c_fieldValues IS
SELECT m.field
FROM my_table m;
BEGIN
FOR rec IN c_fieldValues LOOP
IF l_bFirstPass THEN
l_cMax := rec.field;
l_bFirstPass := FALSE;
ELSIF rec.field > l_cMax THEN
l_cMax := rec.field;
END IF;
END LOOP;
dbms_output.put_line('Maximum Value is: '||l_cMax);
END;
/
You don't really need PL/SQL (unless you're practicing how to use it). SQL can be enough. Have a look at some examples:
Sample data:
SQL> select * From test;
C
-
A
B
C
D
One option:
SQL> select a.col
2 from test a left join test b on a.col < b.col and b.col is not null
3 where b.col is null;
C
-
D
SQL>
And another:
SQL> select a.col
2 from test a
3 where a.col not in (select b.col
4 from test b join test c on b.col < c.col
5 );
C
-
D
SQL>

PL SQL map result set to multiple records

If we have 3 tables for example A, B and C and a cursor like
FOR i IN (
SELECT *
FROM A
JOIN B ON(...)
JOIN C ON(...)
) LOOP
--is there an easy way to map every row to 3 records(A%rowtype, B%rowtype and C%rowtype)?
END LOOP;
Notice that fetching records by ROWID is faster than by index. But you will gain a tiny loss of performance (comparing to simple FOR LOOP) with the following trick, because nothing is free.
DECLARE
l_rec_a table_A%ROWTYPE;
l_rec_b table_B%ROWTYPE;
BEGIN
FOR i IN (SELECT a.ROWID first
, b.ROWID second
FROM table_A AS a
JOIN table_B AS b on a.id = b.id)
LOOP
SELECT * INTO l_rec_a FROM table_A WHERE ROWID = i.first;
SELECT * INTO l_rec_b FROM table_B WHERE ROWID = i.second;
/* do something */
END LOOP;
END;

Merge statement but update only if exactly *one* row matches

I'm running a merge statement but I want the update to happen only if there is exactly one row matching.
merge into mergetest using dual on (a = 1)
when matched then update set b = b+1;
I know I can include a where clause at the end, but I have no idea what to do. Group functions are not allowed, so count(*) = 1 doesn't work.
Does this work?
merge into mergetest
using (select a, count(*) as cnt
from mergetest
where a = 1
group by a
) mt
on mergetest.a = mt.a and cnt = 1
when matched then
update set b = b + 1;
In PL/SQL this could be solved in the following way:
select count(*) into l_count from mergetest where a = 1;
case l_count
when 0 then
insert into mergetest (a,b) values (1,1);
when 1 then
update mergetest set b = b + 1 where a = 1;
else
NULL;
end case;

Fetching data from tables whose names are stored in a table?

I have a table lets say table_names which contains some table names ( table1 , table2 ,table3 etc ). Now I want to fetch the data from table1 , table2 , table3 etc by going through the table table_names.
I am unable to achieve this task. Could anyone please help me out of this situation ?
I am using Oracle DB.
Thank you in advance.
If from TableA you have to get value from Single table then use this,
SELECT * FROM (SELECT table_name FROM tableA WHERE id=3)AS b
Otherwise from getting from multiple table, I prefer below PHP-
while($a=mysql_fetch_assoc(mysql_query((SELECT table_name FROM tableA)))
{ $tablename=$a[table_name];
while($b=mysql_fetch_assoc(mysql_query((SELECT * FROM $tablename))))
{
echo $b[column_name];
}
}
try this query:
DECLARE
str VARCHAR2(30);
num number(5);
BEGIN
SELECT count(*) into num FROM table_names;
FOR i in 1..num loop
SELECT name into str FROM
(SELECT ROWNUM r,t.name FROM table_names t) where r = i;
EXECUTE IMMEDIATE 'SELECT * FROM '||str;
str:='';
end loop;
END;
Select t1.,t2.,t3.* from table1 t1,table2 t2, table3 t3 where (select name from tables)

Deleting rows from multiple related tables

There are three tables related one-to-one on the identifier. I need to delete all the records from the three tables that match the criteria A.ID = B.ID = C.ID
Now I do it in the following way:
DECLARE
CURSOR CUR IS
SELECT C.ID FROM A
INNER JOIN B ON A."ID" = B."ID"
INNER JOIN C ON B."ID" = C."ID"
WHERE A.STATUS = 'ERROR';
IDX NUMBER;
BEGIN
FOR REC IN CUR LOOP
IDX := REC.ID;
DELETE FROM C WHERE C."ID" = IDX;
DELETE FROM B WHERE B."ID" = IDX;
DELETE FROM A WHERE BP."ID" = IDX;
END LOOP;
COMMIT;
END;
A lot of data and this way for very long runs. Is there any way to delete faster?
You could create a PL/SQL type to store the IDs.
CREATE TYPE t_ids AS TABLE OF NUMBER;
Delete all records from table a that match the criterias, and return the IDs into a variable of that type. Then delete all records from b and c with these IDs.
DECLARE
ids_to_delete t_ids;
BEGIN
DELETE FROM a
WHERE a.status = 'ERROR'
AND EXISTS ( SELECT 1 FROM b WHERE b.id = a.id )
AND EXISTS ( SELECT 1 FROM c WHERE c.id = a.id )
RETURNING a.id
BULK COLLECT INTO ids_to_delete;
DELETE FROM b
WHERE id IN ( SELECT COLUMN_VALUE FROM TABLE( ids_to_delete ) );
DELETE FROM c
WHERE id IN ( SELECT COLUMN_VALUE FROM TABLE( ids_to_delete ) );
END;
This should perform a lot better, since it requires no loop and does everything in three SQL statements, instead of three statements per ID.