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

AZh*_*Zhu 2 sql t-sql sql-server performance sql-server-2008

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

除了第一列,其它的包含不同的数据类型的数据long,int,nchar,varchar,等.

我的目标是确定在比较第T-1天时是否有任何剩余列在第T天发生了变化(如果是这样的话)(比如列N被更改,N可以是任何剩余列,并且可能有多列被更改在同一天)我想将日期(第一列)与第N天的Col N记录和第T-1天的Col N(可能是该列的标题)一起返回,所以我的输出将是是:

Date T|Header of Col Modified|Value of Col @ T-1|Value of Col @ T
Run Code Online (Sandbox Code Playgroud)

如果有多个更改,我们将为每个更改添加一行.

显然,"慢"方式是首先将所有数据放在某处,然后逐个比较结果数据集.但是我只是想知道是否有任何快速方法在T-SQL中执行它以便我可以直接获得结果(或接近结果的东西,以便查询输出需要很少的数据操作,因为它会SQL Server能够更快地完成繁重的工作,只返回我需要的数据而不是整个表.

我正在使用SQL Server,因此需要一个T-SQL解决方案.

Vla*_*nov 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');
Run Code Online (Sandbox Code Playgroud)

查询使用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;
Run Code Online (Sandbox Code Playgroud)

结果集

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
Run Code Online (Sandbox Code Playgroud)

如果使用某些以前的版本,那么从性能的角度来看,使用游标循环遍历行,将当前行与前一行进行比较并将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;
Run Code Online (Sandbox Code Playgroud)

结果集

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
Run Code Online (Sandbox Code Playgroud)

执行计划比较

两种变体的结果相同,但执行计划非常不同.该LAG变体的相对成本估计为33%,ROW_NUMBER变体的相对成本为67% - 两倍,因为第二个变量扫描并对表格进行两次排序,样本表非常小.此外,您可以看到,在第二个变体中,表格与其自身相连,从而导致读取100行(10*10).如果您的表很大,则效率非常低,使用游标可能会更好.使用光标,您只需扫描一次表格.

执行计划 LAG

计划与LAG

执行计划 ROW_NUMBER

计划与ROW_NUMBER

  • `row_number` =`row_number + 1`计划可以使用合并连接来评估每一侧一次(总共两次评估)并加入排序结果而不是计划中的11次评估.可能你没有看到这个,因为你正在使用表变量,如果你添加`option(重新编译)`你可能会看到它. (2认同)