SQL:如何将 first_value 忽略为聚合?

Posted

技术标签:

【中文标题】SQL:如何将 first_value 忽略为聚合?【英文标题】:SQL: How can I get the first_value ignoring nulls as an aggregate? 【发布时间】:2016-02-18 18:11:41 【问题描述】:

我有一个表,用作员工、部门和公司设置的分层数据集。员工可以在任何部门工作,如果他们没有指定,他们会继承部门设置。更具体的设置胜出,我想编写一个查询来获取特定员工/部门对的设置。

设置表有一个employeeid 可以为null,departmentid 也可以为null。如果两者都为空,则为公司范围设置的行。 "nvl(employeeid,0) 和 nvl(departmentid,0)" 有唯一的约束。

设置示例数据集:

employeeid    departmentid    address        phone
null          null            123 Corp Dr.   800-555-1212
10            null            1 ABC Ave.     null
null          1               2 Dept Rd.     null
null          2               3 Dept Rd.     617-555-1212           

当我对员工 10 和部门 1 运行查询时,我应该得到一行: 地址 = 1 ABC Ave,电话 = 800-555-1212

对于员工 10 和部门 2,我应该获得更新的电话号码: 地址 = 1 ABC Ave,电话 = 617-555-1212

到目前为止,我能做的最好的事情是使用 first_value,忽略空值,在表上并按我添加的优先级排序。问题是 first_value over 不是聚合的,所以我需要一个单独的外部查询来选择特定的优先级。在我看来,这就像我应该能够聚合的东西。

select
    address,
    phone
from (
    select
        precedence,
        first_value(address ignore nulls) over (order by precedence) address,
        first_value(phone ignore nulls) over (order by precedence) phone
    from (
        select
            1 precedence,
            *
        from
            settings
        where
            settings.employeeid = ?
            and settings.departmentid is null
        union
        select
            2 precedence,
            *
        from
            settings
        where
            settings.departmentid = ?
            and settings.employeeid is null
        union
        select
            3 precedence,
            *
        from
            settings
        where
            settings.departmentid is null
            and settings.employeeid is null
    )
)
where
    precedence = 3

这得到了正确的答案,但我觉得好像应该有一种方法可以在中间查询中将 first_values 汇总为聚合并删除外部查询,并且可能仅依赖联合的显式排序而不是引入优先级列,尽管这不太重要。

我为此使用 Oracle 11。

【问题讨论】:

您可以在ORDER BYFIRST_VALUE() 中使用CASE 表达式。 查看您的、我的和 Alex 的工作 - 您是否还必须为员工包括多行的可能性(一个部门为空,以及该员工 ID 为非空部门 ID 的多行?) 如果是这样,那么我们所有的优先级计算都需要更多的工作。 【参考方案1】:

您可以通过保持密集排名和单级内联视图来做到这一点,这只需要点击一次表格 - 您可以使用案例来决定基表中每个相关行的偏好级别,而不是联合:

select min(address) keep (dense_rank first
    order by case when address is null then 1 else 0 end, preference) as address,
  min(phone) keep (dense_rank first
    order by case when phone is null then 1 else 0 end, preference) as phone
from (
  select s.address, s.phone, case when s.employeeid is not null then 1
    when s.employeeid is null and s.departmentid is not null then 2
    else 3 end as preference
  from settings s
  where s.employeeid = 10
  or (s.employeeid is null and s.departmentid = 2)
  or (s.employeeid is null and s.departmentid is null)
);

ADDRESS      PHONE      
------------ ------------
1 ABC Ave.   617-555-1212

...当然,将= 10= 2 更改为绑定变量占位符。

传入员工 1 和部门 10 得到:

ADDRESS      PHONE      
------------ ------------
1 ABC Ave.   800-555-1212

您仍然可以改用first_value

first_value(address) over (order by case when address is null then 1 else 0 end,
  preference) as address,

...但是您必须使用 distinct 来删除重复项。

【讨论】:

我喜欢这个 Alex。我太忙于关注 OP 的 first_value 起点,而忘记考虑 KEEP 选项。您的成本也更高。【参考方案2】:

避免三个联合设置优先级的一个选项如下。它需要将它全部推送到一个嵌套查询中以消除 first_value 上的空值,但它通过表一次性完成所有操作

(请注意,我在 :emp 和 :dept 占位符上使用 Oracle 冒号前缀进行变量替换):

WITH data as (
select null emp_id, null dept_id, '123 Corp Dr.' addr,   '800-555-1212' phn from dual union all
select 10,          null,         '1 ABC Ave.',           null from dual union all
select null,        1,            '2 Dept Rd.',           null from dual union all
select null ,       2,            '3 Dept Rd.',           '617-555-1212'    from dual )
SELECT DISTINCT addr, phn
FROM (
    SELECT first_value(addr ignore nulls) over (order by precedence) as addr
         , first_value(phn ignore nulls) over (order by precedence)  as phn
    FROM (
        select CASE WHEN emp_id = :emp then 1
                    when dept_id = :dept then 2
                    WHEN emp_id is null and dept_id is null then 3 end as precedence
              , addr
              , phn       
        from data
        )
    where precedence is not null
    )
where addr is not null and phn is not null;

【讨论】:

以上是关于SQL:如何将 first_value 忽略为聚合?的主要内容,如果未能解决你的问题,请参考以下文章

Spark SQL 可以在 GROUP BY 聚合中使用 FIRST_VALUE 和 LAST_VALUE(但这不是标准的)

EF部分字段更新,忽略为null字段

java对象json序列化时候忽略为null的属性

AWS Athena 无法将 FIRST_VALUE() 识别为聚合表达式

(REDSHIFT) 垂直合并 / FIRST_VALUE() 作为聚合

BigQuery、FIRST_VALUE 和 null