【问题标题】:Quick way to compare differences between ordered SQL records比较有序 SQL 记录之间差异的快速方法
【发布时间】:2015-06-09 19:53:49
【问题描述】:

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

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

我的目标是确定在第 T 天与第 T-1 天相比是否有任何剩余的列发生了变化,如果是这样(比如第 N 列发生了变化,N 可以是任何剩余的列,并且可能有多个列在同一天更改)我想返回日期(第一列)以及截至第 T 天的 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 中的解决方案。

【问题讨论】:

  • “我们可以假设日期是有序的。” - 你不能,除非你以有序的方式查询它们。
  • @GolezTrol 这是正确的,实际上我正在处理一个通过运行一些查询返回的结果集但是为了简化这个问题,我假设我们正在查询的数据来自一个表.

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


【解决方案1】:

您使用什么版本的 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

如果您使用以前的版本,那么从性能的角度来看,使用游标循环遍历行、将当前行与前一行进行比较并将差异插入临时表或表变量中可能会更好。没有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

执行计划ROW_NUMBER

【讨论】:

  • row_number = row_number +1 计划可以使用合并联接,该合并联接对每一侧进行一次评估(总共两次评估)并联接排序结果,而不是那里的计划中的 11 次评估。可能您在使用表变量时看不到这一点,如果添加option(recompile),您可能会看到它。
  • 我同意@MartinSmith。此外,对于真正的大表,必须在date 列上建立索引。执行计划可能会变得更有效率。我的示例对于这种情况并不真正有效,因为这里的表很小,而且它是一个没有索引的表变量。
  • @VladimirBaranov 不幸的是,我正在使用 SQL Server 2008,所以我必须使用冗长的路径。最后,我希望只显示正在更改的字段,但是这个查询已经足够简化结果,所以我可以在表示层上做一些逻辑来得到我想要的,非常感谢你的帮助(以及非常详细的执行计划)!您使用什么软件来制定执行计划,还是我不知道的 SQL 客户端中的某些功能?
  • @AZhu,不客气。标准 SSMS 可以显示执行计划 - 工具栏上有指向 Display estimated execution planInclude actual execution plan 的按钮。事实上,底层的 T-SQL 命令是SET SHOWPLAN_ALLSET STATISTICS XML 和类似的。为了生成这些包含行数的漂亮图片,我使用了免费版的SQL Sentry Plan Explorer。它在内部使用相同的命令,但显示信息比 SSMS 更好。
猜你喜欢
  • 1970-01-01
  • 2019-02-07
  • 2022-01-16
  • 1970-01-01
  • 1970-01-01
  • 2011-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多