【发布时间】:2017-03-30 17:06:40
【问题描述】:
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(已解决): 对于每一行,对于每封电子邮件,返回一个附加列,其中包含使用此电子邮件创建第一条记录的用户的名称。
上述样本数据集的预期结果:
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
简要说明:我通过电子邮件对数据集进行分区,然后按创建日期对每个分区中的行进行编号,最后从第 (rn-1) 条记录返回 [createdby] 值。完全按预期工作。
现在,与上面类似,有 TASK 2:
任务 2: 对于每一行,对于每封电子邮件,返回创建第一个副本的用户的名称。 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
解释:对于每个分区中的第一条记录,从后面的记录(即从包含第一个重复的记录)返回[createdby]。对于同一分区中的所有其他记录,从 (rn-2) 记录之前返回 [createdby](即,对于 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 中),以便它总是从每个分区中 rn = 2 的记录中返回值,但是 - 这是重要的一点 - 不进行自联接操作(我知道我可以在单独的子查询中准备 rn = 2 的行,但这意味着对整个表进行额外扫描,并且还会运行不必要的自联接)。
【问题讨论】:
-
编辑您的问题并包含您希望为样本数据获取的结果。
-
这听起来可能很愚蠢,但如果在
q1中你使用ROW_NUMBER()...+2作为rn会怎样?在您的case表达式中,您可以使用CASE q1.rn WHEN 3 THEN ...... ELSE LAG(q1.createdby, q1.rn)
标签: sql tsql sql-server-2012 window-functions