PL/SQL loop through variables - oracle

I have a tool which output is a sql query results. Depending on the user choice query is using subqueries. In order to get all subqueries to my final query I use strings and at the final stage I concat them to one big query - vSQL.
Subqueries are saved in string like vSQL1 .. vSQL14
Since not every subquery is used - user choice - some of them are omitted.
Now it looks like this:
if LENGTH(vSQL1) > 1 then
set_vSQL(vSQL, vSQL1, t);
end if;
..
if LENGTH(vSQL14) > 1 then
set_vSQL(vSQL, vSQL14, t);
end if;
Is it possible to put it into loop so only variable would change?
I tried something like this but this is not working.
for x in 1 .. 14
loop
if LENGTH(vSQL || x) > 1 then
set_vSQL(vSQL, vSQL || x, t);
end if;
end loop

In a meantime I have found a solution.
I have added a new type and a variable:
TYPE vSQLs_table IS TABLE OF VARCHAR2(32000);
vSQLs vSQLs_table;
With this I could input all my variables into one:
vSQLs := vSQLS_table(vSQL1, vSQL2, vSQL3, vSQL4, vSQL5, vSQL6, vSQL7, vSQL8, vSQL9, vSQL10, vSQL11, vSQL12);
With this I could loop through all of them:
for x in 1 .. 12
loop
if LENGTH(vSQLs(x)) > 1 then
set_vSQL(vSQL, vSQLs(x), t);
end if;
end loop;

you can use dynamic SQL.
something like that( the code ist not tested)
declare
l number;
begin
for x in 1 .. 14
loop
execute immediate 'select LENGTH(vSQL'||to_char(x)||') from dual' into l;
if l > 1 then
execute immediate 'set_vSQL(vSQL, vSQL'||to_char(x)||', t)' ;
end if;
end loop
end;

Related

SQL%ROWCOUNT is not providing desired outcome with dynamic SQL Having BULK COLLECT and FORALL in Oracle PL/SQL

SQL%ROWCOUNT is returning the count considered(10) for the run, not the exact number of records updated. Expectation is that SQL%ROWCOUNT should provide the actual number of records updated . Please suggest me how to achieve the task.
Code which triggers dynamic SQL
FORALL indx IN 1 .. l_account_data.COUNT --assume 10 as count
SAVE EXCEPTIONS
EXECUTE IMMEDIATE dynamic_sql_query USING l_account_data (indx);
DBMS_OUTPUT.put_line ('Successful UPDATE of '|| TO_CHAR (SQL%ROWCOUNT) || ' record');
COMMIT;
dynamic_sql_query
BEGIN
SELECT clmn_x, clmn_y
BULK COLLECT INTO l_subscr_data
FROM table_x e, table_y c
WHERE c.ref_id = :account_no AND e.account_no = c.account_no;
FORALL indx IN 1 .. l_subscr_data.COUNT
UPDATE table_z ciem --this update will update multiple records for each account
SET ciem.ext_id = ciem.sub_no || ROWID
WHERE ciem.sub_no = l_subscr_data (indx).clmn_x
AND ciem.subscr_no_resets = l_subscr_data (indx).clmn_y
AND ciem.status IN (1,2);
END;
Your outer execute immediate call isn't aware of what is happening inside the dynamic SQL; it doesn't know what it's doing, or how many rows it may or may not have affected.
To get an accurate count you would need to modify your dynamic statement to add something like:
FOR indx IN 1 .. l_subscr_data.COUNT LOOP
:total_count := :total_count + coalesce(SQL%BULK_ROWCOUNT(indx), 0);
END LOOP;
and change your outer call to (a) pass an extra IN OUT bind variable to track the total count, and (b) use a FOR LOOP rather than FORALL, because that only seems to retain the value after the first dynamic call (not sure if that's documented, or a bug). So something like:
...
l_total_count number := 0;
...
FOR indx IN 1 .. l_account_data.COUNT LOOP
EXECUTE IMMEDIATE dynamic_sql_query
USING l_account_data (indx), in out l_total_count;
END LOOP;
DBMS_OUTPUT.put_line ('Successful UPDATE of '|| TO_CHAR (l_total_count) || ' record');
db<>fiddle demo with made-up data.

ORA-06533: Subscript beyond count ORA-06512 in PL/SQL

i want to have 10 by 10 grid with numbers from 1 to 100
and it give me this error
ORA-06533: Subscript beyond count ORA-06512: at line 25
ORA-06512: at "SYS.DBMS_SQL", line 1721
i don't understand the error and i couldn't solve it
can someone please help me
DECLARE
-- PL SQL code to create and fill a two-dimensional array
-- create VARRAY type of 10 integers
TYPE array_10_int IS VARRAY(10) of PLS_INTEGER;
-- create VARRAY type of array_10_int
TYPE grid_100_int IS VARRAY(10) of array_10_int;
-- declare a variable of the grid_100_int type
grid_var grid_100_int;
-- declare counters
i PLS_INTEGER := 0;
j PLS_INTEGER :=0;
M PLS_INTEGER :=0;
N PLS_INTEGER :=0;
BEGIN
grid_var := grid_100_int();
-- TO DO : use nested loop to fill grid_var with numbers 1- 100
/** YOUR CODE HERE **/
M:=0;
Loop
M:=M+1;
N:=0;
LOOP
J:=j+1;
If grid_var(M)(N)<100 THEN
DBMS_OUTPUT.PUT(' ' || grid_var(M)(N) || ' ');
ELSE
DBMS_OUTPUT.PUT( grid_var(M)(N) || ' ');
END IF;
EXIT WHEN (N =100);
END LOOP;
dbms_output.put_line(' ');
EXIT WHEN (M=10);
END LOOP;
-- Print the grid with nested loop
i:=0;
LOOP --outer loop
i := i+1;
j := 0;
LOOP -- inner loop
j:= j+1;
IF grid_var(i)(j) < 10 THEN
DBMS_OUTPUT.PUT(' ' || grid_var(i)(j) || ' ');
ELSE
DBMS_OUTPUT.PUT( grid_var(i)(j) || ' ');
END IF;
EXIT WHEN (j =10);
END LOOP;
dbms_output.put_line(' ');
EXIT WHEN (i =10);
END LOOP;
END;
A second look.
Creating multiple dimensional arrays (10 x 10) are tricky things. They are actually a collection of a collection. Further, each has the same definition and existence requirements: the element must exist before referenced, either by extend or array initialization. The following initializes each array before referencing it. Also it uses a FOR loop letting Postgres handle the setting, incrementing subscripts and loop exiting, rather than manually. See demo.
declare
-- PL SQL code to create and fill a two-dimensional array
-- create VARRAY type of 10 integers
type array_10_int is varray(10) of pls_integer;
-- create VARRAY type of array_10_int
type grid_100_int is varray(10) of array_10_int ;
-- declare a variable of the grid_100_int type
grid_var grid_100_int;
begin
grid_var := grid_100_int(null,null,null,null,null,null,null,null,null,null); -- initialize outer array
-- TO DO : use nested loop to fill grid_var with numbers 1- 100
/* YOUR CODE HERE */
for m in 1 .. 10
loop
grid_var(m) := array_10_int(null,null,null,null,null,null,null,null,null,null); -- initialize the inner array
for n in 1 .. 10
loop
grid_var(m)(n) := 10*(m-1)+ n;
end loop ;
end loop;
-- Print the grid with nested loop
for m in 1 .. 10
loop
for n in 1 .. 10
loop
dbms_output.put_line ('grid_var(' || to_char(m)
|| ')(' || to_char(n)
|| ') = ' || to_char(grid_var(m)(n))
);
end loop ;
end loop;
end;
Take away: Creating and using multiple dimensional arrays in Oracle is doable. But use them only when there is no other option. They add considerable complexity, usually unnecessary, and are poorly understood. (The above one is vary simple.)
Take away 2 Let Postgres control your loops. Less error prone, less code, easier to read.
PL/SQL array index begin with 1. But in the following code the local variable n is 0 when first used as an index.
Loop
M:=M+1;
N:=0;
LOOP
J:=j+1;
If grid_var(M)(N)<100 THEN --<<< n is 0 which throws your exception.
...
EXIT WHEN (N =100); --<<< NEVER occurs, N is not
Unfortunately, this is not your only issue (in this logic). You exit statement is condition EXIT WHEN (N =100); will never be met. You initialize the variable n before entering the loop, but never increment it in the loop.
Adjust the above to:
Loop
M:=M+1;
N:=1; -- <<< change here
LOOP
J:=j+1;
If grid_var(M)(N)<=100 THEN
...
EXIT WHEN (N >100); --<<< NEVER occurs, N is not incremented in the loop;
END LOOP;
Your other loop does not appear to have the same issue, but you should check it.

PL SQL output in hackerrank

set serveroutput on;
DECLARE
I NUMBER;
J NUMBER;
BEGIN
FOR I IN REVERSE 1..20
LOOP
FOR J IN 1..I
LOOP
DBMS_OUTPUT.PUT('* ') ; -- printing *
END LOOP;
DBMS_OUTPUT.NEW_LINE; -- for new line
END LOOP;
END;
'
Can anyone tell me why this code is not showing any output in the Hackerank question (Draw The Triangle) even after selecting Oracle?
https://www.hackerrank.com/challenges/draw-the-triangle-1/problem
That site doesn't seem to ever show your the output from your submission, unhelpfully, but does with just 'run code'. Surprisingly it does seem to understand PL/SQL; and even more surprisingly it handles the set serveroutput on, which is a SQL\Plus/SQL Developer client command.
But you need to add a terminating / after your code, on a line on its own - again, just like SQL*Plus (though SQL Developer is sometimes doesn't complain):
END LOOP;
END;
/
Your code doesn't produce the expected output because it has a trailing space on each line. Instead of:
DBMS_OUTPUT.PUT('* ') ; -- printing *
skip the space on the last iteration:
DBMS_OUTPUT.PUT('*') ; -- printing *
IF J < I THEN
DBMS_OUTPUT.PUT(' ');
END IF;
So this produces the expected output, and passes the test:
set serveroutput on;
DECLARE
I NUMBER; -- redundant
J NUMBER; -- redundant
BEGIN
FOR I IN REVERSE 1..20
LOOP
FOR J IN 1..I
LOOP
DBMS_OUTPUT.PUT('*') ; -- printing *
IF J < I THEN
DBMS_OUTPUT.PUT(' ');
END IF;
END LOOP;
DBMS_OUTPUT.NEW_LINE; -- for new line
END LOOP;
END;
/
However, your original code also passes the test, despite the trailing spaces, if you just add the terminating /:

Write a PL/SQL block to insert numbers into the MESSAGES table. Insert the numbers 1 through 10, excluding 6 and 8

I'm trying to figure out how to exclude these numbers(6 and 8) when the loop happens. Also, for this question, I CAN'T use FOR and WHILE loops. The question states to ONLY use a basic loop since the lessons after will teach me how to use it. Also, does anyone know if I'm allowed to insert multiple END LOOPs? It's also possible that this syntax may not be legal.
EDIT: I'm pretty sure I've tried doing IF v_results >10 THEN EXIT; but the same error message occurred.
DECLARE
v_results messages.results%TYPE := 0 ; --data type is NUMBER
BEGIN
LOOP
SELECT results INTO v_results
FROM messages;
v_results := v_results + 1; --to increment
IF v_results = ANY(6,8)
THEN
END LOOP; --i thought maybe if I added this, the loop can start over
ELSE
INSERT INTO MESSAGES(results)
VALUES (v_results);
EXIT WHEN v_results >10;
END IF;
END LOOP;
END;
The error that I am getting.
ORA-06550: line 15, column 9:
PLS-00103: Encountered the symbol "END" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with
<<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
You could just avoid the 6 and 8 by adding 1 into it using the IF statement. There is no need for ELSE statement.
Also, you will need to use MAX function while fetching data from the MESSAGE table so that it can return the only max number. Without MAX function, you will get an error of -- multiple rows returned.
END LOOP must be associated with a single LOOP statement. so you can not write two END LOOP when there is a single LOOP statement.
DECLARE
V_RESULTS MESSAGES.RESULTS%TYPE := 0; --data type is NUMBER
BEGIN
LOOP
SELECT
MAX(RESULTS) -- used max to find ONLY ONE MAX RECORD
INTO V_RESULTS
FROM
MESSAGES;
V_RESULTS := NVL(V_RESULTS, 0) + 1; --to increment
IF V_RESULTS IN ( -- used in here
6,
8
) THEN
V_RESULTS := V_RESULTS + 1;
END IF;
INSERT INTO MESSAGES ( RESULTS ) VALUES ( V_RESULTS );
EXIT WHEN V_RESULTS >= 10;
END LOOP;
END;
db<>fiddle demo
Cheers!!
declare
i number:=1;
begin
loop
if i not in(6,8) then
insert into msg values(i);
end if;
i :=i+1;
exit when i>10;
end loop;
end;

How to built a dynamic string PLSQL

I have 2 associative_arrays of number and I am trying to built a dynamic string for a dynamic query.
This is my for statement:
for indx_opt in 1..IDOPTarray.count loop
IF indx_opt=1 AND IDFGCFCParray.count=1 THEN
sql_stmt_2:=sql_stmt_2||' and wopt.id_ft_opt = ';
sql_stmt_2:=sql_stmt_2|| (IDOPTarray(indx_opt));
end if;
if indx_opt=1 AND IDFGCFCParray.count>1 then
sql_stmt_2:=sql_stmt_2||' and wopt.id_ft_opt in(';
sql_stmt_2:=sql_stmt_2||(IDOPTarray(indx_opt));
elsif indx_opt>=1 AND IDFGCFCParray.count>=0 then
sql_stmt_2:=sql_stmt_2||','||(IDOPTarray(indx_opt))||')';
With 2 number in IDOPTarray I get a correct result:
and wopt.id_ft_opt in(27,28)
Instead with more then 2 number in IDOPTarray I get this result:
,17228),17229),17230)
What I want to get is this:
where w.id = 303 and wopt.id_ft_opt in (17228,17229,17230)
if I have 5 numbers, I want to get this for the 'where' clause:
where w.id = 321 and wopt.id_ft_opt in (17228,17229,17230,17231,17232)
I want a dynamic output of my string.
IDFGCFCParray is the 2nd array, but is not important right now in order to get the output I want.
Does somebody can help me? thank you.
You have to close bracket only if indx_opt is equil to IDOPTarray.count.
And simple example.
declare
type list_number is table of number;
xx list_number := new list_number(1,2,3,5,7,8);
str varchar2(4000);
begin
for i in xx.first .. xx.last loop
if i = 1 then
str := ' condition in ('||xx(i);
else
str := str||','||xx(i);
end if;
if i = xx.last then
str := str||')';
end if;
end loop;
dbms_output.put_line(str);
end;
If you colleciton is sql levle type you can do this in this way
declare
xx list_number := new list_number(1,2,3,5,7,8);
str varchar2(4000);
begin
SELECT 'condition in ('||LISTAGG(column_value, ',') WITHIN GROUP (ORDER BY column_value)||')' into str from table(xx);
dbms_output.put_line(str);
end;

Resources