1

I am trying to filter by employee. If I pass single employee it works perfectly but when i pass multiple emp it doesn't work. I am using CTE for multiple emp as a comma separated string. I have attached sample code below- any help will be highly appreciated. I tried to use upper(select ALL_EMP from dept_emp) and I got single-row subquery returns more than one row- how to make it work for more than one employee?

//full code here

Create or replace procedure Emp_Test(

v_empl IN VARCHAR2

)
AS

OPEN  emp_recordset for

with dept_emp as
(select REGEXP_SUBSTR(v_empl, '[^,]+',+1,LEVEL) as ALL_EMP
FROM DUAL
CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(v_empl, '[^,]+'))+1)

select r.name, r.city, e.manager, t.cross_dept, t.dtpt_num
from Employee r
join Dept t on r.emp_id = t.emp_id

where 
(v_empl is null or 
(case when r.emp_status = 'A' and t.cross_dept = 'DEV'
then emp.get_salary(r.v_empl)
else r.v_empl end) = upper(v_empl)
order by r.emp_id;
end  Emp_Test;
//procedure

v_empl IN VARCHAR2

--------
--------
with dept_emp as
(select REGEXP_SUBSTR(v_empl, '[^,]+',+1,LEVEL) as ALL_EMP
FROM DUAL
CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(v_empl, '[^,]+'))+1)

-------- employee r //tbl
--------dept t
//this is working only for single emp, need to work for multiple emp which are  in dept_emp CTE

//how can I make use of dept_emp so it can filter for multiple emp

where 
(v_empl is null or 
(case when r.emp_status = 'A' and t.cross_dept = 'DEV'
then emp.get_salary(r.v_empl)
else r.v_empl end) = upper(v_empl)

also tried this upper(select ALL_EMP from dept_emp) but I am getting single-row subquery returns more than one row error message
user13079741
  • 122
  • 1
  • 10
  • What is not working ? your query will work if you pass multiple employee like '100,200'. so which part and where it is not working ? I hope you know the CTE will give multiple rows in case you pass `100,200` ? – Sujitmohanty30 Aug 28 '20 at 14:23
  • @Sujitmohanty30 CTE is fine but the case statement is not working properly. if I pass CTE like this it gives me error - single roe subquery returns more than one row: case stmt...=upper(select ALL_EMP from dept_emp)). if you see in code snippet its upper(v_empl) is taking single emp, I need to modify this so it can process multiple emp – user13079741 Aug 28 '20 at 14:42
  • May i know what exactly will be the case statement in case of multiple results from CTE ? I understood its not working and the error is expected but I want to understand what you want to do with the data – Sujitmohanty30 Aug 28 '20 at 14:44
  • @Sujitmohanty30 I have updated Case stmnt in code snippet. – user13079741 Aug 28 '20 at 14:53
  • Is there any restriction to use cursor ? because you can loop through the CTE and do some actions for each employee. – Sujitmohanty30 Aug 28 '20 at 14:58
  • @Sujitmohanty30 no restrictions, do you have any code sample I can look into - I will greatly appreciate. the case statement is in where clause – user13079741 Aug 28 '20 at 15:06
  • OK could you put the full code to any fiddle link (https://dbfiddle.uk/?rdbms=oracle_18) if you can , so that i can see and edit accordingly. It difficult to imagine from the piece of where clause you have given. – Sujitmohanty30 Aug 28 '20 at 15:12
  • @Sujitmohanty30 somehow I could not post here (dbfiddle.uk/?rdbms=oracle_18). I have added full code sample in above code snippet. Thank you – user13079741 Aug 28 '20 at 15:33
  • if empl is a comma separated 'list' of 1 to N employees, it should just be applied to an "where empl_id_column in (list)" clause of a dynamic query, and then 'order by' and prior row state variables should suffice to process the output rows. Excessive procedural thinking ruins many simple SQL set logic tasks. – David G. Pickett Aug 28 '20 at 15:54
  • @DavidG.Pickett do you have code sample? – user13079741 Aug 28 '20 at 15:56
  • It might not be allowed in a stored procedure as it might be considered a dynamic query. Separating all the ids from the list and running the remainder for each id is another tactic. However, the stored procedure would then have multiple headers, so you can insert the results from the loop in a #temp table and select that at the end. OOPS, Oracle not sybase/sql server. Well, the oracle equivalent: https://www.oracletutorial.com/oracle-basics/oracle-private-temporary-table/ – David G. Pickett Aug 28 '20 at 16:14
  • My last question what is `emp.get_salary(r.v_empl) ` ? I don't see any `emp` reference any where apart from ths – Sujitmohanty30 Aug 28 '20 at 16:37
  • @Sujitmohanty30 that's a function which gives salary based on supplied emp. I did some debugging the main issue is upper(v_empl) as this is not taking multiple empl – user13079741 Aug 28 '20 at 16:40
  • If it's gives salary then you are trying to match with upper(v_empl). How the salary will match with one emp id? – Sujitmohanty30 Aug 28 '20 at 16:44
  • V_empl is name so once case stmt is excuted it checks in upper(v_empl) – user10806781 Aug 28 '20 at 16:51
  • (case when r.emp_status = 'A' and t.cross_dept = 'DEV' then **`emp.get_salary(r.v_empl)`** else **`r.v_empl end`)** = **`upper(v_empl)`** ..when first condition of the case statement satisfies the final comparison will be like `emp.get_salary(r.v_empl)` = `upper(v_empl)` . How I don't understand it will work even for a single employee ? – Sujitmohanty30 Aug 28 '20 at 17:02
  • Your approach is full of syntax and logic errors and probably way too complicated. What are you actually trying to do? – dnoeth Aug 29 '20 at 20:06

2 Answers2

1

I think, if I understood the question correctly, you want to evaluate the input parameter of the procedure either as a single value or a collection of them. You can't compare a single value in a table retrieved by a select statement against a collection of values. That will never work.

In that case, you might try something like this

Test Case

SQL> create table emp ( emp_id number , name varchar2(100) , manager varchar2(100) , emp_status varchar2(1) ) ;

Table created.

SQL> insert into emp values ( 1 , 'John' , 'Bill' , 'A' ) ;

SQL> insert into emp values ( 2 , 'Mike' , 'Bill' , 'A' ) ;

SQL> insert into emp values ( 3 , 'Sara' , 'Bill' , 'A' ) ;

SQL> insert into emp values ( 4 , 'Dany' , 'Bill' , 'A' ) ;

SQL> insert into emp values ( 5 , 'Mila' , 'Anne' , 'B' ) ;

SQL> insert into emp values ( 6 , 'Jean' , 'Anne' , 'B' ) ;

1 row created.

1 row created.

1 row created.

1 row created.

1 row created.

1 row created.

SQL> commit ;

Commit complete.

SQL> create table dept ( dtpt_num number , cross_dept varchar2(3) ) ;

Table created.

SQL> insert into dept values ( 100 , 'DEV' ) ;

1 row created.

SQL> insert into dept values ( 200 , 'HRM' ) ;

1 row created.

SQL> col name for a20
SQL> col manager for a20
SQL> select * from emp ;

    EMP_ID NAME                 MANAGER              E
---------- -------------------- -------------------- -
         1 John                 Bill                 A
         2 Mike                 Bill                 A
         3 Sara                 Bill                 A
         4 Dany                 Bill                 A
         5 Mila                 Anne                 B
         6 Jean                 Anne                 B

6 rows selected.

SQL> select * from dept ;

  DTPT_NUM CRO
---------- ---
       100 DEV
       200 HRM

Now, let's build a demo function to get a random salary based on the id

SQL> create or replace function get_salary ( pempid in number )
return number deterministic
is
out_sal number;
begin
    select round(dbms_random.value(1000,3000),0) into out_sal from dual ;
        return out_sal ;
end;
/

Function created.

Let's add a new column to the emp table in order to join both tables by department

SQL> alter table emp add dept_id number ;

Table altered.

SQL> update emp set dept_id = ( case when EMP_STATUS = 'A' then 100 else 200 end ) ;

6 rows updated.

SQL> commit ;

Commit complete.

SQL> select * from emp
  2  ;

    EMP_ID NAME                 MANAGER              E    DEPT_ID
---------- -------------------- -------------------- - ----------
         1 John                 Bill                 A        100
         2 Mike                 Bill                 A        100
         3 Sara                 Bill                 A        100
         4 Dany                 Bill                 A        100
         5 Mila                 Anne                 B        200
         6 Jean                 Anne                 B        200

6 rows selected.

We can now join both tables and use the demo function to get a random salary

SQL> select e.emp_id , e.name, e.manager, d.cross_dept, d.dtpt_num , get_salary(e.emp_id) as salary
from emp e
join Dept d on e.DEPT_ID = d.dtpt_num  ;

    EMP_ID NAME                 MANAGER              CRO   DTPT_NUM     SALARY
---------- -------------------- -------------------- --- ---------- ----------
         1 John                 Bill                 DEV        100       2129
         2 Mike                 Bill                 DEV        100       1215
         3 Sara                 Bill                 DEV        100       2930
         4 Dany                 Bill                 DEV        100       1347
         5 Mila                 Anne                 HRM        200       1664
         6 Jean                 Anne                 HRM        200       1770

6 rows selected.

Now, let's build a procedure that will take in consideration when the input parameter might be either a single employee or a list of them split by comma and store the results in a temporary table ( just for demo purposes, you can use a sys_refcursor object as well )

SQL> create table tmp_results as
select e.emp_id , e.name, e.manager, d.cross_dept, d.dtpt_num , get_salary(e.emp_id) as salary
from emp e
join Dept d on e.DEPT_ID = d.dtpt_num
where 1 = 2 ; 

Table created.

create or replace procedure emp_test ( pemployee in varchar2 )
is
v_counter_records pls_integer;
v_num_of_values   pls_integer;
curr_val          varchar2(10);
begin
    v_counter_records := regexp_count ( pemployee , ',' , 1 , 'i' ); 
    if v_counter_records = 0
    then 
        insert into  tmp_results 
        select e.emp_id , e.name, e.manager, d.cross_dept, d.dtpt_num , get_salary(e.emp_id) as salary
        from emp e
        join Dept d on e.DEPT_ID = d.dtpt_num 
        where e.emp_id = to_number(pemployee) ;
    else 
        v_num_of_values := v_counter_records + 1;
        for rec in 1 .. v_num_of_values
        loop 
            curr_val := regexp_substr( pemployee, '[^,]+', 1 , rec );
            insert into  tmp_results 
            select e.emp_id , e.name, e.manager, d.cross_dept, d.dtpt_num , get_salary(e.emp_id) as salary
            from emp e
            join Dept d on e.DEPT_ID = d.dtpt_num 
            where e.emp_id = to_number(curr_val);
        end loop;
    end if;
end;
/

Procedure created.

SQL> exec emp_test( '1,2,3' );

PL/SQL procedure successfully completed.

SQL> select * from tmp_results ;

    EMP_ID NAME                 MANAGER              CRO   DTPT_NUM     SALARY
---------- -------------------- -------------------- --- ---------- ----------
         1 John                 Bill                 DEV        100       2386
         2 Mike                 Bill                 DEV        100       1531
         3 Sara                 Bill                 DEV        100       2202

Considerations

  • My example is just to show you how to split a list of parameters used as a single one in the input of the procedure.
  • You can also change the query to use IN instead of = to evaluate the condition when there is more than a single value.
  • I did not include any special logic for the function, because for the purposes of the example it was not needed.
  • You can use your WITH statement as well, to get a list of employees to be compared later on, but in your question was not clear when you are using that CTE construction.
Roberto Hernandez
  • 8,231
  • 3
  • 14
  • 43
  • What you mentioned at the beginning of your answer makes totally sense but could you tell me what the `case` statement is for in the where clause of OP's question ? I don't get the comparison – Sujitmohanty30 Aug 29 '20 at 16:49
  • @Roberto Hernandez this is exactly what I was looking for. Appreciated for your great effort. – user13079741 Sep 01 '20 at 20:36
0

You have to use in (select upper(ALL_EMP) from dept_em). The function upper expects single value, so you cannot apply it for a select returning multiple rows. For the same reason you have to use in instead of = in the where clause.

Pavel Gatnar
  • 3,987
  • 2
  • 19
  • 29