快速比较有序SQL记录之间差异的方法

时间:2015-04-05 04:06:29

标签: sql sql-server performance sql-server-2008 tsql

我有一个SQL查询,第一列是日期,第二列和第三列是其他信息。我们可以假设每天只有一条记录,我们可以假设订购日期。

除第一列外,其他人包含不同数据类型的数据longintncharvarchar等。

我的目标是弄清楚在第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中使用解决方案。

1 个答案:

答案 0 :(得分:5)

您使用的是哪个版本的SQL Server?

我的解决方案会返回一组原始的已更改行。解决方案只是告诉您两个相邻行的某些列中的值已更改。它没有明确告诉您哪些列已更改。您需要执行此额外处理(最有可能在客户端)以您需要的方式显示数据。该解决方案最重要的部分是它将返回到客户端的行数减少到最小。

如果您使用SQL Server 2012或更高版本,它具有函数LAGLEAD,可用于比较上一行/下一行:

示例数据:

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插入临时表或表变量可能会更好。没有LEADLAG的任何其他解决方案都意味着源表至少被读取两次,而在最差的解决方案中,它将是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

的执行计划

plan with LAG

ROW_NUMBER

的执行计划

plan with ROW_NUMBER