Total ROWS selected in a LOOP ORACLE PL/SQL - sql

I just want to iterate from 0 to n-1 in a oracle loop, just like this:
FOR X IN CLEEVECTOR LOOP
But am I wonder if it is possible to use a conditional like:
if X = TOTALROWS - 1 then
exit;
END IF:
There is some recomendation, or solution ?
Thank you for helping me out with this.

So you want to process all but the last value returned by the cursor. Since a cursor doesn't know how many rows it will return you can't just say "Break if this is the second-to-last row". But you can save the value to be processed, then process it on the next iteration, as in
DECLARE
strLast_ID VARCHAR2(1000) := NULL;
CURSOR CLEEVECTOR IS
select regexp_substr(p_checkboxes,'[^,]+', 1, level) ID
from dual
connect by regexp_substr(p_checkboxes, '[^,]+', 1, level) is not null;
BEGIN
FOR X IN CLEEVECTOR LOOP
IF strLast_ID IS NOT NULL THEN
-- code to process strLast_ID
END IF;
strLast_ID := X.ID;
END LOOP;
END;
Comment this extensively to explain what's going on so Those Who Follow After will be able to figure out what the intent was.

Related

Is it possible to see if a string contains [multiple] dictionary words?

Currently, I have a table of words (~250k) and I want to be able to detect if a User's [new] P/W contains zero, one, or more dictionary words defined within the table. I'd thought that the comparison below would send an error message with only 1 dictionary word but an example P/W string (has at least 4 words excluding Be-My) "ThisWillBeMyPassPhrase2!1!" still gets caught. Any ideas how to resolve this issue?
'''
open word_list;
loop
fetch word_list into word_rec;
exit when word_list%notfound;
if instr(nls_lower(password),nls_lower(word_rec.word)) = 1 then
raise_application_error(-20003, 'New password should have only ONE word or be a Passphrase');
end if;
end loop;
'''
error msg: ORA-20003: New password should have only ONE word or be a Passphrase
28003. 00000 - "password verification for the specified password failed"
*Cause: The new password did not meet the necessary complexity
Your current pseudo-code:
for i := 1 to dictionary_size do
if dictionary_word(i) is in candidate_password exactly 1 time then
stop the "if" and the "for" and raise app error;
end if;
end for;
Whereas it should be:
counter := 0;
for i := 1 to dictionary_size do
if dictionary_word(i) is in candidate_password exactly 1 time then
counter := counter + 1;
end if;
end for;
if counter = 1 then
raise app error;
end if;
Operating on a row set by looping over it like this is not a good idea. Row-by-row is slow-by-slow. You're better off doing this in a single step entirely in SQL. Something like:
select count(*) from (
select dt.word
from dictionary_table dt
where instr( lower( dt.word ), lower( password )) > 0
fetch first 2 rows only
)
The fetch first 2 rows only is there because you only care if there exists a passphrase, you don't care if there are 2 or 7 words in it. So once you find two matching words, you know you have a passphrase and can stop.

How to append elements of a recordlist when iterating over a list of records?

DECLARE
TYPE visit AS RECORD
(
DATA_1 VARCHAR2;
)
TYPE lstVisit IS TABLE OF visit;
BEGIN
FOR I IN LISTA.FIRST..LISTA.LAST LOOP
FOR J IN LISTB.FIRST..LISTB.LAST LOOP
lstfetchdata:=function_fetchdata(LISTA);
END LOOP;
lstVisit:= lstfetchdata;
END LOOP;
Now the issue I am facing is how to store all the elements in lstVisit.
Suppose the function fetchdata() returns 3 rows and in the subsequent call returns 2 and so on.
How should i index lstVisit to store all the values one after another
For each record returned by the function call, you can extend your final lstVisit collection, and assign the fetched record to that position; something like:
-- initialise the final collection
lstVisit := new tabVisit();
-- loop over both existing collections
for i in lista.first..lista.last loop
for j in listb.first..listb.last loop
-- get a subset of values
lstfetchdata:=function_fetchdata(lista(i), listb(j));
-- loop around that subset
for k in 1..lstfetchdata.count loop
-- extend the final collection and add the subset value
lstVisit.extend();
lstVisit(lstVisit.count) := lstfetchdata(k);
end loop;
end loop;
end loop;
db<>fiddle with some name changes and a made-up function to generate random strings based on the lista/b values, but should give you the idea.

Why is the last element in associative array (index by table) not printed by?

This seems like a basic thing but i am not able to figure out.
I have a PLSQL code block like below. It creates a simple sparse associative array and stores 5 elements at subscripts as -1,0,1,2,100.
Printing ARRAY.LAST gives 100 and ARRAY.COUNT gives 5. In my while loop it prints all elements properly.
But For loop going from ARRAY.FIRST to ARRAY.LAST prints only the elements at consecutive subscripts till 2, even though ARRAY.LAST gives 100
DECLARE
TYPE assoc_array IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
table1 assoc_array;
i BINARY_INTEGER;
BEGIN
table1(-1) := 100;
table1(0) := 101;
table1(1) := 102;
table1(2) := 103;
table1(100) := 104;
i := table1.FIRST;
dbms_output.put_line(table1.FIRST);
dbms_output.put_line(table1.LAST);
dbms_output.put_line(table1.COUNT);
i := table1.first;
while (i is not null)
loop
dbms_output.put_line( table1(i) );
i := table1.next(i);
end loop;
dbms_output.put_line( '***');
for i IN table1.FIRST .. table1.LAST
loop
dbms_output.put_line(table1(i) );
end loop;
END;
Output looks like:
-1
100
5
100
101
102
103
104
***
100
101
102
103
Add this snippet to your block to see the reason, which is ORA-01403:
...
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line(sqlerrm);
END;
Your loop doesn't loop through the effective array indexes. It loops from -1 .. 100, and when you try to acces table1(3), well, you cannot.
Alternatively, you could run this:
FOR i IN table1.FIRST .. table1.LAST LOOP
IF table1.EXISTS(i) THEN
dbms_output.put_line(table1(i));
END IF;
END LOOP;
But that's not a good idea, because it's quite inefficient to loop through all integer values that you already know are not indexes of your associative array.
ARRAY.FIRST is the smallest index number in your array, so in your case it will be the -1. On the other hand ARRAY.LAST took the greatest index number (100).
Then the FOR loop from the smallest index to the greatest with the step of 1. But when it reachs a non existing index it will throw an exception.
So just add one IF statement inside your loop to check that the element at a particular index exists.
IF array.EXISTS(i) THEN
-- do something
null;
END IF;

Printing the position of the bigger letter in a list of characters

I am having some logic thinking trouble with this task.
So the task asks to return the position of the first bigger letter in a list of letters.
For example:
ABVD -> 3
BCDG -> 4
CFDE -> 2
This tasks suggests to use lenght, ascii, and named block, function
So this is what I could do so far:
declare
x varchar2(10) :='ABFD';
BEGIN
FOR i in 1..length(x) LOOP
dbms_output.put_line(ASCII(SUBSTR(x, i, 1)));
END LOOP;
END;
My thought was to turn the letters to numbers : 65, 66, 70, 68. The pattern is x + 1 and since the number 70 is not equal 66 + 1, so the program will return the position of that number, which is 3.
Unfortunately I don't know how turn this idea into code. Can you give me some hints/suggestions? Thanks!
In the problem statement you said "... use named block, function."
Your solution is an anonymous procedure. It is not named anywhere (which is why it is called "anonymous"). And it is not a function - it doesn't return anything.
I will let you study the documentation to understand the difference between function and procedure, and how to name a function or procedure. Below I will follow your lead and show how you can modify your code to make it into a workable anonymous procedure. (In the procedure I "print" the final value of ind; when you change this to a function, you should return that value, instead of printing it.)
In the code you posted, you are printing the letters in the input string, one by one. You are not even attempting to define or assign to an integer (the index of the first occurrence of the "highest" letter in the string). That should be done in the DECLARE block. Then we also need to store the highest letter found "so far" (for future comparisons).
The code might look like this:
declare
x varchar2(10) :='ABFD';
ind number := 1;
max_letter char(1) := substr(x, 1, 1);
BEGIN
FOR i in 2..length(x) LOOP
if substr(x, i, 1) > max_letter
then max_letter := substr(x, i, 1);
ind := i;
end if;
END LOOP;
dbms_output.put_line(ind);
END;
/
Note that letters can be compared to each other directly, there is no reason to convert them to numbers.
Pure SQL using model clause
with t(str) as
(select 'ABVD' from dual
union all select 'BCDG' from dual
union all select 'CFDE' from dual)
select str, instr(str, max_chr) ind
from t
model
partition by (rownum rn)
dimension by (1 dummy)
measures (str, chr(1) max_chr)
rules
iterate (4e3) until (substr(str[1], iteration_number + 2, 1) is null)
(max_chr[1] = greatest(max_chr[1], substr(str[1], iteration_number + 1, 1)));
STR IND
---- ----------
ABVD 3
BCDG 4
CFDE 2

ERROR: Reference the counter as the target of an assignment - PL/SQL

I'm relatively new to SQL.
I'm trying to print a simple pattern through this code
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
a:=a+1;loop
for b in 1..temp loop
b:=b+1;
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
c:=c+1;
dbms_output.put_line('*');
end loop;
end loop;
end;
/
I keep getting this error:
PLS-00103: Encountered the symbol "A" when expecting one of the following:
* & - + / at loop mod remainder rem <an exponent (**)> ||
multiset
I understand Oracle doesn't allow to reference the counter as the target of an assignment which is why I keep getting error at line 6 but I'm unable to make it work even by declaring another global variable and assigning increment statement in it but it doesn't work either.
Please help.
Thanks!
Modifying the previous answers to actually give you Pascal's triangle, which you mentioned you were attempting in a comment:
set serveroutput on format wrapped
declare
n number(2):=5;
begin
for a in 1..n loop
for b in 1..n-a loop
dbms_output.put(' ');
end loop;
for c in 1..2*a-1 loop
dbms_output.put('*');
end loop;
dbms_output.new_line;
end loop;
end;
/
*
***
*****
*******
*********
PL/SQL procedure successfully completed.
Both your dbms_output.put_line calls needed to be just dbms_output.put, as that was printing each * on a line on its own. But you do need a line break after each time around the a loop, so I've added a dbms_output.newline at the end of that. You were also decrementing temp inside the b loop, which meant it was zero instead of (n-1) for the second time around the a loop; but you don't really need a separate temp variable at all as that is always the same as (n-a)+1 and the +1 just puts an extra space on every line. (I also made the a loop 1..n as I assume you want to change the value of n later in one place only). With n := 8:
*
***
*****
*******
*********
***********
*************
***************
Crucially though you also have to set serveroutput on format wrapped, otherwise the leading spaces you're generating in the b loop are discarded.
You can also do this in plain SQL, though you need to supply the 5 twice, or use a bind or substitution variable:
select lpad(' ', 5 - level, ' ') || rpad('*', (level * 2) - 1, '*') as pascal
from dual
connect by level <= 5
PASCAL
------------------------------
*
***
*****
*******
*********
Your b and c loops are just doing a manual lpad really.
When I took your code in my editor I first noticed, you tried to increase a before starting the loop, and Oracle gives first error at that point. And also it does not allow you to increase counter variable in for loop, (I don't know why) I checked on the internet and found that you can not set increment step for Oracle for loops also you can not set a value for counter variable in for loop.
The code below works fine for me :
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
loop --a:=a+1;
for b in 1..temp loop
--b:=b+1;
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
--c:=c+1;
dbms_output.put_line('*');
end loop;
end loop;
end;
/
As StephaneM says, loop variables are incremented by the loop itself: you don't need to do a := a + 1, and most of all you CAN'T assign them ! Here is a corrected version:
declare
n number(2):=5;
temp number(2):=n;
begin
for a in 1..5
loop
for b in 1..temp loop
dbms_output.put_line(' ');
temp:=temp-1;
end loop;
for c in 1..2*a-1 loop
dbms_output.put_line('*');
end loop;
end loop;
end;
/