从 CASE WHEN 语句中取出相关子查询

Posted

技术标签:

【中文标题】从 CASE WHEN 语句中取出相关子查询【英文标题】:Take Correlated Sub-query Out of CASE WHEN Statement 【发布时间】:2016-08-12 20:27:21 【问题描述】:

我在 Oracle 中有一个 employee 表,它可以在表 future_jobs 中拥有 1 个或 2 个“未来”工作,这是某种业务规则,例如

| 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  |

我必须通过调用具有特定参数的存储过程来获取每个未来工作的描述,例如F1F2,基于 job_start_date 的降序排列。在上面的例子中,对于employee_id = 1,当对job_id = 127589行执行下面的查询时,因为job_start_date = 12-SEP-2016employee_id = 1的两行中最早的日期,所以应该调用get_description(emp.employee_id, 'F1'),而get_description(emp.employee_id, 'F2')对于job_id = 834780.

对于employee_id = 2,因为只有一个未来的工作,get_description(emp.employee_id, 'F1') 应该使用下面的查询来调用。目前,我可以通过以下查询拉取相关信息:

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    | ....         | 

但是,我想知道是否有另一种方法可以将相关子查询从 CASE WHEN 语句中取出?有没有办法在不使用相关子查询的情况下做到这一点?我需要在单个语句中完成此操作,而不是使用 WITH 子句类型解决方案。

【问题讨论】:

【参考方案1】:

我认为你只需要窗口函数:

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;

我不能 100% 确定逻辑是否完全正确。它遵循您的描述,并在未来的第一份工作中使用F1

【讨论】:

谢谢。这就是我一直在寻找的。我更新了描述以解释查询应返回的内容。您如何将此解决方案的性能与问题中的性能进行比较?在什么条件下,分区和解析函数表现更好?【参考方案2】:

实际上,您甚至不需要最大开始日期,也不需要嵌套选择来获取行号,您可以在使用 count(*) 作为窗口函数的 case 语句中正确执行此操作。

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

我喜欢 Gordon 正在考虑窗口函数,但我使用 MAX() 和 COUNT() 来测试您的子选择条件。但和他一样,我并不肯定我完全理解你想要的逻辑。

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

运行计数示例

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

【讨论】:

谢谢马特。我更新了问题。您的解决方案也有效。鉴于有两种可能的情况,我们可以消除 MAX()COUNT() 聚合并执行 Gordon 提出的类似操作。 是的,但因为他的解决方案是嵌套选择,我很想知道我们的解决方案之间的性能差异是什么。我愿意打赌 case 语句中的 count() 会稍微快一点 我想知道是否有一种方法可以在没有分析功能的情况下进行查询。 您是否在考虑没有窗口功能?还是没有聚合函数?基本上,您必须复制聚合窗口函数的功能,或者您必须以某种方式创建 row_number。所有其他方法都不会那么干净,也不会表现得那么好 我刚刚注意到我实际上忘记了我的答案中的 order by 只使用了 COUNT(*) order by 使其成为需要的运行计数

以上是关于从 CASE WHEN 语句中取出相关子查询的主要内容,如果未能解决你的问题,请参考以下文章

hibernate hql case when 子查询报java.lang.NullPointerException错误

Oracle SQL:对 CASE WHEN 重复使用子查询,而无需重复子查询

TSQL:SELECT CASE WHEN THEN 子查询:错误:子查询返回超过 1 个值

子查询作为 CASE WHEN 条件

Case when 条件下来自另一个表的子查询

关于Hive中case when不准使用子查询的解决方法