Function Returning Multiple Values SQL - oracle

I have been trying to return 2 values from a function in PL/SQL . The first value i want it to be the salary of the guy i have to search for. The second i want it to be the number of rows affected by this. I searched google for a while and i found out that i must first make a type so that i can return the data. However i get an error :
Error(9,1): PL/SQL: SQL Statement ignored
Error(9,36): PL/SQL: ORA-00936: missing expression
The code that i have is :
CREATE OR REPLACE TYPE return_type AS OBJECT(val1 NUMBER,val2 NUMBER);
CREATE OR REPLACE FUNCTION f2
(v_nume employees.last_name%TYPE DEFAULT 'Bell')
RETURN return_type IS
out_var return_type;
salariu employees.salary%type;
BEGIN
SELECT salary INTO salariu
FROM employees
WHERE last_name = v_nume;
INSERT INTO out_var values(salariu,##ROWCOUNT);
RETURN out_var;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20000, 'Nu exista angajati cu numele dat');
WHEN TOO_MANY_ROWS THEN
RAISE_APPLICATION_ERROR(-20001, 'Exista mai multi angajati cu numele dat');
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20002,'Alta eroare!');
END f2;
/

I did it this way:
CREATE OR REPLACE FUNCTION f2
(v_nume employees.last_name%TYPE DEFAULT 'Bell',
nr OUT employees.salary%TYPE )
RETURN NUMBER IS
salariu employees.salary%type;
BEGIN
SELECT salary INTO salariu
FROM employees
WHERE last_name = v_nume;
nr := SQL%ROWCOUNT;
RETURN salariu;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20000,
'Nu exista angajati cu numele dat');
WHEN TOO_MANY_ROWS THEN
RAISE_APPLICATION_ERROR(-20001,
'Exista mai multi angajati cu numele dat');
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20002,'Alta eroare!');
END f2;
/

Related

How to return output from stored procedure - Oracle

I have create a PL/SQL stored procedure from which I want to return output and display it.
I want to return the output either the success or failure message into variable : MSG
How do I need to do this any idea
PL/SQL code:
CREATE OR REPLACE PROCEDURE CHECKFILED
(
MY_NAME EMP.FIRSTNAME%TYPE,
MY_ID EMP.ID%TYPE
) RETURN VARCHAR2
IS
V_MSG VARCHAR(4000 CHAR);
BEGIN
SELECT FIRSTNAME INTO MY_NAME FROM EMP WHERE ID=MY_ID;
IF MY_NAME IS NOT NULL THEN
INSERT INTO CUSTOMER VALUES (UID,FIRSTNAME);
V_MSG='FIELD IS NOT EMPTY';
ELSE
RAISE_APPLICATION_ERROR(-20000,'FIELD IS EMPTY');
END IF;
EXCEPTION
WHEN OTHERS THEN
V_MSG := SQLCODE || '-' || SQLERRM;
RETURN(V_MSG);
RETURN(V_MSG);
END;
Calling the stored procedure:
DECLARE
MSG VARCHAR2(4000 CHAR);
BEGIN
SELECT CHECKFILED('10A') AS MSG FROM DUAL;
END;
If it is a procedure, it should have an OUT parameter which will then be used to return some value.
Something like this; note that it is probably useless to check whether select returned null (employees do have names; select would return no row and therefore raise no_data_found exception).
CREATE OR REPLACE PROCEDURE checkfiled (my_id IN emp.id%TYPE,
v_msg OUT VARCHAR2)
IS
my_name emp.firstname%TYPE;
BEGIN
SELECT firstname
INTO my_name
FROM emp
WHERE id = my_id;
INSERT INTO customer
VALUES (UID, firstname);
v_msg := 'FIELD IS NOT EMPTY';
EXCEPTION
WHEN OTHERS
THEN
v_msg := SQLCODE || '-' || SQLERRM;
END;
You'd then call it as
DECLARE
msg VARCHAR2 (4000 CHAR);
BEGIN
checkfiled ('10A', msg);
DBMS_OUTPUT.put_line ('Procedure returned ' || msg);
END;
/
PROCEDUREs do not have any return value. If you want to return a value, it needs to be a FUNCTION. Try the code below.
CREATE OR REPLACE FUNCTION CHECKFILED (MY_NAME EMP.FIRSTNAME%TYPE, MY_ID EMP.ID%TYPE)
RETURN VARCHAR2
IS
V_MSG VARCHAR (4000 CHAR);
BEGIN
SELECT FIRSTNAME
INTO MY_NAME
FROM EMP
WHERE ID = MY_ID;
IF MY_NAME IS NOT NULL
THEN
INSERT INTO CUSTOMER
VALUES (UID, FIRSTNAME);
V_MSG := 'FIELD IS NOT EMPTY';
ELSE
RAISE_APPLICATION_ERROR (-20000, 'FIELD IS EMPTY');
END IF;
RETURN (V_MSG);
EXCEPTION
WHEN OTHERS
THEN
V_MSG := SQLCODE || '-' || SQLERRM;
RETURN (V_MSG);
END;

What considerations Do You have about my PLSQL package?

I had a little doubts about my code but yesterday I finally understood some points about to how to start coding my final package project. I share this code with the purpose if You want suggest me some change to perform or anything about my package I will appreciate you.
CREATE OR REPLACE PACKAGE BODY emp_upd_pkg IS
-- Function to update commission of employee --
FUNCTION comm_upd(
p_empid employees.employee_id%TYPE)
RETURN employees.commission_pct%TYPE
IS
v_oldcomm employees.commission_pct%TYPE;
v_newcomm employees.commission_pct%TYPE;
BEGIN
-- Valid parameter --
SELECT commission_pct
INTO v_oldcomm
FROM employees
WHERE employee_id = p_empid;
IF
v_oldcomm IS NOT NULL THEN
UPDATE employees
SET commission_pct = commission_pct * 1.1
WHERE employee_id = p_empid
RETURNING commission_pct
INTO v_newcomm;
RETURN v_newcomm;
ELSE
/*UPDATE employees
SET commission_pct = 0.1
WHERE employee_id = p_empid
RETURNING commission_pct
INTO v_newcomm;
RETURN v_newcomm;*/
RETURN (0);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN (0);
END comm_upd;
-- Function to update salary of employee --
FUNCTION sal_upd(
p_empid employees.employee_id%TYPE)
RETURN employees.salary%TYPE
IS
v_oldsal employees.salary%TYPE;
v_newsal employees.salary%TYPE;
BEGIN
-- Valid parameter --
SELECT salary
INTO v_oldsal
FROM employees
WHERE employee_id = p_empid;
IF
v_oldsal IS NOT NULL THEN
UPDATE employees
SET salary = salary + 100
WHERE employee_id = p_empid
RETURNING salary
INTO v_newsal;
RETURN v_newsal;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN (0);
END sal_upd;
-- Procedure to update comm and sal using package functions --
PROCEDURE commsal_upd(
p_empid employees.employee_id%TYPE)
IS
v_newcomm employees.commission_pct%TYPE;
v_newsal employees.salary%TYPE;
BEGIN
-- Call package functions to update sal and comm of all employees --
DBMS_OUTPUT.PUT_LINE(comm_upd(p_empid));
DBMS_OUTPUT.PUT_LINE(sal_upd(p_empid));
-- Query for final inform --
SELECT commission_pct, salary
INTO v_newcomm, v_newsal
FROM employees
WHERE employee_id = p_empid;
DBMS_OUTPUT.PUT_LINE('THE NEW COMMISSION FOR EMPLOYEE' || p_empid ||
' IS ' || v_newcomm || ' AND THE NEW SALARY IS ' || v_newsal);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('NO EXISTE EMPLEADO INGRESADO');
END commsal_upd;
END emp_upd_pkg;
Also, I have a little question: When I do use of a function within a procedure, Can I restringe the "RETURN" sentence of the function with the propuse of only send to call Procedure information?
SET SERVEROUTPUT ON
DECLARE
CURSOR cur_empid IS
SELECT employee_id
FROM employees;
TYPE empid_rec IS RECORD(
p_empid employees.employee_id%TYPE);
empid empid_rec;
BEGIN
FOR empid IN cur_empid LOOP
emp_upd_pkg.commsal_upd(empid.employee_id);
EXIT WHEN cur_empid%NOTFOUND;
END LOOP;
END;
/
When I use a simple record to update all employees I receive in console information about RETURN info of functions and info about DBMS... of procedure. Can I change my code to receive only Procedure information on console? Thanks!.
0
24100
THE NEW COMMISSION FOR EMPLOYEE100 IS AND THE NEW SALARY IS 24100
0
17100
THE NEW COMMISSION FOR EMPLOYEE101 IS AND THE NEW SALARY IS 17100
0
17100
THE NEW COMMISSION FOR EMPLOYEE102 IS AND THE NEW SALARY IS 17100
0
9100

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.

Exact fetch returns more than requested number of rows

I'm trying to create a procedure to pass in the customer number and return the number of purchases and total value for purchases within the last year. If there are no purchases, return zero for number and total and also return the number of contacts the salesmen had with that customer in the last year.
I'm calling it as follows:
DECLARE
a_Var NUMBER;
b_Var NUMBER;
C_Var NUMBER;
D_Var NUMBER;
BEGIN
three_pr(001116,a_Var, b_Var);
IF a_Var > 0 THEN
DBMS_OUTPUT.PUT_LINE('the number of purchases :' || a_Var);
DBMS_OUTPUT.PUT_LINE('the total value of purchase :' || b_Var);
ELSE
SELECT ContactID,Count(contactID)
INTO
C_Var,D_Var
FROM DD_Contacts
WHERE DateofContact between to_date ('2012/01/01', 'yyyy/mm/dd')
AND to_date ('2012/12/31', 'yyyy/mm/dd')
Group By ContactID;
DBMS_OUTPUT.PUT_LINE (C_Var||D_Var);
END IF;
END;
/
When using the above code I get the error:
ORA-01422: exact fetch returns more than requested number of rows ORA-06512: at line 16
and here's the procedure:
CREATE or REPLACE PROCEDURE three_pr
(par_CustomerID IN NUMBER, par_sumpurchase OUT Number,par_totalvalue OUT Number)
IS
BEGIN
SELECT
COUNT(O.OrderID),SUM(Price*Quantity)
INTO par_sumpurchase,par_totalvalue
FROM DD_Orders O JOIN DD_OrderLine OL ON O.OrderID = OL.OrderID
WHERE DatePurchase between to_date ('2012/01/01', 'yyyy/mm/dd')
AND to_date ('2012/12/31', 'yyyy/mm/dd')
AND CustomerID = par_CustomerID;
END;
/
The procedure three_pr does not correctly handle the NO_DATA_FOUND exception when the query does not return results.
CREATE or REPLACE PROCEDURE three_pr
(par_CustomerID IN NUMBER, par_sumpurchase OUT Number,par_totalvalue OUT Number)
IS
BEGIN
BEGIN
SELECT
COUNT(O.OrderID),SUM(Price*Quantity)
INTO par_sumpurchase,par_totalvalue
FROM DD_Orders O JOIN DD_OrderLine OL ON O.OrderID = OL.OrderID
WHERE DatePurchase between to_date ('2012/01/01', 'yyyy/mm/dd')
AND to_date ('2012/12/31', 'yyyy/mm/dd')
AND CustomerID = par_CustomerID;
EXCEPTION
WHEN NO_DATA_FOUND THEN
par_sumpurchase := 0;
par_totalvalue := 0;
END;
END;
/

Is it possible to use sql%rowcount for SELECT?

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.

Resources