PL/SQL: ORA-00947: not enough values - oracle

Im creating a procedure to display the n number of maximum and minimum salary for an employee. If i ll give 5 as input, the query will get me 5 maximum and minimum salary for an employee.
For the above scenario, I have created an object with two columns like below
create type vrec as object(
empno number,
sal number
);
/
Then i created nested table with the help of object type, so that i can use the nested table as out parameter to return all the rows at one short.
create type vrec_type is table of vrec;
/
After the data type creation, im creating a procedure like below
create or replace procedure pro_first_last(input in number,salary out vrec_type)
is
begin
select empno,sal BULK COLLECT INTO salary from (
select empno,sal from
(select empno,sal,rank() over(order by sal asc) min_sal from emp5 where sal is not null) where min_sal <= 5
union all
select empno,sal from
(select empno,sal,rank() over(order by sal desc) max_sal from emp5 where sal is not null) where max_sal <= 5);
for i in salary.first..salary.last
loop
dbms_output.put_line(salary(i).empno);
end loop;
end;
/
When i compiling the above procedure, im getting not enough values. I have also created the object with two columns and in select statement also im returning only two column. Could someone review and help me on this or provide some alternate solution.

You are directly adding empno, sal values into salary (vrec_type object, which can take the values of only object type vrec)
You need to create the object of vrec and then add it into salary as following:
create or replace procedure pro_first_last(input in number,salary out vrec_type)
is
begin
select vrec(empno,sal) -- change in this line
BULK COLLECT INTO salary from (
select empno,sal from
(select empno,sal,rank() over(order by sal asc) min_sal from emp5 where sal is not null) where min_sal <= 5
union all
select empno,sal from
(select empno,sal,rank() over(order by sal desc) max_sal from emp5 where sal is not null) where max_sal <= 5);
for i in salary.first..salary.last
loop
dbms_output.put_line(salary(i).empno);
end loop;
end;
Cheers!!

Related

Not enough values while compiling procedure

Im trying to get the compile the below program and im getting not enough values error. This program will give the first 5 maximum and mininum salary holding by employees.
select * from all_tables where table_name like 'EMP5%';
create or replace package ptest1 is
Type rec is record (emin number,salmin number,min_salary number,emax number,salmax number,max_salary number);
TYPE T71 is table of rec;
procedure min_max(inp number, oup out T71);
end;
/
create or replace package body ptest1 as
procedure min_max(inp number, oup out T71)
as
begin
select emin,salmin,min_salary,emax,salmax,max_salary
into oup
from
(select * from
(select empno emin,sal salmin,row_number() over(order by sal asc) min_salary from emp5 where sal is not null) where min_salary <= inp ) t1,
(select * from
(select empno emax,sal salmax,row_number() over(order by sal desc) max_salary from emp5 where sal is not null) where max_salary <= inp) t2
where t1.min_salary = t2.max_salary;
for i in oup.first..oup.last
loop
dbms_output.put_line('Employee number '||oup(i).emin);
end loop;
end;
end ptest1;
/

picking 1 column from 2 tables and comparing them

In my Oracle database there are two tables which are TEMP_HR and PAY_SLIP_APR_16. Both of them have a common column named EMP_ID. TEMP_HR has over 10,000 records and PAY_SLIP_APR_16 has around 6,000 records. I want to know how many EMP_ID of PAY_SLIP_APR_16 is matched with TEMP_HR. If any ID doesn't match then print it. And here is my simple approach but I think its a very bad approach. So any faster method?
DECLARE
INPUT_EMP_NO VARCHAR2(13 BYTE);
INPUT_EMP_ID VARCHAR2(13 BYTE);
ROW_COUNT_1 NUMBER(6,0);
ROW_COUNT_2 NUMBER(6,0);
MATCHED_ID NUMBER;
UNMATCHED_ID NUMBER;
BEGIN
ROW_COUNT_1:=0;
ROW_COUNT_2:=0;
MATCHED_ID:=0;
UNMATCHED_ID:=0;
SELECT COUNT(*) INTO ROW_COUNT_1 FROM PAY_SLIP_APR_16;
SELECT COUNT(*) INTO ROW_COUNT_2 FROM TEMP_HR;
FOR A IN 1..ROW_COUNT_1 LOOP
BEGIN
SELECT EMP_ID INTO INPUT_EMP_ID FROM (SELECT EMP_ID, ROWNUM AS RN FROM PAY_SLIP_APR_16) WHERE RN=A;
FOR B IN 1..ROW_COUNT_2 LOOP
SELECT EMP_NO INTO INPUT_EMP_NO FROM (SELECT EMP_NO, ROWNUM AS RON FROM TEMP_HR) WHERE RON=B;
IF(INPUT_EMP_ID=INPUT_EMP_NO)THEN
MATCHED_ID:=MATCHED_ID+1;
EXIT;
ELSE
CONTINUE;
END IF;
END LOOP;
UNMATCHED_ID:=UNMATCHED_ID+1;
DBMS_OUTPUT.PUT_LINE(INPUT_EMP_ID);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(INPUT_EMP_ID||' -> '||SQLERRM);
END;
END LOOP;
DBMS_OUTPUT.PUT_LINE('MATCHED -> '||MATCHED_ID);
DBMS_OUTPUT.PUT_LINE('UNMATCHED -> '||UNMATCHED_ID);
END;
Use an outer join filtering for missed joins:
select p.*
from PAY_SLIP_APR_16 p
left join TEMP_HR t on t.EMP_ID = p.EMP_ID
where t.EMP_ID is null
An index on TEMP_HR(EMP_ID) will make this query fly.
You should use SQL sets!
To check which EMP_ID isn't in TEMP_HR you may try this:
select EMP_ID FROM PAY_SLIP_APR_16
where EMP_ID not in (select EMP_NO from TEMP_HR);
Then the other way around:
select EMP_NO FROM TEMP_HR
where EMP_NO not in (select EMP_ID from PAY_SLIP_APR_16);

PL\SQL Hot do I convert select value to bit flag?

I would like to have pl-sql like the following:
SELECT ID FROM some-table WHERE
(SELECT MAX(some-expression) FROM another-table = 1) OR
(some-table.ID IN SELECT (SELECT ID FROM one-more-table))
This pseudo-query would select all IDs from some-table if maximum value of some-expression equals 1 (or filter IDs by one-more-table values otherwise)
How do I properly implement this in PL-SQL ?
Thank you in advance!
Please find the below pl-sql:
DECLARE
v_Column1 NUMBER(4);
v_deptno NUMBER(2);
BEGIN
SELECT MAX(A.DEPTNO)
INTO v_deptno
FROM (SELECT DEPTNO FROM DEPT) A;
SELECT A.EMPNO
INTO v_Column1
FROM EMP A
WHERE A.DEPTNO=v_deptno
OR
A.EMPNO IN ( SELECT EMPNO FROM EMP_2);
END;
/

How to use sum() function inside stored procedure in oracle?

The example below works fine and it return some rows. But I need summary of the rows.
DECLARE
x number;
Cursor c1 is
select sal,deptno from emp;
rw c1%rowtype;
BEGIN
x:=0;
open c1;
LOOP
fetch c1 into rw;
FOR i IN 1..rw.deptno LOOP
x:=x+rw.sal;
end loop;
exit when c1%notfound;
DBMS_OUTPUT.PUT_LINE(x);
END LOOP;
close c1;
END;
/
Suppose you have three employees and every employee's has different salary. The salary has due for 10 months and 20 months and 30 months. The salary is due for long time. So you want to add 2% bonus amount with salary for every month as the way:
The below description is for single Employee for 10 months:
Month-1 Salary = 800 => 800*2% = 16.00 => Total = 800+16 =816
Month-2 Salary = 816 => 816*2% = 16.32 => Total = 816+16.32 =832.32
............................................................................
Month-10 Salary = 956.07 => 956.07*% = 19.12 => Total = 956.07+19.12 =975.20
The Months-1 Total Salary=816. So Month-2 Salary=816. This will continue up 10 months.Every Employee has the same condition. So I need summary of the total column. Thanks and best regards.
When you use aggregate function SUM in your query (unlike, when you adding yourself), you don't need to convert NULL. SUM takes care of it. Although, as #DavidAldridge pointed, if you expect that all rows in summarized group of records may contain NULL, your sum will also be NULL. If you want to return a value, you can wrap your sum as follows coalesce(sum(sal),0)
This will give you SUM of all salaries
select SUM(sal) TotalSal from emp;
This will give you SUM by department
select SUM(sal) TotalDeptSal, deptno
from emp
group by deptno;
In you question you posted that you need to execute it in stored procedure while your code as an anonymous block. If you want to return single value from Stored procedure you have a choice to declare function with return parameter or stored procedure with output parameter. To return a recordset from stored procedure in Oracle you need to declare a refcursor output parameter
CREATE OR REPLACE PROCEDURE Get_TotalSal_ByDept (
p_recordset OUT SYS_REFCURSOR) AS
BEGIN
OPEN p_recordset FOR
select SUM(sal) TotalDeptSal, deptno
from emp
group by deptno;
END;
Edit
I see that you added row - total. It is not changing much from the original question. Still, using cursor is not needed. You can run 2 queries and return 2 output parameters, one with data by department and another is total.
CREATE OR REPLACE PROCEDURE Get_SalByDept_WithTotal (
p_total OUT NUMBER,
p_recordset OUT SYS_REFCURSOR) AS
BEGIN
select SUM(sal) INTO p_total from emp;
OPEN p_recordset FOR
select SUM(sal) TotalDeptSal, deptno
from emp
group by deptno;
END;
Is this what you looking for? The Running totals?
SELECT totals.deptNo, totals.depttotal, SUM(totals.depttotal) OVER (ORDER BY totals.id)
FROM (
select deptNo, deptTotal, rownum id
from (
select deptNo, sum(sal * deptNo) deptTotal
from emp
group by deptNo)
) totals
ORDER BY totals.id;
If you have some sort of department Id you can use that instead of artificially generated one from ROWNUM

In Oracle PL/SQL, how do i iterate/access a collection containing the result of LISTAGG function

Assuming table EMP has three columns (deptno, ename and salary).
How would i write a for loop that would look like this and be able to access the items in the LISTAGG function
BEGIN
FOR rec IN (SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM emp
WHERE deptNo = 'XYZ'
GROUP BY deptno)
LOOP
Update EMP
set salary = 10000
WHERE ename in (THE ITEMS IN THE LISTAGG)
FOR EACH (ITEM RETURNED IN THE LISTAGG)
DO SOMETHING
END;
END LOOP;
END;
Basically i want to know two things,
How can i refer to all the items in returned by the listagg function (See UPDATE statement inside for loop)
How can i iterate through each of the items returned by teh listagg function. See INNER FOR LOOP.
Would the syntax for the above also work for WM_CONCAT and COLLECT functions.
Thanks in advance.
Using PL/SQL ListAGG Function Results in a Procedural Loop
Here is a sample application of the OP's request:
BEGIN
FOR rec IN (SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM emp
WHERE deptNo = 'XYZ'
GROUP BY deptno)
LOOP
Update EMP
set salary = 10000
WHERE ename in
( select regexp_substr(rec.employees,'[^,]+',1,level)
from dual
connect by level <= length(regexp_replace(rec.employees,'[^,]+')) + 1
)
AND deptNo=rec.deptNo; --not to miss this filter condition
FOR EACH (ITEM RETURNED IN THE LISTAGG)
DO SOMETHING
END;
END LOOP;
END;
To see another working example of parsing a delimited string list, see Stack Overflow for a similar problem involving a delimited string parsing routine.

Resources