UPDATE : Also see Jeffrey Kemp's blog post on the same topic, earlier in 2008.
When you have a cursor that is assigning a return value to a variable, you expect the cursor to return a single row. If it returns more than one row, the resulting error is a TOO_MANY_ROWS error. This can be handled as an Exception.
However, this blog post is about what might be happening to the variable that you assign the fetched value to.
In this demo, I use a "CUSTOMER" table and a VALIDATE_CUSTOMER procedure :
When you have a cursor that is assigning a return value to a variable, you expect the cursor to return a single row. If it returns more than one row, the resulting error is a TOO_MANY_ROWS error. This can be handled as an Exception.
However, this blog post is about what might be happening to the variable that you assign the fetched value to.
In this demo, I use a "CUSTOMER" table and a VALIDATE_CUSTOMER procedure :
SQL> select * from v$version; BANNER -------------------------------------------------------------------------------- Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production PL/SQL Release 11.2.0.1.0 - Production CORE 11.2.0.1.0 Production TNS for Linux: Version 11.2.0.1.0 - Production NLSRTL Version 11.2.0.1.0 - Production SQL> SQL> drop table CUSTOMER purge; Table dropped. SQL> SQL> create table CUSTOMER 2 (cust_id number, sales_mgr_id number, cust_name varchar2(50), cust_start_date date, status_flag varchar2(1)); Table created. SQL> SQL> insert into CUSTOMER values (1,1,'USA-NY Customer',to_date('01-JAN-2010','DD-MON-YYYY'),'Y'); 1 row created. SQL> insert into CUSTOMER values (2,1,'USA-LA Customer',to_date('01-JUL-2010','DD-MON-YYYY'),'Y'); 1 row created. SQL> insert into CUSTOMER values (3,2,'GB-LN Customer',to_date('01-SEP-2010','DD-MON-YYYY'),'Y'); 1 row created. SQL> insert into CUSTOMER values (4,3,'FR-PR Customer',to_date('01-OCT-2010','DD-MON-YYYY'),'Y'); 1 row created. SQL> -- this cust_id 4 will be replacing the current one from 01-JAN-2013 SQL> -- unfortunately, the date entered is 01-JAN-2011 SQL> insert into CUSTOMER values (4,4,'JP-TY Customer',to_date('01-JAN-2011','DD-MON-YYYY'),'N'); 1 row created. SQL> SQL> commit; Commit complete. SQL> SQL> SQL> create or replace procedure VALIDATE_CUSTOMER (in_cust_id in number, out_cust_status out varchar2) 2 as 3 -- initialize status 4 -- U for Unknown 5 l_cust_flag varchar2(1) := 'U'; 6 begin 7 select status_flag into l_cust_flag 8 from CUSTOMER 9 where cust_id = in_cust_id 10 and cust_start_date <= SYSDATE; 11 out_cust_status := l_cust_flag; 12 dbms_output.put_line('Flag : ' || l_cust_flag); 13 dbms_output.put_line('Cust Status Returned : ' || out_cust_status); 14 exception 15 when others then 16 dbms_output.put_line('Flag : ' || l_cust_flag); 17 dbms_output.put_line('Exception !! Cust Status Returned : ' || out_cust_status); 18 end; 19 / Procedure created. SQL> SQL> show errors No errors. SQL>So, I have a table with 2 rows for CUST_ID=4. How will the procedure handle it ?
I have deliberately added dbms_output.put_line to show the values that the procedure "sees".
I run with three test CUST_IDs : 1 (existant and valid), 9 (not existant), 4 ("too_many_rows").
SQL> SET SERVEROUTPUT ON SQL> SQL> -- declare a variable to get the return value in sqlplus SQL> SQL> -- a valid customer SQL> variable cust_status_1 varchar2(1); SQL> execute VALIDATE_CUSTOMER(1,:cust_status_1); Flag : Y Cust Status Returned : Y PL/SQL procedure successfully completed. SQL> print :cust_status_1 CUST_STATUS_1 -------------------------------- Y SQL> SQL> -- a non-existent customer SQL> variable cust_status_9 varchar2(1); SQL> execute VALIDATE_CUSTOMER(9,:cust_status_9); Flag : U Exception !! Cust Status Returned : PL/SQL procedure successfully completed. SQL> print :cust_status_9 CUST_STATUS_9 -------------------------------------------------------------------------------- SQL> SQL> -- a cust_id with too_many_rows SQL> variable cust_status_4 varchar2(1); SQL> execute VALIDATE_CUSTOMER(4,:cust_status_4); Flag : Y Exception !! Cust Status Returned : PL/SQL procedure successfully completed. SQL> print :cust_status_4 CUST_STATUS_4 -------------------------------------------------------------------------------- SQL>The "Flag" and returned (output out_cust_status) values are correct for CUST_IDs 1 and 9. (For 9, the Flag is the initialized value of "U" because the cursor did not overwrite it).
However, for CUST_ID 4, although the procedure raises an EXCEPTION and returns no value, the Flag that is present *did* get reset from the initialized value of "U" to "Y" !! Apparently, when a cursor returns a row, it does assign the return value to the designated variable. If the cursor then returns *another* row, it raises an Exception but does not clear the value that was already assigned from the first row.
.
.
.
2 comments:
c.f.: http://jeffkemponoracle.com/2008/03/10/too_many_rows-side-effect/
Jeffrey,
Yes, I had seen your blog post after I had reproduced an issue (around TOO_MANY_ROWS exceptions) that we had encountered at work last Friday.
My blog post is a more developed case than the one I used to demonstrate the exception last Friday -- and I used your blog post, found later, as evidence.
Post a Comment