Is it possible to use sql%rowcount for SELECT? - oracle

The code below may return more than one row. Will sql%rowcount return the number of rows fetched?
select * from emp where empname = 'Justin' and dept='IT'
if sql%rowcount>0
...
This is my sample proc; am I using sql%rowcount in correct way?
CREATE PROCEDURE Procn(in_Hid IN VARCHAR2,outInststatus OUT VARCHAR2,outSockid IN NUMBER,outport OUT VARCHAR2,outIP OUT VARCHAR2,outretvalue OUT NUMBER)
AS
BEGIN
select INST_STATUS into outInststatus from TINST_child where INST_ID = in_Hid and INST_STATUS = 'Y';
if outInststatus = 'Y' then
select PORT_NUMBER,STATIC_IP into outport,outIP from TINST where INST_ID = in_Hid and IP_PORT_STATUS = 'Y';
if sql%rowcount >= 1 then
select SOCK_ID into outSockid from TINST where PORT_NUMBER = outport AND STATIC_IP = outIP;
outretvalue := 0;
else
outretvalue := -12;
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -13;
end if;
END;

Yes, you can use SQL%ROWCOUNT. It's valid in PL/SQL.
However, in PL/SQL the result of your query needs to go somewhere e.g. into a PL/SQL table. PL/SQL will never send the result to the output (terminal, window etc.). So SELECT * FROM won't work.
Your code could look like this:
DECLARE
TYPE emp_t ...;
emp_tab emp_t;
BEGIN
SELECT *
BULK COLLECT INTO emp_tab
FROM emp
WHERE empname = 'Justin' AND dept='IT';
IF sql%rowcount > 0 THEN
.. do something ...
END IF;
END;
/
Update:
The updated questions suggests that you're looking for something else.
Option 1: Use exceptions
If there are 0 rows or more than 1 row, these cases are handled separately (as errors):
BEGIN
select PORT_NUMBER,STATIC_IP into outport, outIP
from TINST
where INST_ID = in_Hid AND IP_PORT_STATUS = 'Y';
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -12;
RETURN;
WHEN TOO_MANY_ROWS THEN
outretvalue := -13;
RETURN;
END;
Option 2: Use aggregations
Using aggregations, the query will always return exactly one row. If now source row matched the WHERE clause, then both result values will be NULL. If there WHERE clause matched more than one row, the maximum will be taken.
Note that this query might return a port number and an IP address that originally were not on the same row.
select MAX(PORT_NUMBER), MAX(STATIC_IP) into outport, outIP
from TINST
where INST_ID = in_Hid AND IP_PORT_STATUS = 'Y';
IF outport IS NULL OR outIP IS NULL THEN
outretvalue := -12;
RETURN;
END IF;
Option 3: Use ROWNUM
This query returns at most one row. If no row matched the WHERE clause, an exception is thrown and needs to be handled:
BEGIN
select PORT_NUMBER, STATIC_IP into outport, outIP
from TINST
where INST_ID = in_Hid AND IP_PORT_STATUS = 'Y'
AND ROWNUM = 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -12;
RETURN;
END;

Based on your comment
If 2nd 'select' query returns more than one row i want to take the first one and process with it
... this ought to work, but perhaps not quite as you expect, as you haven't defined what the 'first one' means.
CREATE PROCEDURE Procn(in_Hid IN VARCHAR2, outInststatus OUT VARCHAR2,
outSockid IN NUMBER, outport OUT VARCHAR2, outIP OUT VARCHAR2,
outretvalue OUT NUMBER)
AS
BEGIN
select INST_STATUS into outInststatus
from TINST_child
where INST_ID = in_Hid and INST_STATUS = 'Y';
-- no need to check if outInstatus is Y, that's all it can be here
-- restricting with `rownum` means you'll get at most one row, so you will
-- not get too_many_rows. But it will be an arbitrary row - you have no
-- criteria to determine which of the multiple rows you want. And you can
-- still get no_data_found which will go to the same exception and set -12
select PORT_NUMBER, STATIC_IP into outport, outIP
from TINST
where INST_ID = in_Hid and IP_PORT_STATUS = 'Y'
and rownum < 2;
-- no need to check sql%rowcount; it can only be 1 here
-- not clear if this can return multiple rows too, and what should happen
-- if it can; could use rownum restriction but with the same caveats
select SOCK_ID into outSockid
from TINST
where PORT_NUMBER = outport AND STATIC_IP = outIP;
outretvalue := 0;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -12;
END;
The exception handler applies to the whole block. If any of the select statements find no rows, the no_data_found exception will be handled by that block and will set outretvalue to -12.
If you want a different outretvalue for each select then you can wrap them in sub-blocks, each with their own exception handling section:
CREATE PROCEDURE Procn(in_Hid IN VARCHAR2, outInststatus OUT VARCHAR2,
outSockid IN NUMBER, outport OUT VARCHAR2, outIP OUT VARCHAR2,
outretvalue OUT NUMBER)
AS
BEGIN
BEGIN
select INST_STATUS into outInststatus
from TINST_child
where INST_ID = in_Hid and INST_STATUS = 'Y';
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -12;
END;
BEGIN
select PORT_NUMBER, STATIC_IP into outport, outIP
from TINST
where INST_ID = in_Hid and IP_PORT_STATUS = 'Y'
and rownum < 2;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -13;
END;
BEGIN
select SOCK_ID into outSockid
from TINST
where PORT_NUMBER = outport AND STATIC_IP = outIP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := -14;
END;
outretvalue := 0;
END;
You only need to do that if the caller needs to know which select failed, and if you never really expect any of them to fail then it's probably more common not to catch the exception at all and let the caller see the raw no_data_found and decide what to do. Depends what the exception condition means to you and your application though.

Related

Passing data when query returns value and No "EXCEPTION WHEN NO_DATA_FOUND THEN" (Oracle 11g)

I have created a procedure for updating my t_ritm table. First I have select rrcd_qnty (which is my product quantity) of a product id from t_rrcd table. Then I update the rrcd_qnty value in t_ritm table.
Here is my procedure:
procedure update_ritm_new_rate(p_oid in varchar2, p_ritm_rate in varchar2, p_euser in varchar2)
is
nrate varchar2(4);
begin
SELECT rrcd_rate into nrate
FROM (select oid, t_rrcd.rrcd_rate
from t_rrcd
where rrcd_ritm= p_oid
ORDER BY oid DESC )
WHERE rownum <= 1
ORDER BY rownum DESC ;
EXCEPTION
WHEN NO_DATA_FOUND THEN nrate := 0;
update t_ritm
set ritm_rate = nrate, euser = p_euser, edat = sysdate
where oid = p_oid;
commit;
end update_ritm_new_rate;
Some of my product id Quantity was null. so I was getting No_Data_Found error. But when and which product id has Quantity value they were successfully updating. For avoiding No_Data_Found I used EXCEPTION WHEN NO_DATA_FOUND THEN nrate := 0; which solved my no_Data_Found error. But when product id has quantity value they were not updating.
I had search lot of for this issue but not get good solution. What should be the best practice for avoiding No_Data_Found error? Could I pass my value if I don't get any No_Data_Found error?
thank in advance
That's because - if your SELECT returns something, it never reaches UPDATE as it is hidden behind the EXCEPTION handler.
Therefore, enclose it (SELECT) into its own BEGIN-END block, and put UPDATE out of it so that it is executed with whichever NRATE value is used.
PROCEDURE update_ritm_new_rate (p_oid IN VARCHAR2,
p_ritm_rate IN VARCHAR2,
p_euser IN VARCHAR2)
IS
nrate VARCHAR2 (4);
BEGIN
BEGIN --> this
SELECT rrcd_rate
INTO nrate
FROM ( SELECT oid, t_rrcd.rrcd_rate
FROM t_rrcd
WHERE rrcd_ritm = p_oid
ORDER BY oid DESC)
WHERE ROWNUM <= 1
ORDER BY ROWNUM DESC;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
nrate := 0;
END; --> this
UPDATE t_ritm
SET ritm_rate = nrate, euser = p_euser, edat = SYSDATE
WHERE oid = p_oid;
COMMIT;
END update_ritm_new_rate;
I have fixed the issue by adding EXCEPTION WHEN NO_DATA_FOUND THEN nrate := 0; after the update query.
procedure update_ritm_new_rate(p_oid in varchar2, p_ritm_rate in varchar2, p_euser in varchar2)
is
nrate varchar2(4);
begin
SELECT rrcd_rate into nrate FROM (select oid, t_rrcd.rrcd_rate from t_rrcd where rrcd_ritm= p_oid ORDER BY oid DESC )
WHERE rownum <= 1 ORDER BY rownum DESC ;
update t_ritm set ritm_rate = nrate, euser = p_euser, edat = sysdate where oid = p_oid;
commit;
EXCEPTION WHEN NO_DATA_FOUND THEN nrate := 0;
end update_ritm_new_rate;

Bypass no data execution Oracle PL/SQL

I am trying to check for two kinds of authorization level for entries in the menu to display according to different levels given to each user.
But the no data exception in oracle sql is preventing me from checking the second table for authorization.
Here is my code:
declare
v_count number;
v_name varchar2(255);
begin
select DASHBOARD into v_count from RCM_ADMINISTRATOR where USER_NAME = :APP_USER;
select MANAGER into v_name from RCM_ADMINISTRATION_TEAMS where MANAGER = :APP_USER;
if v_count >= 1 then
return true;
end if;
if v_name is not null then
return true;
end if;
if v_count = 0 then
return false;
end if;
EXCEPTION WHEN NO_DATA_FOUND THEN
return false;
end;
Is there a way to make it so that if I get no data from the first query, I can still run the second query?
This is a very common problem, with many solutions.
One option is to put a BEGIN / EXCEPTION / END block around each SELECT INTO that you're trying to do.
declare
v_count number;
v_name varchar2(255);
begin
BEGIN
select DASHBOARD into v_count from RCM_ADMINISTRATOR where USER_NAME = :APP_USER;
EXCEPTION when no_data_found THEN v_count := null;
END;
BEGIN
select MANAGER into v_name from RCM_ADMINISTRATION_TEAMS where MANAGER = :APP_USER;
EXCEPTION when no_data_found THEN v_name := null;
END;
if v_count >= 1 then
...etc...
I often find it easier just to use MAX:
declare
v_count number;
v_name varchar2(255);
begin
select max(DASHBOARD) into v_count from RCM_ADMINISTRATOR where USER_NAME = :APP_USER;
select max(MANAGER) into v_name from RCM_ADMINISTRATION_TEAMS where MANAGER = :APP_USER;
if v_count >= 1 then
...etc...
It's a lazy technique, and if you aren't careful, it can disguise ORA-01422 errors (exact fetch returns more than requested number of rows). But if you know your query will only return 0 or 1 row, then it will work fine.
There are several issues with your code. You are using an anonymous block and returning a value, which is not possible. You can write a function which returns a BOOLEAN value(True or False) However, it is of no use because as per Oracle documentation
http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/datatypes.htm#CJACJGBG.
You cannot insert the values TRUE and FALSE into a database column.
You cannot select or fetch column values into a BOOLEAN variable.
Functions called from a SQL query cannot take any BOOLEAN parameters.
Neither can built-in SQL functions such as TO_CHAR; to represent
BOOLEAN values in output, you must use IF-THEN or CASE constructs to
translate BOOLEAN values into some other type, such as 0 or 1, 'Y' or
'N', 'true' or 'false', and so on.
So In the code, VARCHAR2 is used as RETURN argument.
Pass APP_USER as function argument instead of using a bind variable.
For your condition that each query must execute irrespective of exceptions, you can place it in separate BEGIN..END blocks.
Once you have used RETURN from a stored procedure, you cannot jump back to the rest of the code after exception as you are trying to achieve. Instead, store the value in a variable exit_code and RETURN using the code before the last END to return eventually with an exit code if other conditions are not satisfied.
CREATE OR REPLACE FUNCTION f_test_ret(p_app_user IN VARCHAR2)
RETURN VARCHAR2
AS
v_count NUMBER;
v_name VARCHAR2(255);
exit_code VARCHAR2(6);
BEGIN
BEGIN
SELECT DASHBOARD
INTO v_count
FROM RCM_ADMINISTRATOR
WHERE USER_NAME = p_app_user;
EXCEPTION
WHEN NO_DATA_FOUND THEN
exit_code := 'FALSE';
END;
BEGIN
SELECT MANAGER
INTO v_name
FROM RCM_ADMINISTRATION_TEAMS
WHERE MANAGER = p_app_user;
EXCEPTION
WHEN NO_DATA_FOUND THEN
exit_code := 'FALSE';
END;
IF v_count >= 1 THEN
RETURN 'TRUE';
END IF;
IF v_name IS NOT NULL THEN
RETURN 'TRUE';
END IF;
IF v_count = 0 THEN
RETURN 'TRUE';
END IF;
RETURN exit_code;
END;
/

PL/SQL INSERT Ignored statement

What I have to do is to INSERT in table "info" different content, depending on the select result: if it is one row, no rows or more than one row.
I want to set the outretvalue variable on the exception section, then do the insert in the IF section, depending on outretvalue value.
Anyway, I get an error at compiling saying that f2 function is in an invalid state. I have 2 errors: for the INSERT and for not recognising rowcount. Why?
CREATE OR REPLACE FUNCTION f2 (v_nume employees.last_name%TYPE DEFAULT 'Bell')
RETURN NUMBER
IS
salariu employees.salary%type;
outretvalue number(2) := 0;
BEGIN
SELECT salary
INTO salariu
FROM employees
WHERE last_name = v_nume;
RETURN salariu;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := 1;
WHEN TOO_MANY_ROWS THEN
--at this row I have 2 errors: for the INSERT and for not recognising rowcount
INSERT INTO info(`no_lines`) VALUES(SQL%ROWCOUNT);
END f2;
/
SELECT f2('King') FROM dual;
Your function:
DECLARE
BEGIN
END;
... something
END;
Add another BEGIN at begin or move your IF inside existing BEGIN END block and remove second END.
EDIT: after clarification
CREATE OR REPLACE FUNCTION f2 (v_nume employees.last_name%TYPE DEFAULT 'Bell')
RETURN NUMBER
IS
salariu employees.salary%type;
outretvalue number(2) := 0;
BEGIN
SELECT salary
INTO salariu
FROM employees
WHERE last_name = v_nume;
RETURN salariu;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN -1;
WHEN TOO_MANY_ROWS THEN
SELECT count(*)
INTO salariu
FROM employees
WHERE last_name = v_nume;
INSERT INTO info(no_lines) VALUES(salariu);
RETURN -2;
WHEN OTHERS THEN
RETURN -3;
END f2;
/
SET SERVEROUTPUT on
DECLARE
l_ret NUMBER;
BEGIN
dbms_output.put_line(f2('Bell'));
dbms_output.put_line(f2('noBell'));
dbms_output.put_line(f2('King'));
END;
Try this. It will definelty help you out.
CREATE OR REPLACE FUNCTION f2(
v_nume employees.last_name%TYPE DEFAULT 'Bell')
RETURN NUMBER
IS
salariu employees.salary%type;
outretvalue NUMBER(2) := 0;
lv_cnt PLS_INTEGER;
BEGIN
SELECT salary INTO salariu FROM employees WHERE last_name = v_nume;
RETURN salariu;
EXCEPTION
WHEN NO_DATA_FOUND THEN
outretvalue := 1;
WHEN TOO_MANY_ROWS THEN
SELECT COUNT(1) INTO lv_cnt FROM employees WHERE last_name = v_nume;
INSERT INTO info
( no_lines
) VALUES
( lv_cnt
);
RETURN 2;
WHEN OTHERS THEN
RETURN 3;
END f2;
Oracle saves the compile errors in a table. I use the following query for retrieving the PL/SQL errors in my stored procs/funcs:
SELECT '*** ERROR in ' || TYPE || ' "' || NAME || '", line ' || LINE || ', position ' || POSITION || ': ' || TEXT
FROM SYS.USER_ERRORS
You could try running it and see if it helps identify the error in the function.

pl sql %NOTFOUND

I'm just wondering why this piece of code is not working. I don't have any supplier id=1 in my table.
DECLARE
VAR SUPP_NM VARCHAR(100);
VAR_SUPP_ID NUMBER := 1;
WHILE_VAR CHAR := 'Y';
BEGIN
SELECT SUPP_NM
INTO VAR_SUPP_NM
FROM TEST.SUPPLIER
WHERE SUPP_ID = VAR_SUPP_ID;
IF SQL%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('SQL DATA NOT FOUND');
ELSIF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE('DATA FOUND');
END IF;
END;
I get a 01403 error in Toad but not handled as sql%notfound.
Why isn't the sql%notfound working?
To catch the NO_DATA_FOUND exception rewrite your code as follows by adding exception section:
DECLARE
VAR_SUPP_NM VARCHAR2(100);
VAR_SUPP_ID NUMBER := 1;
WHILE_VAR CHAR := 'Y';
BEGIN
SELECT SUPP_NM
INTO VAR_SUPP_NM
FROM TEST.SUPPLIER
WHERE SUPP_ID = VAR_SUPP_ID;
DBMS_OUTPUT.PUT_LINE('DATA FOUND');
exception
when no_data_found
then DBMS_OUTPUT.PUT_LINE('SQL DATA NOT FOUND');
END;
Checking SQL%FOUND or SQL%NOTFOUND have no meaning in the case of select into statement, because if the select statement returns no rows it will always raise no_data_found exception, except, if that select statement invokes aggregate function, it will always return data or null if no rows has been selected.
Do not use varchar datatype, use varchar2 datatype instead.
Nicholas's answer is what you want if you want to use SELECT INTO. However, if it is more important that you are able to use %FOUND or %NOTFOUND, consider FETCHing from a cursor instead:
DECLARE
VAR SUPP_NM VARCHAR2(100);
VAR_SUPP_ID NUMBER := 1;
WHILE_VAR CHAR := 'Y';
CURSOR c1 IS
SELECT SUPP_NM
FROM TEST.SUPPLIER
WHERE SUPP_ID = VAR_SUPP_ID;
BEGIN
OPEN c1;
FETCH c1 INTO VAR_SUPP_NM;
IF c1%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('SQL DATA NOT FOUND');
ELSIF c1%FOUND THEN
DBMS_OUTPUT.PUT_LINE('DATA FOUND');
END IF;
CLOSE c1;
END;
Nick's answer is correct.
In oracle documentation however it is stated that SQL%NOTFOUND works with SELECT INTO but before one could check SQL%NOTFOUND to be TRUE an error is generated called as no_data_found.
so to use SQL%NOTFOUND one first needs to hande no_data_found error.
DECLARE
VAR SUPP_NM VARCHAR(100);
VAR_SUPP_ID NUMBER := 1;
WHILE_VAR CHAR := 'Y';
BEGIN
BEGIN
SELECT SUPP_NM
INTO VAR_SUPP_NM
FROM TEST.SUPPLIER
WHERE SUPP_ID = VAR_SUPP_ID;
EXCEPTION
WHEN NO_DATA_FOUND THEN
null; -- or write something here if u want.
END;
IF SQL%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('SQL DATA NOT FOUND');
ELSIF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE('DATA FOUND');
END IF;
END;
So what I have done is added a inner BEGIN-END block enclosing the SELECT statement that generates no_data_found exception. After that you can check for the value of SQL%NOTFOUND.
You can read more about this in oracle docs.
Start from this active link in mytime : https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/errors.htm#LNPLS00703

Need inner and outer join in one function

I have one table that holds a record for each customer (main table). I then have a table with additional detail for some customers. The additional detail table sometimes has no records for a record in the main table. Sometimes the detail table has multiple records for a record in the main table & if this is the case I need the most recent record (hence the max subselect).
The trouble is my function only returns values for the few records in the detail table. If I comment out the portion of the function that looks at the detail table and just return the STAT3 value it seems to work. How do I make the second select statment below only apply if there is a result for that query?
create or replace FUNCTION "F_RETURN_STAT" (
N_UNIQUE IN NUMBER)
RETURN VARCHAR2
IS
V_STAT3 varchar2(20);
V_STAT varchar2(20);
V_STAT2 varchar2(20);
D_ACTDATE date;
D_STARTDATE date;
BEGIN
select expire into D_ACTDATE
from main_table a
where a.uniquefield = N_UNIQUE;
IF
D_ACTDATE > SYSDATE
or
D_ACTDATE is null
then
V_STAT :='TRUE';
else
v_STAT :='FALSE';
end if;
select b.startdate into D_STARTDATE
from main_table a, detail_table b
where a.uniquefield= b.main_table_id(+) and
b.main_table_id = N_UNIQUE and
b.uniquefield in
(select max(c.uniquefield) from detail_table c group by main_table_id);
if
D_STARTDATE is not null
then
V_STAT2 :='FALSE';
end if;
if
V_STAT2 ='FALSE'
then
V_STAT3 :='FALSE';
ELSE
V_STAT3 := V_STAT;
end if ;
RETURN(V_STAT3);
end;
I think this version of your function will solve your problem:
CREATE OR REPLACE FUNCTION f_return_stat(n_unique IN NUMBER)
RETURN VARCHAR2 IS
v_stat3 VARCHAR2(20);
v_stat VARCHAR2(20);
v_stat2 VARCHAR2(20);
d_actdate DATE;
d_startdate DATE;
BEGIN
--First Query
SELECT expire
INTO d_actdate
FROM main_table a
WHERE a.uniquefield = n_unique;
IF d_actdate > SYSDATE OR d_actdate IS NULL THEN
v_stat := 'TRUE';
ELSE
v_stat := 'FALSE';
END IF;
BEGIN
--Second Query
SELECT b.startdate
INTO d_startdate
FROM detail_table b
WHERE b.main_table_id = n_unique
AND b.uniquefield IN (SELECT MAX(c.uniquefield)
FROM detail_table c
GROUP BY main_table_id);
EXCEPTION
WHEN NO_DATA_FOUND THEN
d_startdate := NULL;
END;
IF d_startdate IS NOT NULL THEN
v_stat2 := 'FALSE';
END IF;
IF v_stat2 = 'FALSE' THEN
v_stat3 := 'FALSE';
ELSE
v_stat3 := v_stat;
END IF;
RETURN (v_stat3);
END;
In your version of the second query, your join (a.uniquefield= b.main_table_id) and your filter (b.main_table_id = N_UNIQUE) are equivalent, so main_table a can be removed altogether. The only reason to leave it in is to make sure that your query always returns a row. If you use exception handling to catch the NO_DATA_FOUND exception, that need goes away and you can simplify your query to just select from detail_table b.
I believe there could be a more efficient way however this might do the job:
SELECT b.startdate
INTO d_startdate
FROM detail_table b
WHERE b.main_table_id = n_unique
and
(b.uniquefield in
(select max(c.uniquefield) from detail_table c group by main_table_id)
or b.uniquefield is null);

Resources