我有一个SQL查询,第一列是日期,第二列和第三列是其他信息。我们可以假设每天只有一条记录,我们可以假设订购日期。
除第一列外,其他人包含不同数据类型的数据long
,int
,nchar
,varchar
等。
我的目标是弄清楚在第T天与T-1比较时是否有任何剩余的列发生了变化,如果是这样的话(比如列N被更改,N可能是任何剩余的列,并且可能有多个列在同一天更改)我想将日期(第一列)与第N天的Col N记录和第T-1天的Col N(可能是该列的标题)一起返回,所以我的输出是:
Date T|Header of Col Modified|Value of Col @ T-1|Value of Col @ T
如果有多项更改,我们每次更改都会有一行。
显然,“慢”的方法是首先将所有数据放在某处,然后逐个比较结果数据集。但是我只是想知道是否有任何快速的方法在T-SQL中执行它以便我可以直接获得结果(或接近结果的东西,以便查询输出需要很少的数据操作,因为它会SQL Server能够更快地完成繁重的工作,只返回我需要的数据,而不是整个表格。
我正在使用SQL Server,因此需要在T-SQL中使用解决方案。
答案 0 :(得分:5)
我的解决方案会返回一组原始的已更改行。解决方案只是告诉您两个相邻行的某些列中的值已更改。它没有明确告诉您哪些列已更改。您需要执行此额外处理(最有可能在客户端)以您需要的方式显示数据。该解决方案最重要的部分是它将返回到客户端的行数减少到最小。
如果您使用SQL Server 2012或更高版本,它具有函数LAG
和LEAD
,可用于比较上一行/下一行:
示例数据:
DECLARE @T TABLE(dt date, v1 int, v2 varchar(50));
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-01', 1, 'a');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-02', 2, 'b');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-03', 2, 'b');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-04', 3, 'b');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-05', 3, 'b');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-06', 3, 'c');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-07', 4, 'd');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-08', 4, 'd');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-09', 4, 'd');
INSERT INTO @T (dt, v1, v2) VALUES ('2015-01-10', 4, 'd');
使用LAG
,SQL Server 2012 +
WITH
CTE
AS
(
SELECT
dt
,v1
,v2
,LAG(v1) OVER(ORDER BY dt) AS PrevV1
,LAG(v2) OVER(ORDER BY dt) AS PrevV2
FROM @T AS T
)
SELECT *
FROM CTE
WHERE
v1 <> PrevV1
OR v2 <> PrevV2
ORDER BY dt;
结果集
dt v1 v2 PrevV1 PrevV2
2015-01-02 2 b 1 a
2015-01-04 3 b 2 b
2015-01-06 3 c 3 b
2015-01-07 4 d 3 c
如果使用某些以前的版本,那么从性能的角度来看,使用游标循环遍历行,将当前行与前一行进行比较并将diff插入临时表或表变量可能会更好。没有LEAD
或LAG
的任何其他解决方案都意味着源表至少被读取两次,而在最差的解决方案中,它将是O(n*n)
而不是O(n)
。
从SQL Server 2005开始,有一个函数ROW_NUMBER
。如果您表中的日期列表可能存在差距,我们可以使用它。如果您确定日期没有间隙,则可以在没有生成行号的额外步骤的情况下执行此操作,并直接在日期列中加入表。它仍然是一个自我加入。
使用ROW_NUMBER
,SQL Server 2005 +
WITH
CTE
AS
(
SELECT
dt
,v1
,v2
,ROW_NUMBER() OVER(ORDER BY dt) AS rn
FROM @T AS T
)
SELECT
CTE_Curr.dt
,CTE_Curr.v1 AS CurrV1
,CTE_Curr.v2 AS CurrV2
,CTE_Prev.v1 AS PrevV1
,CTE_Prev.v2 AS PrevV2
FROM
CTE AS CTE_Curr
INNER JOIN CTE AS CTE_Prev ON CTE_Curr.rn = CTE_Prev.rn+1
WHERE
CTE_Curr.v1 <> CTE_Prev.v1
OR CTE_Curr.v2 <> CTE_Prev.v2
ORDER BY CTE_Curr.dt;
结果集
dt CurrV1 CurrV2 PrevV1 PrevV2
2015-01-02 2 b 1 a
2015-01-04 3 b 2 b
2015-01-06 3 c 3 b
2015-01-07 4 d 3 c
执行计划比较
两种变体的结果相同,但执行计划却截然不同。 LAG
变体的相对成本估计为33%,ROW_NUMBER
变体的相对成本为67% - 两倍,因为第二个变量扫描并对表进行两次排序,样本表非常小。此外,您可以看到,在第二个变体中,表格与其自身相连,从而导致读取100行(10 * 10)。如果您的表很大,则效率非常低,使用游标可能会更好。使用光标,您只需扫描一次表。
LAG
ROW_NUMBER