返回 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:不匹配的输入“来自”期望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 值并忽略空值 - 使用前面的非空值填充空值的主要内容,如果未能解决你的问题,请参考以下文章
无法在 varbinary(max) 中插入空值并出现错误:不允许从数据类型 nvarchar 到 varbinary(max) 的隐式转换