Oracle Collection function - Not getting expected results - sql

Please have a look at the below code. The 4th element in the collection is null, and hence I am expecting that the variable does not exists. But still, I am getting TRUE when using exist function. Please clarify as to why this is happening?
DECLARE
type nt is varray(5) of varchar2(5);
nt1 nt := nt();
BEGIN
nt1.extend(5);
nt1 :=nt('ant','ball','cat',null,'elm');
for i in nt1.first..nt1.last
loop
dbms_output.put_line(nt1(i));
end loop;
if nt1.exists(4) then dbms_output.put_line('TRUE'); end if;
END;
Is it enough, If I have just extended that variable, for the exist function to evaluate to true?

The element is there, only its value is null.
With
nt1.extend(5);
you already create 5 entries, so nt1.exists(4) is true. As a varray can have no gaps, exists only tells you if the array is filled up to that position, yet. (And if it is, you can safely access that element.)
if nt1.exists(4) then
if nt1(4) is null then
dbms_output.put_line('Entry 4 is empty (null)');
else
dbms_output.put_line('Entry 4 has value' || nt1(4));
end if;
else
dbms_output.put_line('Array not filled up till 4th element, yet');
end if;

Related

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;

Custom SQL function - PL/SQL: Statement ignored

Hi made a function that modifies a little bit the INSTR function (Oracle SQL):
instead of getting '0' when a space char isn't found, I get the length of the string.
It compiles but when I try to execute it, i'm getting the ORA-06550 error (which is, if i'm not wrong, a compilation error).
I tried this function as a procedure and it runs all right... so I don't understand why it doesn't want to execute.
Can you help me?
CREATE OR REPLACE FUNCTION "INSTR2" (str varchar2) RETURN NUMBER AS
pos number(4,0) := 0;
BEGIN
select INSTR(str, ' ') into pos from dual;
if (pos = 0) then
select to_number(length(str)) into pos from dual;
return pos;
else
return pos;
end if;
END INSTR2;
Thanks a lot,
R.L.
Well, there is already a built-in function called INSTR2 in Oracle (look, here it is).
I've just compiled your function with another name and it worked. So the code is valid, you just have to pick the name for it.
Apparently Oracle resolves INSTR2 to the built-in function despite the fact you now have the function with such name in your own schema (I couldn't find the reason for it in docs, but this behaviour is not exactly surprising). And INSTR2 expects more parameters than your implementation. That causes a runtime error.
Change you function name for excample to INSTR_SPACE and everething there will be ok, because in oracle there is a instr2 function and you will have problem during calling
Change your function body to
create or replace
function INSTR_SPACE(str varchar2) return number as
pos number(4, 0) := 0;
begin
pos := INSTR(str, ' ');
if (pos = 0) then
return to_number(length(str));
else
return pos;
end if;
end INSTR_SPACE;
you dont need sql operations, you can do it by simple pl/sql operations and it will be more faster

Check if string contains certain characters

I'm trying to check if the login being inserted into employee table is given in a certain format, which is: the first letter is strictly given - x, then 5 letters and two numbers => xlogin00.
CREATE OR REPLACE TRIGGER check_login
BEFORE INSERT OR UPDATE OF login ON employee
FOR EACH ROW
DECLARE
xlogin00 employee.login%TYPE;
prefix VARCHAR2(1);
name VARCHAR2(5);
number VARCHAR2(2);
BEGIN
xlogin00 := :new.login;
prefix := substr(xlogin00,1,1);
name := substr(xlogin00,2,5);
number := substr(xlogin00,7,2);
if(LENGTH(xlogin00) != 8) then
Raise_Application_Error (-20203, 'Error');
end if;
if(prefix != 'x') then
Raise_Application_Error (-20204, 'Error');
end if;
if NOT REGEXP_LIKE(name, '[^a-z]') then
Raise_Application_Error (-20204, 'Error');
end if;
if NOT REGEXP_LIKE(number, '[^0-9]') then
Raise_Application_Error (-20204, 'Error');
end if;
END;
The only thing that seems to work in my code is if(prefix != 'x'). Other stuff doesn't work at all. What am I doing wrong?
You can do all of that with one check:
IF NOT REGEXP_LIKE(:new.login, '^x[a-z]{5}\d\d$') THEN
Raise_Application_Error (-20204, 'Error');
Note that you had also your boolean logic reversed: in both last tests you have a double negation (NOT in the IF and [^...] in the regex).
Note that in the proposed regex the ^ and $ are needed to make sure the match is with the complete login value, not just a substring.

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;
/