【问题标题】:Select rows where column value has changed选择列值已更改的行
【发布时间】:2012-06-23 01:20:59
【问题描述】:

假设我有下表:

Value    Time
0        15/06/2012 8:03:43 PM
1        15/06/2012 8:03:43 PM     *
1        15/06/2012 8:03:48 PM 
1        15/06/2012 8:03:53 PM
1        15/06/2012 8:03:58 PM     
2        15/06/2012 8:04:03 PM     *
2        15/06/2012 8:04:08 PM
3        15/06/2012 8:04:13 PM     *
3        15/06/2012 8:04:18 PM
3        15/06/2012 8:04:23 PM
2        15/06/2012 8:04:28 PM     *
2        15/06/2012 8:04:33 PM     

如何选择已加星标的行,即Value 已更改的位置?基本上我试图找出Value 发生变化的时间,以便我可以根据这些时间间隔进行其他查询。解决方案不应依赖于提前知道ValueTime

在我看来,这应该不是很难(但显然对我来说已经够难了!)。

我目前使用的是 SQL Server 2008,但如果新的窗口/分析功能有用,我可以访问 2012。

我尝试在http://blog.sqlauthority.com/2011/11/24/sql-server-solution-to-puzzle-simulate-lead-and-lag-without-using-sql-server-2012-analytic-function/ 调整解决方案,但一个小时后我的查询没有完成!我认为连接将行大小爆炸到无法管理的程度(或者我搞砸了)。

我可以通过 C# 代码和多个 db 调用来解决这个问题,但似乎可以在表值函数或 SP 中完成,这样会更好。

此外,如果更简单,仅适用于增加 Value 的解决方案也可以。

【问题讨论】:

    标签: sql sql-server-2008 tsql select


    【解决方案1】:

    我们也可以使用子查询来做到这一点

    SELECT sub1.value, sub1.time FROM 
      (SELECT *,rn,id FROM 
         (SELECT *,row_number() over (partition by value order by time) AS rn, row_number() over (order by time) AS id FROM x ) order by time) sub1
      LEFT OUTER JOIN 
      (SELECT *,rn,id FROM 
         (SELECT *,row_number() over (partition by value order by time) AS rn, row_number() over (order by time) AS id FROM x ) order by time) sub2
      ON sub1.id = sub2.id + 1 
      WHERE sub1.rn - sub2.rn <> 1 OR sub2.rn IS NULL;
    

    所以,我比较了 2 行的值,如果它发生变化,那么 rn 的差值将不等于 1,否则 rn 值将增加 1,所以,我选择了与下一行的 rn 值差为的所有行not 1 and sub2.rn IS NULL 用于第一行,因为连接将从 id = 2 开始。

    【讨论】:

      【解决方案2】:
      DECLARE @x TABLE(value INT, [time] DATETIME)
      
      INSERT @x VALUES
      (0,'20120615 8:03:43 PM'),--
      (1,'20120615 8:03:43 PM'),--*
      (1,'20120615 8:03:48 PM'),--
      (1,'20120615 8:03:53 PM'),--
      (1,'20120615 8:03:58 PM'),--
      (2,'20120615 8:04:03 PM'),--*
      (2,'20120615 8:04:08 PM'),--
      (3,'20120615 8:04:13 PM'),--*
      (3,'20120615 8:04:18 PM'),--
      (3,'20120615 8:04:23 PM'),--
      (2,'20120615 8:04:28 PM'),--*
      (2,'20120615 8:04:33 PM');
      
      
      ; with temp as
      (
      SELECT 
          value, [time],  lag(value,1,-1) over (order by [time] ) as lastValue
      FROM    @x
      ) 
      SELECT 
          [value],[time] 
      FROM 
          temp 
      WHERE value <> lastValue
      

      结果:

      value   time
      ---------------------------
      0   2012-06-15 20:03:43.000
      1   2012-06-15 20:03:43.000
      2   2012-06-15 20:04:03.000
      3   2012-06-15 20:04:13.000
      2   2012-06-15 20:04:28.000
      

      【讨论】:

      • 整洁,但值得一提的是,这仅从 SQL 2012 开始支持。
      • 这速度超级快!以前版本的查询工作了 11 分钟,之后我不得不取消它。
      • LAG() 和 LEAD() 是超级有用的窗口函数 - 我在 Presto DB 上使用它们,它们通过相同的数据集保存重复扫描 - 更快
      【解决方案3】:

      我想这就是你所追求的:

      ;WITH x AS
      (
        SELECT value, time, rn = ROW_NUMBER() OVER 
        (PARTITION BY Value ORDER BY Time)
        FROM dbo.table
      )
      SELECT * FROM x WHERE rn = 1;
      

      如果结果集很大并且没有良好的支持索引,这可能会很慢...

      编辑

      啊,等一下,值会上升和下降,而不仅仅是上升...如果是这种情况,您可以尝试这种慢得多的方法:

      DECLARE @x TABLE(value INT, [time] DATETIME)
      
      INSERT @x VALUES
      (0,'20120615 8:03:43 PM'),--
      (1,'20120615 8:03:43 PM'),--*
      (1,'20120615 8:03:48 PM'),--
      (1,'20120615 8:03:53 PM'),--
      (1,'20120615 8:03:58 PM'),--
      (2,'20120615 8:04:03 PM'),--*
      (2,'20120615 8:04:08 PM'),--
      (3,'20120615 8:04:13 PM'),--*
      (3,'20120615 8:04:18 PM'),--
      (3,'20120615 8:04:23 PM'),--
      (2,'20120615 8:04:28 PM'),--*
      (2,'20120615 8:04:33 PM');
      
      ;WITH x AS
      (
        SELECT *, rn = ROW_NUMBER() OVER (ORDER BY time)
        FROM @x
      )
      SELECT x.value, x.[time]
      FROM x LEFT OUTER JOIN x AS y
      ON x.rn = y.rn + 1
      AND x.value <> y.value
      WHERE y.value IS NOT NULL;
      

      结果:

      value  time
      -----  -----------------------
      1      2012-06-15 20:03:43.000
      2      2012-06-15 20:04:03.000
      3      2012-06-15 20:04:13.000
      2      2012-06-15 20:04:28.000
      

      【讨论】:

      • 哇,真快。增值是可以的。我试试看。
      • 太棒了,谢谢@Aaron Bertrand。根据我的数据,第二个版本花费的时间不到第一个版本的两倍,因此性能差异并不算太差。
      • 如何将此查询转换为在结果输出中也包含一个 ID(新列)?
      • @ThinkCode 您需要更具体地说明您在该列中想要的内容。现在我可以把任何东西放在一个新的专栏里,但这不会很有用。
      • @Aaron : pastebin.com/LT0ygatC 我们现在有两个不同的 ID,而不是为一个 ID 处理所有行。希望我说得通!谢谢
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-23
      • 2019-03-17
      相关资源
      最近更新 更多