CASE 内的 LAG 给出假阴性偏移
LAG within CASE giving false negative offset
TL;DR:向下滚动到任务 2。
我正在处理以下数据集:
email,createdby,createdon
a@b.c,jsmith,2016-10-10
a@b.c,nsmythe,2016-09-09
a@b.c,vstark,2016-11-11
b@x.y,ajohnson,2015-02-03
b@x.y,elear,2015-01-01
...
等等。保证每封邮件在数据集中至少有一份重复。
现在,有两个任务要解决;我解决了其中一个问题,但正在努力解决另一个问题。为了完整起见,我现在将介绍这两项任务。
任务 1(已解决):
对于每一行,对于每封电子邮件,return 一个附加列,其中包含使用该电子邮件创建第一条记录的用户的姓名。
上述样本数据集的预期结果:
email,createdby,createdon,original_createdby
a@b.c,jsmith,2016-10-10,nsmythe
a@b.c,nsmythe,2016-09-09,nsmythe
a@b.c,vstark,2016-11-11,nsmythe
b@x.y,ajohnson,2015-02-03,elear
b@x.y,elear,2015-01-01,elear
获取以上代码:
;WITH q0 -- this is just a security measure in case there are unique emails in the data set
AS ( SELECT t.email
FROM t
GROUP BY t.email
HAVING COUNT(*) > 1) ,
q1
AS ( SELECT q0.email
, createdon
, createdby
, ROW_NUMBER() OVER ( PARTITION BY q0.email ORDER BY createdon ) rn
FROM t
JOIN q0
ON t.email = q0.email)
SELECT q1.email
, q1.createdon
, q1.createdby
, LAG(q1.createdby, q1.rn - 1) OVER ( ORDER BY q1.email, q1.createdon ) original_createdby
FROM q1
ORDER BY q1.email
, q1.rn
简要说明:我按电子邮件对数据集进行分区,然后按创建日期对每个分区中的行进行编号,最后我 return [createdby] 来自第 (rn-1) 条记录的值。完全按照预期工作。
现在,与上面类似,还有任务 2:
任务 2:
对于每一行,对于每封电子邮件,return 创建第一个副本的用户的姓名。 IE。用户名,其中 rn=2.
预期结果:
email,createdby,createdon,first_dupl_createdby
a@b.c,jsmith,2016-10-10,jsmith
a@b.c,nsmythe,2016-09-09,jsmith
a@b.c,vstark,2016-11-11,jsmith
b@x.y,ajohnson,2015-02-03,ajohnson
b@x.y,elear,2015-01-01,ajohnson
我想保持性能,所以尝试使用 LEAD-LAG 函数:
WITH q0
AS ( SELECT t.email
FROM t
GROUP BY t.email
HAVING COUNT(*) > 1) ,
q1
AS ( SELECT q0.email
, createdon
, createdby
, ROW_NUMBER() OVER ( PARTITION BY q0.email ORDER BY createdon ) rn
FROM t
JOIN q0
ON t.email = q0.email)
SELECT q1.email
, q1.createdon
, q1.createdby
, q1.rn
, CASE q1.rn
WHEN 1 THEN LEAD(q1.createdby, 1) OVER ( ORDER BY q1.email, q1.createdon )
ELSE LAG(q1.createdby, q1.rn - 2) OVER ( ORDER BY q1.email, q1.createdon )
END AS first_dupl_createdby
FROM q1
ORDER BY q1.email
, q1.rn
说明:对于每个分区中的第一条记录,return [createdby] 来自以下记录(即来自包含第一个重复项的记录)。对于同一分区 return [createdby] 来自 (rn-2) 条记录的所有其他记录(即对于 rn = 2,我们停留在同一记录上,对于 rn = 3,我们将返回 1 条记录, 对于 rn = 4 - 2 条记录等等)。
出现问题
ELSE LAG(q1.createdby, q1.rn - 2)
操作。显然,与任何逻辑相反,尽管存在前一行 (WHEN 1 THEN...),ELSE 块也被评估为 rn = 1,导致传递给 LAG 函数的负偏移值:
消息 8730,级别 16,状态 2,第 37 行
Lag 和 Lead 函数的偏移参数不能为负值。
当我注释掉 ELSE 行时,整个过程都正常,但显然我没有在 rn > 1 的 first_dupl_createdby 列中得到任何结果。
问题:
有没有什么方法可以重写上面的 CASE 语句(在任务 #2 中),以便它总是 returns 来自每个分区中 rn = 2 的记录的值但是 - 这很重要 - 没有做自连接操作(我知道我可以在单独的子查询中准备 rn = 2 的行,但这将意味着对整个 table 和 运行 不必要的自连接进行额外扫描)。
您可以使用 row_number()
和条件聚合获取每封电子邮件的信息:
select email,
max(case when seqnum = 1 then createdby end) as createdby_first,
max(case when seqnum = 2 then createdby end) as createdby_second
from (select t.*,
row_number() over (partition by email order by createdon) as seqnum
from t
) t
group by email;
你可以join
将这些信息回溯到原始数据,得到你想要的信息。我不明白 lag()
自然会如何解决这个问题。
我认为您可以简单地使用 max
window 函数,因为您试图从每个分区的 rownumber = 2 中获取值。
SELECT q1.email
, q1.createdon
, q1.createdby
, q1.rn
, max(case when rn=2 then q1.createdby end) over(partition by q1.email) first_dup_created_by
FROM q1
ORDER BY q1.email, q1.rn
对于第一种情况,您也可以使用类似的查询来获取 rownumber=1 的结果。
/耸肩
; WITH duplicate_email_addresses AS (
SELECT email
FROM t
GROUP
BY email
HAVING Count(*) > 1
)
, records_with_duplicate_email_addresses AS (
SELECT email
, createdon
, createdby
, Row_Number() OVER (PARTITION BY email ORDER BY createdon) AS sequencer
FROM t
WHERE EXISTS (
SELECT *
FROM duplicate_email_addresses
WHERE email = t.email
)
)
, second_duplicate_record AS ( -- Why do you need any more than this?
SELECT email
, createdon
, createdby
FROM records_with_duplicate_email_addresses
WHERE sequencer = 2
)
SELECT records_with_duplicate_email_addresses.email
, records_with_duplicate_email_addresses.createdon
, records_with_duplicate_email_addresses.createdby
, second_duplicate_record.createdby AS first_duplicate_createdby
FROM records_with_duplicate_email_addresses
INNER
JOIN second_duplicate_record
ON second_duplicate_record.email = records_with_duplicate_email_addresses.email
;
TL;DR:向下滚动到任务 2。
我正在处理以下数据集:
email,createdby,createdon
a@b.c,jsmith,2016-10-10
a@b.c,nsmythe,2016-09-09
a@b.c,vstark,2016-11-11
b@x.y,ajohnson,2015-02-03
b@x.y,elear,2015-01-01
...
等等。保证每封邮件在数据集中至少有一份重复。
现在,有两个任务要解决;我解决了其中一个问题,但正在努力解决另一个问题。为了完整起见,我现在将介绍这两项任务。
任务 1(已解决): 对于每一行,对于每封电子邮件,return 一个附加列,其中包含使用该电子邮件创建第一条记录的用户的姓名。
上述样本数据集的预期结果:
email,createdby,createdon,original_createdby
a@b.c,jsmith,2016-10-10,nsmythe
a@b.c,nsmythe,2016-09-09,nsmythe
a@b.c,vstark,2016-11-11,nsmythe
b@x.y,ajohnson,2015-02-03,elear
b@x.y,elear,2015-01-01,elear
获取以上代码:
;WITH q0 -- this is just a security measure in case there are unique emails in the data set
AS ( SELECT t.email
FROM t
GROUP BY t.email
HAVING COUNT(*) > 1) ,
q1
AS ( SELECT q0.email
, createdon
, createdby
, ROW_NUMBER() OVER ( PARTITION BY q0.email ORDER BY createdon ) rn
FROM t
JOIN q0
ON t.email = q0.email)
SELECT q1.email
, q1.createdon
, q1.createdby
, LAG(q1.createdby, q1.rn - 1) OVER ( ORDER BY q1.email, q1.createdon ) original_createdby
FROM q1
ORDER BY q1.email
, q1.rn
简要说明:我按电子邮件对数据集进行分区,然后按创建日期对每个分区中的行进行编号,最后我 return [createdby] 来自第 (rn-1) 条记录的值。完全按照预期工作。
现在,与上面类似,还有任务 2:
任务 2: 对于每一行,对于每封电子邮件,return 创建第一个副本的用户的姓名。 IE。用户名,其中 rn=2.
预期结果:
email,createdby,createdon,first_dupl_createdby
a@b.c,jsmith,2016-10-10,jsmith
a@b.c,nsmythe,2016-09-09,jsmith
a@b.c,vstark,2016-11-11,jsmith
b@x.y,ajohnson,2015-02-03,ajohnson
b@x.y,elear,2015-01-01,ajohnson
我想保持性能,所以尝试使用 LEAD-LAG 函数:
WITH q0
AS ( SELECT t.email
FROM t
GROUP BY t.email
HAVING COUNT(*) > 1) ,
q1
AS ( SELECT q0.email
, createdon
, createdby
, ROW_NUMBER() OVER ( PARTITION BY q0.email ORDER BY createdon ) rn
FROM t
JOIN q0
ON t.email = q0.email)
SELECT q1.email
, q1.createdon
, q1.createdby
, q1.rn
, CASE q1.rn
WHEN 1 THEN LEAD(q1.createdby, 1) OVER ( ORDER BY q1.email, q1.createdon )
ELSE LAG(q1.createdby, q1.rn - 2) OVER ( ORDER BY q1.email, q1.createdon )
END AS first_dupl_createdby
FROM q1
ORDER BY q1.email
, q1.rn
说明:对于每个分区中的第一条记录,return [createdby] 来自以下记录(即来自包含第一个重复项的记录)。对于同一分区 return [createdby] 来自 (rn-2) 条记录的所有其他记录(即对于 rn = 2,我们停留在同一记录上,对于 rn = 3,我们将返回 1 条记录, 对于 rn = 4 - 2 条记录等等)。
出现问题ELSE LAG(q1.createdby, q1.rn - 2)
操作。显然,与任何逻辑相反,尽管存在前一行 (WHEN 1 THEN...),ELSE 块也被评估为 rn = 1,导致传递给 LAG 函数的负偏移值:
消息 8730,级别 16,状态 2,第 37 行 Lag 和 Lead 函数的偏移参数不能为负值。
当我注释掉 ELSE 行时,整个过程都正常,但显然我没有在 rn > 1 的 first_dupl_createdby 列中得到任何结果。
问题: 有没有什么方法可以重写上面的 CASE 语句(在任务 #2 中),以便它总是 returns 来自每个分区中 rn = 2 的记录的值但是 - 这很重要 - 没有做自连接操作(我知道我可以在单独的子查询中准备 rn = 2 的行,但这将意味着对整个 table 和 运行 不必要的自连接进行额外扫描)。
您可以使用 row_number()
和条件聚合获取每封电子邮件的信息:
select email,
max(case when seqnum = 1 then createdby end) as createdby_first,
max(case when seqnum = 2 then createdby end) as createdby_second
from (select t.*,
row_number() over (partition by email order by createdon) as seqnum
from t
) t
group by email;
你可以join
将这些信息回溯到原始数据,得到你想要的信息。我不明白 lag()
自然会如何解决这个问题。
我认为您可以简单地使用 max
window 函数,因为您试图从每个分区的 rownumber = 2 中获取值。
SELECT q1.email
, q1.createdon
, q1.createdby
, q1.rn
, max(case when rn=2 then q1.createdby end) over(partition by q1.email) first_dup_created_by
FROM q1
ORDER BY q1.email, q1.rn
对于第一种情况,您也可以使用类似的查询来获取 rownumber=1 的结果。
/耸肩
; WITH duplicate_email_addresses AS (
SELECT email
FROM t
GROUP
BY email
HAVING Count(*) > 1
)
, records_with_duplicate_email_addresses AS (
SELECT email
, createdon
, createdby
, Row_Number() OVER (PARTITION BY email ORDER BY createdon) AS sequencer
FROM t
WHERE EXISTS (
SELECT *
FROM duplicate_email_addresses
WHERE email = t.email
)
)
, second_duplicate_record AS ( -- Why do you need any more than this?
SELECT email
, createdon
, createdby
FROM records_with_duplicate_email_addresses
WHERE sequencer = 2
)
SELECT records_with_duplicate_email_addresses.email
, records_with_duplicate_email_addresses.createdon
, records_with_duplicate_email_addresses.createdby
, second_duplicate_record.createdby AS first_duplicate_createdby
FROM records_with_duplicate_email_addresses
INNER
JOIN second_duplicate_record
ON second_duplicate_record.email = records_with_duplicate_email_addresses.email
;