返回 MIN 和 MAX 值并忽略空值 - 使用前面的非空值填充空值

Posted

技术标签:

【中文标题】返回 MIN 和 MAX 值并忽略空值 - 使用前面的非空值填充空值【英文标题】:Returning MIN and MAX values and ignoring nulls - populate null values with preceding non-null value 【发布时间】:2019-04-25 23:25:56 【问题描述】:

使用事件表,我需要返回日期和类型:

第一个事件 最近的(非空)事件

最近的事件可能有空值,在这种情况下需要返回最近的非空值

我在 SO 上发现了一些相似(甚至可能相同)但无法解码或理解解决方案的文章和帖子 - 即

Fill null values with last non-null amount - Oracle SQL

https://www.itprotoday.com/sql-server/last-non-null-puzzle

https://koukia.ca/common-sql-problems-filling-null-values-with-preceding-non-null-values-ad538c9e62a6

表格如下 - 有额外的列,但为了简单起见,我只包括 3 个。另请注意,first 类型和日期可能为空。在这种情况下,需要返回 null。

╔═══════╦════════╦════════════╗
║ Email ║  Type  ║    Date    ║
╠═══════╬════════╬════════════╣
║ A     ║ Create ║ 2019-04-01 ║
║ A     ║ Update ║ 2019-04-02 ║
║ A     ║ null   ║ null       ║
╚═══════╩════════╩════════════╝

输出应该是:

╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate  ║ LastType ║  LastDate  ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A     ║ Create    ║ 2019-04-01 ║ Update   ║ 2019-04-02 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝

我尝试的第一种方法是使用子查询将表连接到自身,该子查询使用 case 语句查找 MIN 和 MAX 日期:

select
  Email,
  max(case when T1.Date = T2.Min_Date then T1.Type end) as FirstType,
  max(case when T1.Date = T2.Min_Date then T1.Date end) as FirstDate,
  max(case when T1.Date = T2.Max_Date then T1.Type end) as LastType,
  max(case when T1.Date = T2.Max_Date then T1.Date end) as LastDate,
from
  T1
join
  (select
    EmailAddress,
    max(Date) as Max_Date,
    min(Date) as Min_Date
  from
    Table1
  group by 
    Email
  ) T2
on
  T1.Email = T2.Email
group by
  T1.Email

这似乎适用于 MIN 值,但 MAX 值将返回 null。

为了解决返回最后一个非值的问题,我尝试了这个:

select
   EmailAddress,
   max(Date) over (partition by EmailAddress rows unbounded preceding) as LastDate,
   max(Type) over (partition by EmailAddress rows unbounded preceding) as LastType
from
   T1
group by
   EmailAddress,
   Date,
   Type

但是,这会给出 3 行而不是 1 行的结果。

我承认我不太了解分析函数,因为我不必详细处理它们。任何帮助将不胜感激。

编辑: 上述示例准确地表示了数据的外观,但下面的示例是我正在使用的确切示例数据。

示例:

╔═══════╦════════╦════════════╗
║ Email ║  Type  ║    Date    ║
╠═══════╬════════╬════════════╣
║ A     ║ Create ║ 2019-04-01 ║
║ A     ║ null   ║ null       ║
╚═══════╩════════╩════════════╝

期望的结果:

╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate  ║ LastType ║  LastDate  ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A     ║ Create    ║ 2019-04-01 ║ Create   ║ 2019-04-01 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝

其他用例:

╔═══════╦════════╦════════════╗
║ Email ║  Type  ║    Date    ║
╠═══════╬════════╬════════════╣
║ A     ║ null   ║ null       ║
║ A     ║ Create ║ 2019-04-01 ║
╚═══════╩════════╩════════════╝

期望的结果:

╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate  ║ LastType ║  LastDate  ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A     ║ null      ║ null       ║ Create   ║ 2019-04-01 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝

【问题讨论】:

【参考方案1】:

使用窗口函数和条件聚合:

select t.email,
       max(case when seqnum = 1 then type end) as first_type,
       max(case when seqnum = 1 then date end) as first_date,
       max(case when seqnum_nonull = 1 and type is not null then type end) as last_type,
       max(case when seqnum_nonull = 1 and type is not null then date end) as last_date
from (select t.*,
             row_number() over (partition by email order by date) as seqnum,
             row_number() over (partition by email, (case when type is null then 1 else 2 end) order by date) as seqnum_nonull
      from t
     ) t
group by t.email;

【讨论】:

感谢您的回复 - 此代码在 Databricks 中返回一个我无法调试的错误。 SQL 语句中的错误:ParseException:不匹配的输入“来自”期望 (第 6 行,位置 0) @A.Coustic 。 . . seqnum_nonull 的定义中存在拼写错误,可能导致您的问题。 错字修复确实解决了问题,但输出并不完全符合预期。我使用的示例数据集只有 2 条记录 - 第一条是非空的,第二条是空值。结果返回空值作为第一个事件,非空值作为第二个事件(我认为这是翻转的?)期望的结果应该返回第一个和最后一个事件的第一条记录。此外,我认为最后一个 case 语句中有一个错字,它混合了类型和日期。【参考方案2】:

由于 Spark SQL 窗口函数支持 NULLS LAST|FIRST 语法,您可以使用它然后为 rn 值 1 和 2 指定具有多个聚合的枢轴。我可以查看更多示例数据,但这适用于您的数据集:

%sql
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp;

;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
)
SELECT *
FROM cte
PIVOT ( MAX(date), MAX(type) FOR rn In ( 1, 2 ) )

通过在查询中提供所需的部分来重命名列,例如

-- Pivot and rename columns
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
)
SELECT *
FROM cte
PIVOT ( MAX(date) AS Date, MAX(type) AS Type FOR rn In ( 1 First, 2 Last ) ) 

交替提供一个列列表,例如

-- Pivot and rename columns
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
), cte2 AS
(
SELECT *
FROM cte
PIVOT ( MAX(date) AS Date, MAX(type) AS Type FOR rn In ( 1 First, 2 Last ) )
) 
SELECT *
FROM cte2 AS (Email, FirstDate, FirstType, LastDate, LastType)

此简单查询使用ROW_NUMBER 为按日期列排序的数据集分配行号,但使用NULLS LAST 语法确保空行出现在编号的最后。 PIVOT 然后将行转换为列。

【讨论】:

谢谢,这似乎有效。如何重命名输出列?你能简单解释一下这里发生了什么吗? @A.Coustic 有什么更新吗?如果它有效并回答了您的问题,请考虑将其标记为答案。 看起来输出并不完全符合预期。我使用的示例数据集只有 2 条记录,后者是空记录(回想起来,我的帖子应该使用这个示例)。它返回第一个事件的非空值和最后一个事件的空值。 Desired 应该返回第一个和最后一个事件的第一条记录,因为 null 仅在它是第一条记录时才有效。我不能说使用帖子中最初提供的示例数据的输出是什么.. 请提供一些准确的样本数据和预期结果。 请看我编辑的帖子。这两个示例都是用例​​,所以我不确定这是否会影响代码。

以上是关于返回 MIN 和 MAX 值并忽略空值 - 使用前面的非空值填充空值的主要内容,如果未能解决你的问题,请参考以下文章

SqlServer函数的聚合函数

聚集函数

无法在 varbinary(max) 中插入空值并出现错误:不允许从数据类型 nvarchar 到 varbinary(max) 的隐式转换

Oracle——分组函数

SQL 中的常用函数及使用

Ext JS 在摘要中排除空值 Ext.grid.feature.Summary 在网格中