PL/SQL - The function deosn't return multiple rows - sql

I wanted to select 10 highly paid employees from the "employees" table, but the function returnes only 1 row. How to get multiple rows in this case? My subquery for selecting employees works well, but when I call function it returns 1 row.
This is my code:
CREATE OR REPLACE FUNCTION f_sal
RETURN Varchar2
IS cursor c_emp is (select first_name, last_name from (select first_name, last_name, row_number()
over(order by salary desc) as ranking from employees) where ranking <= 10);
v_first employees.first_name%type;
v_last employees.last_name%type;
begin
open c_emp;
fetch c_emp into v_first, v_last;
close c_emp;
return v_first || ' ' || v_last;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
dbms_output.put_line('Error');
when others then dbms_output.put_line('Other Error');
END;
select f_sal from dual;

Option 1: Use a collection
CREATE FUNCTION f_sal
RETURN SYS.ODCIVARCHAR2LIST
IS
v_names SYS.ODCIVARCHAR2LIST;
BEGIN
SELECT first_name || ' ' || last_name
BULK COLLECT INTO v_names
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
return v_names;
END;
/
Then:
SELECT * FROM TABLE(f_sal);
Option 2: Use a pipelined function and iterate over a cursor into a collection
CREATE OR REPLACE FUNCTION f_sal
RETURN SYS.ODCIVARCHAR2LIST PIPELINED
IS
BEGIN
FOR n IN (
SELECT first_name || ' ' || last_name AS name
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY
)
LOOP
PIPE ROW (n.name);
END LOOP;
END;
/
Then:
SELECT * FROM TABLE(f_sal);
Option 3: Return a cursor
CREATE FUNCTION f_sal
RETURN SYS_REFCURSOR
IS
v_names SYS_REFCURSOR;
BEGIN
OPEN v_names FOR
SELECT first_name || ' ' || last_name AS name
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
return v_names;
END;
/
Then:
DECLARE
v_names SYS_REFCURSOR := f_sal();
v_name VARCHAR2(100);
BEGIN
LOOP
FETCH v_names INTO v_name;
EXIT WHEN v_names%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_name );
END LOOP;
END;
/
db<>fiddle here

Related

Insert data with SELECT into Index by Table PL/SQL

Well I have a INDEX BY TABLE which looks like this:
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
And with this Loop I want to fill it:
FOR l IN 1..10
LOOP
deptno := deptno + 10;
SELECT department_id, department_name INTO my_dept_table FROM departments WHERE department_id=deptno;
END LOOP;
Is there a possible way to do that because with my solution it's not working...
Thanks in advice
You can either read each department into the appropriate element of the collection:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
DEPTNO NUMBER;
BEGIN
FOR l IN 1..10
LOOP
deptno := l + 10;
SELECT department_name
INTO my_dept_table(l)
FROM departments
WHERE department_id = deptno;
END LOOP;
FOR I IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
works fine.
But better yet, you can BULK COLLECT the data into your collection, which saves a bunch of code:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
DEPTNO NUMBER;
BEGIN
SELECT DEPARTMENT_NAME
BULK COLLECT INTO MY_DEPT_TABLE
FROM DEPARTMENTS;
FOR I IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
db<>fiddle here
EDIT
OP has indicated that he wants the department number to be used as the index to the collection. In that case I'd use a cursor and add each element separately:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
BEGIN
FOR aRow IN (SELECT DEPARTMENT_ID, DEPARTMENT_NAME
FROM DEPARTMENTS
ORDER BY DEPARTMENT_ID)
LOOP
MY_DEPT_TABLE(aRow.DEPARTMENT_ID) := aRow.DEPARTMENT_NAME;
END LOOP;
FOR I IN MY_DEPT_TABLE.FIRST..MY_DEPT_TABLE.LAST LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
db<>fiddle here

How to resolve the missing or invalid option in oracle database?

SET SERVEROUTPUT on
declare
type emp_record is record
(v_empid emp1.empno % type;
v_ename emp1.ename % type; )
emp_rec emp_record;
v_sal emp1.sal%type;
begin
v_sal:=:v_sal;
select empno,ename
into emp_rec
from emp1
where sal:=v_sal;
dbms_output.put_line('The employee whose salary is ' || v_sal || 'has Employee Id ' || emp_rec.v_empid || 'and his name is ' || emp_rec.v_ename);
end;
The error is :
ORA-00922: missing or invalid option
There are several issues within the code. Convert to the one as below:
declare
type emp_record is record
(v_empid emp1.empno % type,
v_ename emp1.ename % type );
emp_rec emp_record;
v_sal emp1.sal%type:=&i_sal;
begin
select empno,ename
into emp_rec
from emp1
where sal = v_sal;
dbms_output.put_line('The employee whose salary is ' || v_sal || 'has Employee Id '
|| emp_rec.v_empid || 'and his name is ' || emp_rec.v_ename);
end;
/
Whenever it prompts for the i_sal, you'll input the desired salary parameter value.
Demo
If you have multiple records to be manipulated as #APC mentioned, such as more than one people has the same salary,you'll need such a loop statement :
declare
type emp_record is record
(v_empid emp1.empno % type,
v_ename emp1.ename % type,
v_sal emp1.sal % type);
emp_rec emp_record;
v_sal emp1.sal%type:=&i_sal;
cursor c_emp is
select empno,ename,sal
into emp_rec
from emp1
where sal = v_sal;
begin
open c_emp;
loop
fetch c_emp into emp_rec;
exit when c_emp%notfound;
dbms_output.put_line('The employee whose salary is ' || emp_rec.v_sal || ' has Employee Id '
|| emp_rec.v_empid || ' and his name is ' || emp_rec.v_ename);
end loop;
close c_emp;
end;
/
Demo
The simplest method to fix that particular problem is to use aggregation:
select max(empno), max(ename)
into emp_rec
from emp1
where sal = v_sal;
An aggregation query with no group by always returns exactly one row.
That said, the more correct method is to handle the exception that is generated by not having a matching value. You will also need to handle exceptions for duplicate values as well.

Fetching cursor data into array in PL/SQL

I have a problem at one exercise: I have a table with an ussualy column, and another column which is another table, like this:
CREATE OR REPLACE TYPE list_firstnames AS TABLE OF VARCHAR2(10);
then I create the following table:
CREATE TABLE persons (last_name VARCHAR2(10), first_name list_firstnames)
NESTED TABLE first_name STORE AS lista;
I insert:
INSERT INTO persons VALUES('Stewart', list_firstnames('John', 'Jack'));
INSERT INTO persons VALUES('Bauer', list_firstnames('Helen', 'Audrey'));
INSERT INTO persons VALUES('Obrian', list_firstnames('Mike', 'Logan'));
I want to make a cursor to take all last and first names from persons, and then I want to put all of them in an array. After this, I want to count the first names which contains the 'n' letter.
First of all, I want to know how I can put all the information from the cursor in an array. I try this:
DECLARE
CURSOR firstnames_students IS
select last_name,COLUMN_VALUE as "FIRSTNAME" from persons p, TABLE(p.last_name) p2;
v_lastname VARCHAR(50);
v_firstname VARCHAR(50);
v_I NUMBER := 1;
v_count NUMBER := 0;
v_contor INTEGER := 0;
TYPE MyTab IS TABLE OF persons%ROWTYPE INDEX BY VARCHAR2(20);
std MyTab;
BEGIN
OPEN firstnames_students;
LOOP
FETCH firstnames_students INTO v_lastname, v_firstname;
EXIT WHEN firstnames_students%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_lastname || ' ' || v_firstname);
END LOOP;
--select * INTO std FROM firstnames_students;
CLOSE firstnames_students;
END;
SET SERVEROUTPUT ON;
DECLARE
CURSOR students IS
SELECT * FROM persons;
v_row PERSONS%ROWTYPE;
v_names SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
v_count INT := 0;
BEGIN
OPEN students;
LOOP
FETCH students INTO v_row;
EXIT WHEN students%NOTFOUND;
v_names.EXTEND;
v_names(v_names.COUNT) := v_row.last_name;
FOR i IN 1 .. v_row.first_name.COUNT LOOP
v_names.EXTEND;
v_names(v_names.COUNT) := v_row.first_name(i);
END LOOP;
END LOOP;
CLOSE students;
FOR i IN 1 .. v_names.COUNT LOOP
IF INSTR( v_names(i), 'n' ) > 0 THEN
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE( v_names(i) );
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE( v_count );
END;
Output:
John
Helen
Obrian
Logan
4

select statement not working in procedure

create table Employee(Id number,
Name varchar(20),
Age number,
DId number,
Salary number,
primary key(Id),
foreign key(DId) references Department on delete cascade);
declare
total number;
procedure myFunction(x in number) is
begin
insert into Employee values(17,'Jaskaran Singh',31,1,200000);
dbms_output.put_line('successfully executed');
select * from Employee;
end;
begin
myFunction(3);
end;
To return data from stored procedure, you should create a cursor and return that select like the following:
CREATE OR REPLACE PACKAGE TYPES
AS
TYPE DATA_CURSOR IS REF CURSOR;
END;
then in your code is going to be like:
CREATE OR REPLACE PROCEDURE MYPROC(RESULTSET OUT TYPES.DATA_CURSOR) AS
BEGIN
INSERT INTO EMPLOYEE VALUES(17,'Jaskaran Singh',31,1,200000);
DBMS_OUTPUT.PUT_LINE('successfully executed');
OPEN RESULTSET FOR
SELECT * FROM EMPLOYEE;
END;
The Execution part like
DECLARE THE_RESULT_SET OUT TYPES.DATA_CURSOR;
BEGIN
MYPROC(3, THE_RESULT_SET);
-- You can now get the THE_RESULT_SET and take the result from it...
END;
Important: if you want to print as I understand the other case, you can get that result (same code), and loop whatever you want and print the result from the THE_RESULT_SET
If you want to print what's in the EMPLOYEES table you have loop a cursor over the EMPLOYEES table, printing each row appropriately. Here's an example:
DECLARE
TOTAL NUMBER;
PROCEDURE MYFUNCTION(X IN NUMBER) IS
BEGIN
INSERT INTO EMPLOYEE VALUES(17,'Jaskaran Singh',31,1,200000);
DBMS_OUTPUT.PUT_LINE('successfully executed');
FOR aRow IN (SELECT * FROM EMPLOYEE) LOOP
DBMS_OUTPUT.PUT_LINE('ID=' || aRow.ID ||
' NAME=''' || aRow.NAME || '''' ||
' AGE=' || aRow.AGE ||
' DID=' || aRow.DID ||
' SALARY=' || aRow.SALARY);
END LOOP;
END;
BEGIN
MYFUNCTION(3);
END;
Share and enjoy.

Any alternatives to using cursor in SQL procedure in Oracle 10g?

I give the SQL few inputs and I need to get all the ID's and their count that doesn't satisfy the required criteria.
I would like to know if there are there any alternatives to using cursor.
DECLARE
v_count INTEGER;
v_output VARCHAR2 (1000);
pc table1%ROWTYPE;
unmarked_ids EXCEPTION;
dynamic_sql VARCHAR (5000);
cur SYS_REFCURSOR;
id pp.id%TYPE;
pos INTEGER;
BEGIN
v_count := 0;
SELECT *
INTO pc
FROM table1
WHERE id = '&ID';
DBMS_OUTPUT.ENABLE;
dynamic_sql :=
'SELECT ID from pp
WHERE ( TO_CHAR(cdate, ''yyyy/mm/dd'') =
TO_CHAR (:a, ''yyyy/mm/dd''))
AND aid IN (SELECT aid FROM ppd WHERE TO_CHAR(cdate, ''yyyy/mm/dd'') =
TO_CHAR (:b, ''yyyy/mm/dd'')
AND cid = :c )
AND cid <> :d';
OPEN cur FOR dynamic_sql USING pc.cdate, pc.cdate, pc.id, pc.id;
LOOP
FETCH cur INTO id;
EXIT WHEN cur%NOTFOUND;
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE (' Id:' || id);
END LOOP;
CLOSE cur;
IF (v_count > 0)
THEN
DBMS_OUTPUT.PUT_LINE ( 'Count: ' || v_count || ' SQL: ' || dynamic_sql);
RAISE unmarked_ids;
END IF;
DBMS_OUTPUT.PUT_LINE('SQL ended successfully');
EXCEPTION
WHEN unmarked_ids
THEN
DBMS_OUTPUT.put_line (
'Found ID's that not marked with the current id.');
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line (
'No data found in table1 with the current id ' || '&ID');
END;
There are bind variables in the query. One of them is date, there are three more.
The count and ID's are required to be shown which will later be reported.
You could store the rowid in a temporary table along with an index value (0...n) and then use a while loop to go through the index values and join to the real table using the rowid.