Handle NULL within Oracle PL/SQL - oracle

Data is imported into data_import table through sql insert statements. These insert statements may not be the most accurate so I want to let all data in to the table (hence no PK to prevent duplicates)
The code checks the contents of the data_import table for duplicate instances using a count on occurrences any occurrences are mark as in error.
DECLARE
CURSOR C_DUPLICATE_IMPORT_IDS
IS
SELECT COUNT (D.LEARNER_ID), D.LEARNER_ID
FROM ILA500.DATA_IMPORT D
WHERE D.USER_ID IN (SELECT S.OSUSER
FROM V$SESSION S
WHERE S.SID IN (SELECT DISTINCT V.SID
FROM V$MYSTAT V))
AND D.ERROR_FLAG = 'N'
AND D.LEARNER_ID <> NULL
HAVING COUNT (D.LEARNER_ID) > 1
GROUP BY D.LEARNER_ID;
V_DUPLICATE_IMPORT_IDS C_DUPLICATE_IMPORT_IDS%ROWTYPE;
BEGIN
OPEN C_DUPLICATE_IMPORT_IDS;
LOOP
FETCH C_DUPLICATE_IMPORT_IDS INTO V_DUPLICATE_IMPORT_IDS;
EXIT WHEN C_DUPLICATE_IMPORT_IDS%NOTFOUND;
UPDATE ILA500.DATA_IMPORT D
SET D.ERROR_FLAG = 'Y'
WHERE D.LEARNER_ID = V_DUPLICATE_IMPORT_IDS.LEARNER_ID;
UPDATE ILA500.DATA_IMPORT D
SET D.IMPORT_NOTIFICATION =
'DUPLICATE LEARNER_ID IDENTIFIED ('
|| V_DUPLICATE_IMPORT_IDS.LEARNER_ID
|| '). LEARNER_IDS ERROR_FLAG SET, THIS CASE WILL NOT IMPORT UNTIL CORRECTED.'
WHERE D.LEARNER_ID = V_DUPLICATE_IMPORT_IDS.LEARNER_ID;
IF V_DUPLICATE_IMPORT_IDS.LEARNER_ID IS NOT NULL
THEN
DBMS_OUTPUT.PUT_LINE (
'THE FOLLOWING LEARNER WAS IDENTIFIED AS A DUPLICATE '
|| V_DUPLICATE_IMPORT_IDS.LEARNER_ID);
ELSE
DBMS_OUTPUT.PUT_LINE (
CHR (10)
|| 'THERE ARE NO DUPLICATE LEARNER_IDS WITHIN THIS UPLOAD.');
END IF;
END LOOP;
CLOSE C_DUPLICATE_IMPORT_IDS;
DBMS_OUTPUT.PUT_LINE (CHR (10) || 'STEP 1 COMPLETED');
COMMIT;
END;
My problem comes within the IF statement
IF V_DUPLICATE_IMPORT_IDS.LEARNER_ID IS NOT NULL
THEN
DBMS_OUTPUT.PUT_LINE (
'THE FOLLOWING LEARNER WAS IDENTIFIED AS A DUPLICATE '
|| V_DUPLICATE_IMPORT_IDS.LEARNER_ID);
ELSE
DBMS_OUTPUT.PUT_LINE (
CHR (10)
|| 'THERE ARE NO DUPLICATE LEARNER_IDS WITHIN THIS UPLOAD.');
If the count is greater than 1 then data is returned and the
DBMS_OUTPUT.PUT_LINE ('THE FOLLOWING LEARNER WAS IDENTIFIED AS A DUPLICATE '|| DUPLICATE_IMPORT_IDS.LEARNER_ID);
statement successfully outputs the line .
However if a null count is returned within the query the
ELSE (DBMS_OUTPUT.PUT_LINE (
CHR (10)
|| 'THERE ARE NO DUPLICATE LEARNER_IDS WITHIN THIS UPLOAD.');)
doesn't output the line.
How to get a dmbs_output to produce data from a null?

However if a null count is returned ...
A null count can't be returned. The COUNT (D.LEARNER_ID) in your cursor query can't evaluate to null; it could produce zero but you're filtering any such results with your HAVING clause (which would also exclude null, if it could happen). And a null ID can't be returned because of how you are counting the values.
Your cursor query includes:
AND D.LEARNER_ID <> NULL
which isn't right; null isn't equal or unequal to anything, so this excludes all rows; you could do:
AND D.LEARNER_ID IS NOT NULL
but it's redundant anyway because you're doing COUNT(D.LERARNER_ID), which won't count nulls. It would make a difference if you were doing COUNT(*) though. db<>fiddle with just the cursor query, showing that effect.
Anyway... if you wanted to display a message for each ID if that had no duplicates you could remove the HAVING clause and test for non-zero instead of not-null; but from the text it looks like you want a single message if your cursor finds no rows at all. You can handle that with a 'found' flag which you set to true if you go into the cursor loop, and then test after the loop:
DECLARE
CURSOR C_DUPLICATE_IMPORT_IDS
IS
...
V_DUPLICATE_IMPORT_IDS C_DUPLICATE_IMPORT_IDS%ROWTYPE;
V_FOUND BOOLEAN := FALSE;
BEGIN
OPEN C_DUPLICATE_IMPORT_IDS;
LOOP
FETCH C_DUPLICATE_IMPORT_IDS INTO V_DUPLICATE_IMPORT_IDS;
EXIT WHEN C_DUPLICATE_IMPORT_IDS%NOTFOUND;
...
DBMS_OUTPUT.PUT_LINE (
'THE FOLLOWING LEARNER WAS IDENTIFIED AS A DUPLICATE '
|| V_DUPLICATE_IMPORT_IDS.LEARNER_ID);
V_FOUND := TRUE;
END LOOP;
CLOSE C_DUPLICATE_IMPORT_IDS;
IF NOT V_FOUND THEN
DBMS_OUTPUT.PUT_LINE (
CHR (10)
|| 'THERE ARE NO DUPLICATE LEARNER_IDS WITHIN THIS UPLOAD.');
END IF;
DBMS_OUTPUT.PUT_LINE (CHR (10) || 'STEP 1 COMPLETED');
COMMIT;
END;
db<>fiddle with and without matching data.
You could also simplify this a bit with an implicit cursor loop but the 'found' logic would be the same.

In the past, when a DML statement fails the whole statement is rolled back, regardless of how many rows were processed successfully before the error was detected. the only way around this problem was to process each row individually. There's a DBMS_ERRLOG package that provides a procedure that enables you to create an error logging table so that DML operations can continue after encountering errors rather than abort and rollback. This enables you to save time and system resources.
You can use this package and set the primary key in your table. For example:
-- First, create the error logging table.
BEGIN
DBMS_ERRLOG.create_error_log (dml_table_name => 'dest_table');
END;
Then in your insert staement do this:
INSERT INTO dest_table
SELECT *
FROM source_table
LOG ERRORS INTO err$_dest_table ('INSERT') REJECT LIMIT UNLIMITED;
Finally, You don't need these codes.
For more information read here and here.

Related

function not returning a value for no rows returned

I created a function that takes a movie id as input and returns stock information based from the ID. The function mostly works but if I want to retrieve information from a movie that is not in the database(returns no rows) nothing returns. Can't figure out why?
doesn't give me an error when i call an ID that returns no rows so exception handling wouldn't work.
create or replace function stock_info
(p_id IN NUMBER
)
return VARCHAR2
IS
cursor c1 is
select movie_id, movie_title, movie_qty
from mm_movie
where p_id = movie_id;
lv_movie_info VARCHAR2(100);
BEGIN
for i in c1 loop
if p_id = i.movie_id then
lv_movie_info := i.movie_title || ' is available: ' || i.movie_qty || ' on the shelf';
else
lv_movie_info := 'no data found';
end if;
end loop;
return lv_movie_info;
END STOCK_INFO;
/
The reason you don't get anything when there is no data is that the loop doesn't execute. Logically the For expression says "execute the following loop for every row returned in the cursor" but there are no rows in the cursor so it never executes the loop. Further the structure actually indicates you are expecting multiple for a given p_id. If that's not the case you can eliminate the cursor all together. Assuming p_id is the primary key you have either 0 or 1 row so:
create or replace function stock_info (p_id in number)
return text
is
lv_movie_info varchar2(100);
begin
select i.movie_title || ' is available: ' || i.movie_qty || ' on the shelf'
into lv_movie_info
from mm_movie i
where p_id = movie_id;
return lv_movie_info;
exceptions
when no_data_found
then return 'no data found';
end stock_info;
Of course if do expect more that 1 row the cursor is needed, but the IF is not as the were clause guarantees it's true. Still with 0 rows the loop will not be executed so the 'no data found' message needs to go after "End Loop".
Belayer
the cursor statement you used fetches data from the in parameter. i.e., in the cursor select you limiting based on the movie id passed.
on passing a movie id which is not in the data base, the cursor select statement would not fetch any records, and so the flow won't even go inside the for loop.
if you wanted to return no data found - on passing a movie id which is not in the database, two ways to resolve
1. before the loop, have select statement to set a flag to Y or N if exists according and to have your requirement.
2. in if not using for cursor, there is an option to check not found...
sample:
declare
cursor c1 is select * from table_sample; -- empty table
c_rec c1%rowtype;
begin
open c1;
fetch c1 into c_rec;
if c1%notfound then
dbms_output.put_line('not found');
end if;
close c1;
end;
-- output
not found

Why I receive "NO DATA FOUND" exception when I use RETURNING clause after update statement?

I'm coding a simple LOOP FOR to update a column from a table using information populated in an associative array. All seems right when I only use UPDATE statement but when I add a RETURNING clause I receive "NO DATA FOUND" error. Thanks!
DECLARE
TYPE emps_info IS TABLE OF employees23%ROWTYPE
INDEX BY PLS_INTEGER;
t_emps_current_info emps_info;
t_emps_new_info emps_info;
BEGIN
SELECT *
BULK COLLECT INTO t_emps_current_info
FROM employees;
FOR emps_index IN t_emps_current_info.FIRST .. t_emps_current_info.LAST
LOOP
IF
NVL(t_emps_current_info(emps_index).commission_pct, 0) = 0 THEN
UPDATE employees23
SET commission_pct = 0.3
WHERE employee_id = t_emps_current_info(emps_index).employee_id;
ELSIF
t_emps_current_info(emps_index).commission_pct BETWEEN 0.1 AND 0.3 THEN
UPDATE employees23
SET commission_pct = 0.5
WHERE employee_id = t_emps_current_info(emps_index).employee_id;
END IF;
END LOOP;
END;
Now When I add RETURNING clause I receive following error:
DECLARE
TYPE emps_info IS TABLE OF employees23%ROWTYPE
INDEX BY PLS_INTEGER;
t_emps_current_info emps_info;
t_emps_new_info emps_info;
BEGIN
SELECT *
BULK COLLECT INTO t_emps_current_info
FROM employees;
FOR emps_index IN t_emps_current_info.FIRST .. t_emps_current_info.LAST
LOOP
IF
NVL(t_emps_current_info(emps_index).commission_pct, 0) = 0 THEN
UPDATE employees23
SET commission_pct = 0.3
WHERE employee_id = t_emps_current_info(emps_index).employee_id
RETURNING commission_pct
INTO t_emps_new_info(emps_index).commission_pct;
ELSIF
t_emps_current_info(emps_index).commission_pct BETWEEN 0.1 AND 0.3 THEN
UPDATE employees23
SET commission_pct = 0.5
WHERE employee_id = t_emps_current_info(emps_index).employee_id
RETURNING commission_pct
INTO t_emps_new_info(emps_index).commission_pct;
END IF;
DBMS_OUTPUT.PUT_LINE('EMPLOYEE_ID: ' ||
t_emps_current_info(emps_index).employee_id ||
' OLD COMMISSION: ' ||
NVL(t_emps_current_info(emps_index).commission_pct, 0)
|| ' NEW COMMISSION: ' ||
t_emps_new_info(emps_index).commission_pct);
END LOOP;
END;
Informe de error -
ORA-01403: no data found
ORA-06512: at line 22
01403. 00000 - "no data found"
*Cause: No data was found from the objects.
*Action: There was no data from the objects which may be due to end of fetch.
This part in your code
DBMS_OUTPUT.PUT_LINE('EMPLOYEE_ID: ' ||
t_emps_current_info(emps_index).employee_id ||
' OLD COMMISSION: ' ||
NVL(t_emps_current_info(emps_index).commission_pct, 0)
|| ' NEW COMMISSION: ' ||
t_emps_new_info(emps_index).commission_pct);
prints both the original array as well as the returning array .
At some point of time during your update it may happen that your update statement doesnt update any row , in that case your original array will have a value at that index , but your returning array wont
So check for the existence of index to avoid this error
case when t_emps_new_info.exists(emps_index) then
dbms_output.put_line('print something') ;
else dbms_output.put_line('print something else') ;
end case;
You are getting NO_DATA_FOUND because nowhere in your code are you initialising (creating) the t_emps_new_info(emps_index) records - whenever your code tries to assign t_emps_new_info(emps_index).commission_pct it will fail.
I'm not sure why it should be necessary, but I would change each UPDATE statement return the value into a simple local variable. Then I'd set the table record field to the simple variable.
Probably unrelated, but I noticed that your ELSIF test isn't doing an NVL() on the existing t_emps_current_info(emps_index).commission_pct, as your IF test does.
If it was me, I'd log the updates of both sides of the IF-ELSEIF to see if one update is working while the other fails. Maybe it's just failing on the ELSIF, because of the missing NVL()?

Error in pl/sql block has me stuck - Number and/or types of columns in a query does not match

Not quite sure what I am doing wrong here, went over the code a few times tonight and I think I need a fresh set of eyes. I keep getting an error that states:
*Cause: Number and/or types of columns in a query does not match declared
return type of a result set variable, or declared types of two Result
Set variables do not match.
*Action: Change the program statement or declaration. Verify what query the variable
actually refers to during execution.
DECLARE
cv_prod SYS_REFCURSOR;
rec_payment dd_payment%ROWTYPE;
TYPE pay2 IS RECORD (
pledge_id dd_pledge.idpledge%TYPE,
amount NUMBER(8,2)
);
rec_payment2 pay2;
lv_donor_id dd_donor.iddonor%TYPE := 303;
lv_indicator_value CHAR(1) := 'd';
BEGIN
IF lv_indicator_value = 'd' THEN
OPEN cv_prod FOR
SELECT idpledge, payamt, paydate, paymethod
FROM dd_pledge inner join dd_payment USING (idpledge)
WHERE iddonor = lv_donor_id
ORDER BY idpledge, paydate;
LOOP
FETCH cv_prod INTO rec_payment;
EXIT WHEB cv_prod%NOTFOUND;
dbms_output.put_line(rec_payment.idpledge || ' ' || rec_payment.payamt || ' ' || rec_payment.paydate || ' ' || rec_payment.paymethod);
END LOOP;
ELSIF Lv_Indicator_Value = 's' THEN
OPEN cv_Prod FOR
SELECT idpledge, payamt, paydate, paymethod
FROM dd_pledge INNER JOIN dd_payment USING (idpledge)
WHERE iddonor = lv_donor_id
GROUP BY idpledge;
LOOP
FETCH cv_prod INTO rec_payment2;
EXIT WHEN cv_prod%NOTFOUND;
dbms_output.put_line(rec_payment2.pledge_id || ' ' || rec_payment2.amount);
END LOOP;
END IF;
END;
You're collecting the fields selected by your query into a record which doesn't have the same number and type of fields of your query.
For example, in your second cursor you're selecting the fields idpledge, payamt, paydate, paymethod:
OPEN cv_prod FOR
SELECT idpledge, payamt, paydate, paymethod
FROM dd_pledge INNER JOIN dd_payment USING (idpledge)
WHERE iddonor = lv_donor_id
GROUP BY idpledge;
And you're trying to put them into the rec_payment2 record:
FETCH cv_prod INTO rec_payment2;
But the type of this record is pay2, which is a record with only two fields:
TYPE pay2 IS RECORD (
pledge_id dd_pledge.idpledge%TYPE,
amount NUMBER(8,2)
);

Fetch variable from cursor error in oracle pl sql

I'm fetching value using cursor:
CURSOR Colcond
IS
SELECT CONDITION
FROM CONDITION_TAB
WHERE PROCEDURE_NAME = 'CALL_VOL';
In first iteration it would fetch "SUM(CASE WHEN CALL_REF=0 THEN 1 ELSE 0 END)".
In my program:
OPEN Colcond;
FETCH Colcond INTO cond_val;
SELECT Appnum, customer_num,'"cond_val"'
INTO iappnum, icustnum,icond_val
FROM CALL_DETAILS WHERE APPNUM = val_appl
AND customer_num = val_cust
Group by APPLICATION_NUM,CUST_SGMT_NUM,DNIS;
INSERT INTO S_CALL_VOLUME VALUES (iappnum, icustnum, SYSDATE, icond_val);
The record thRough the variable "icond_val" inserted is SUM(CASE WHEN CALL_REF=0 THEN 1 ELSE 0 END) instead of the value (10 or 20 or 50).
How to get the value instead of that Sum case statement?
You need to use dynamic SQL to incorporate the value you selected from the condition_tab table into the next query. Here's an example in an anonymous block rather than a procedure:
declare
val_appl number; -- procedure argument in your version?
val_cust number; -- procedure argument in your version?
query_string varchar2(2000);
cond_val condition_tab.condition%type;
iappnum call_details.appnum%type;
icustnum call_details.customer_num%type;
icond_val number;
cursordyn sys_refcursor;
cursor colcond is
select condition
from condition_tab
where procedure_name = 'CALL_VOL';
begin
open colcond;
fetch colcond into cond_val;
close colcond;
query_string:='select appnum, customer_num, ' || cond_val || ' from call_details '
|| 'where appnum = :val_appl and customer_num = :val_cust '
|| 'group by application_num,cust_sgmt_num,dnis';
open cursordyn for query_string using val_appl, val_cust;
fetch cursordyn into iappnum, icustnum, icond_val;
close cursordyn;
insert into s_call_volume values (iappnum, icustnum, sysdate, icond_val);
end;
/
Your column names seem to be a bit inconsistent so it probably needs some tweaking.
For both cursors you're only selecting one row, so (a) they don't really need to be cursors, they can just be select into statements; and (b) the second one is selecting the two columns from the where clause which seems a bit pointless - when you use iappnum in the insert, you could just use val_app, etc. So I think you could simplify this to:
declare
val_appl number; -- procedure argument in your version?
val_cust number; -- procedure argument in your version?
query_string varchar2(2000);
cond_val condition_tab.condition%type;
icond_val number;
begin
select condition
into cond_val
from condition_tab
where procedure_name = 'CALL_VOL';
query_string:='select ' || cond_val || ' from call_details '
|| 'where appnum = :val_appl and customer_num = :val_cust '
|| 'group by application_num,cust_sgmt_num,dnis';
execute immediate query_string into icond_val using val_appl, val_cust;
insert into s_call_volume values (val_appl, val_cust, sysdate, icond_val);
end;
/
This will error if either query doesn't return exactly one row. Your cursor version will error if the condition_tab query finds no data, and will only use one 'condition' if there are multiples; and will only use the first result from the second query if there are multiples. If you're expecting multiples from either (not sure what your actual grouping is supposed to be, it looks inconsistent too) then you need to loop over the cursor, fetching repeatedly.
Hopefully this will get you started though.

Oracle PL\SQL Null Input Parameter WHERE condition

As of now I am using IF ELSE to handle this condition
IF INPUT_PARAM IS NOT NULL
SELECT ... FROM SOMETABLE WHERE COLUMN = INPUT_PARAM
ELSE
SELECT ... FROM SOMETABLE
Is there any better way to do this in a single query without IF ELSE loops. As the query gets complex there will be more input parameters like this and the amount of IF ELSE required would be too much.
One method would be to use a variant of
WHERE column = nvl(var, column)
There are two pitfalls here however:
if the column is nullable, this clause will filter null values whereas in your question you would not filter the null values in the second case. You could modify this clause to take nulls into account but it turns ugly:
WHERE nvl(column, impossible_value) = nvl(var, impossible_value)
Of course if somehow the impossible_value is ever inserted you will run into some other kind of (fun) problems.
The optimizer doesn't understand correctly this type of clause. It will sometimes produce a plan with a UNION ALL but if there are more than a couple of nvl, you will get full scan even if perfectly valid indexes are present.
This is why when there are lots of parameters (several search fields in a big form for example), I like to use dynamic SQL:
DECLARE
l_query VARCHAR2(32767) := 'SELECT ... JOIN ... WHERE 1 = 1';
BEGIN
IF param1 IS NOT NULL THEN
l_query := l_query || ' AND column1 = :p1';
ELSE
l_query := l_query || ' AND :p1 IS NULL';
END IF;
/* repeat for each parameter */
...
/* open the cursor dynamically */
OPEN your_ref_cursor FOR l_query USING param1 /*,param2...*/;
END;
You can also use EXECUTE IMMEDIATE l_query INTO l_result USING param1;
This should work
SELECT ... FROM SOMETABLE WHERE COLUMN = NVL( INPUT_PARAM, COLUMN )

Resources