简体   繁体   中英

Take Correlated Sub-query Out of CASE WHEN Statement

I have an employee table in Oracle that can have either 1 or 2 "future" jobs in table future_jobs , a business rule of some sort, eg

| employee_id | job_id | job_start_date | job_end_date |
|-------------|--------|----------------|--------------|
| 1           | 127589 | 12-SEP-2016    | 25-DEC-2016  |
| 1           | 834780 | 26-DEC-2016    | 08-AUG-2017  |
| 2           | 800253 | 20-OCT-2016    | 13-APR-2017  |

I have to get the description of each future job by invoking a stored procedure with a specific parameter, eg F1 and F2 , based on a descending order of job_start_date . In example above, for employee_id = 1 , when the query below is executed for job_id = 127589 row, since job_start_date = 12-SEP-2016 is the earliest date among the two rows for employee_id = 1 , get_description(emp.employee_id, 'F1') should be called, and get_description(emp.employee_id, 'F2') for job_id = 834780 .

and for employee_id = 2 , since there is only one future job, get_description(emp.employee_id, 'F1') should be invoked with the query below. Currently, I can pull the relevant information with the following query:

select
    emp.employee_id,
    case
        when fj.job_start_date = (select max(job_start_date)
                                  from future_jobs
                                  where employee_id = fj.employee_id
                                  group by employee_id
                                  having count(employee_id) > 1)
        then get_description(emp.employee_id, 'F2')
        else get_description(emp.employee_id, 'F1')
    end job_description,
    fj.job_start_date
    jd.some_additional_columns
from employees emp
join future_jobs fj
    on emp.employee_id = fj.employee_id
join job_details jd
    on  jd.job_id = fj.job_id
    and jd.job_start_date = fj.job_start_date
    and jd.job_end_date = fj.job_end_date

.

| employee_id |    job_description   | job_start_date |  jd.columns  |
|-------------|----------------------|----------------|--------------|
| 1           | 1st future job desc  | 12-SEP-2016    | ....         | 
| 1           | 2nd future job desc  | 26-DEC-2016    | ....         |  
| 2           | 1st future job desc  | 20-OCT-2016    | ....         | 

However, I would like to know if there is another way to take the correlated sub-query out of CASE WHEN statement? Is there even a way to do it without using the correlated sub-query? I need to have this done in a single statement rather than using WITH clause type solution.

I think you just want window functions:

select emp.employee_id,
       (case when fj.seqnum = 1
             then get_description(emp.employee_id, 'F1')
             else get_description(emp.employee_id, 'F2')
        end) as job_description,
       jd.some_additional_columns
from employees emp join
     (select fj.*,
             row_number() over (partition by employee_id order by fj.job_start_date) as seqnum
      from future_jobs fj
     ) fj
    on emp.employee_id = fj.employee_id join
    job_details jd
    on jd.job_id = fj.job_id and
       jd.job_start_date = fj.job_start_date and
       jd.job_end_date = fj.job_end_date;

I'm not 100% sure the logic is exactly correct. It follows your description and uses F1 for the first future job.

Actually on second thought you don't even need the max start date and you don't need the nested select to get a row number you can do it right in the case statement with count(*) as a window function.

select
    emp.employee_id,
    case
        when COUNT(*) OVER (PARTITION BY fj.employee_id ORDER BY fj.job_start_date) > 1
        then get_description(emp.employee_id, 'F2')
        else get_description(emp.employee_id, 'F1')
    end job_description,
    jd.some_additional_columns
from
    employees emp
    join future_jobs fj
    on emp.employee_id = fj.employee_id
    join job_details jd
    on  jd.job_id = fj.job_id
    and jd.job_start_date = fj.job_start_date
    and jd.job_end_date = fj.job_end_date

I like Gordon was thinking of window functions but I uses MAX() and COUNT() over to test your conditions of your subselect. But like him I am not positive I fully understand your desired logic.

select
    emp.employee_id,
    case
        when fj.job_start_date = MAX(fj.job_start_date) OVER (PARTITION BY fj.employee_id)
          AND COUNT(*) OVER (PARTITION BY fj.employee_id) > 1
        then get_description(emp.employee_id, 'F2')
        else get_description(emp.employee_id, 'F1')
    end job_description,
    jd.some_additional_columns
from
    employees emp
    join future_jobs fj
    on emp.employee_id = fj.employee_id
    join job_details jd
    on  jd.job_id = fj.job_id
    and jd.job_start_date = fj.job_start_date
    and jd.job_end_date = fj.job_end_date

Running Count Example

DECLARE @Table AS TABLE (A CHAR(1),P INT)
INSERT INTO @Table (A,P) VALUES ('A',1),('B',1),('C',2),('D',2)

SELECT
    *
    ,COUNT(*) OVER (PARTITION BY P ORDER BY A) as RunningCount
FROM
    @Table

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM