您使用什么版本的 SQL Server?
我下面的解决方案返回一组原始更改的行。该解决方案只是告诉您两个相邻行的某些列中的值已更改。它不会明确告诉您哪些列已更改。您需要执行此额外处理(很可能在客户端)以您需要的方式呈现数据。该解决方案最重要的部分是将返回给客户端的行数减少到最少。
如果你使用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
如果您使用以前的版本,那么从性能的角度来看,使用游标循环遍历行、将当前行与前一行进行比较并将差异插入临时表或表变量中可能会更好。没有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