【问题标题】:MySQL - Subtracting value from previous row, group byMySQL - 从前一行中减去值,分组
【发布时间】:2012-11-02 13:27:51
【问题描述】:

我需要根据 SN 号获得基于前一个的消耗值。 这是我的数据:

表能量日志

SN     Date                 Value
2380   2012-10-30 00:15:51  21.01
2380   2012-10-31 00:31:03  22.04
2380   2012-11-01 00:16:02  22.65
2380   2012-11-02 00:15:32  23.11
20100  2012-10-30 00:15:38  35.21
20100  2012-10-31 00:15:48  37.07
20100  2012-11-01 00:15:49  38.17
20100  2012-11-02 00:15:19  38.97
20103  2012-10-30 10:27:34  57.98
20103  2012-10-31 12:24:42  60.83

这是我需要的结果:

SN      Date                 Value  consumption
2380    2012-10-30 00:15:51  21.01  0
2380    2012-10-31 00:31:03  22.04  1.03
2380    2012-11-01 00:16:02  22.65  0.61
2380    2012-11-02 00:15:32  23.11  0.46
20100   2012-10-30 00:15:38  35.21  0
20100   2012-10-31 00:15:48  37.07  1.86
20100   2012-11-01 00:15:49  38.17  1.1
20100   2012-11-02 00:15:19  38.97  0.8
20103   2012-10-30 10:27:34  57.98  0
20103   2012-10-31 12:24:42  60.83  2.85

【问题讨论】:

  • 有自动增量ID吗?
  • 对于每个 SN 编号,它将以 0 开头
  • 对我来说,这听起来像是让数据库呈现您的非规范化应用程序值。在演示循环中做减法是个坏主意吗?

标签: mysql sql


【解决方案1】:

使用 MySQL 变量非常棒,就像内联程序变量赋值一样。首先,FROM 子句为您“声明”@ 变量,默认为空白。然后以您想要的预期顺序查询记录。它对数据进行单次传递,而不是通过可能很耗时的重复子查询。

对于读取的每一行,将@lastSN 与当前记录的SN 进行比较。如果不同,则始终返回 0。如果相同,则计算简单差。只有在比较完成后,将@lastSN 和@lastValue 设置为等于当前记录的值,以进行下一个记录比较。

select
      EL.SN,
      EL.Date,
      EL.Value, --remove duplicate alias
      if( @lastSN = EL.SN, EL.Value - @lastValue, 0000.00 ) as Consumption,
      @lastSN := EL.SN,
      @lastValue := EL.Value
   from
      EnergyLog EL,
      ( select @lastSN := 0,
               @lastValue := 0 ) SQLVars
   order by
      EL.SN,
      EL.Date

【讨论】:

  • 对于大表,这可能是搜索非索引列的最快速度。
  • 是否也可以指定 @lastSN := EL.SN, @lastValue := EL.Value 而无需在结果中实际返回它? (我知道你不必使用它)
  • @Yeti,不,如果您不想在最终结果中看到这些列,只需将此查询再包装一层并仅提取您想要的列(因此没有 at-占位符列)。
  • @DRapp:你拯救了我的一天!谢谢你。
  • 不错的一个!排序依据是非常重要的,但它会使查询变慢。它仍然比对每一行进行子查询要快得多。
【解决方案2】:

这应该可以解决问题:

SELECT l.sn,
       l.date, 
       l.value,
       l.value - (SELECT value 
                  FROM energylog x
                  WHERE x.date < l.date
                  AND x.sn = l.sn
                  ORDER BY date DESC
                  LIMIT 1) consumption
FROM energylog l;

请参阅 SQLFiddle:http://sqlfiddle.com/#!2/b9eb1/8

【讨论】:

  • 在子条款中按日期排序听起来不是一个好主意。
  • @andig 请详细说明
  • 对于每一行energylog,需要再次读取部分energylog表,并对整个结果集进行排序以找到第一行。如果我没记错的话,这意味着对 n 条记录进行 n 次排序操作。
  • @andig - 我知道的旧帖子,但是......如果有适当的索引,这不一定是真的。
  • @MatBailie 不确定优化器是否如此。我更喜欢 max(a) 而不是 sort(a) 限制 1?
【解决方案3】:

一种近乎通用的解决方案是将数据连接到自身,以查找先前的记录,方法是在连接条件中包含相关的子查询...

SELECT
  ThisLog.*,
  COALESCE(ThisLog.Value - PrevLog.Value, 0) AS consumption
FROM
  EnergyLog    AS ThisLog
LEFT JOIN
  EnergyLog    AS PrevLog
    ON  PrevLog.SN   = ThisLog.SN
    AND PrevLog.Date = (SELECT MAX(Date)
                          FROM EnergyLog
                         WHERE SN   = ThisLog.SN
                           AND Date < ThisLog.Date)

这在一个索引同时覆盖(SN, Date) 时表现最佳。

【讨论】:

  • 如果您不能或不想使用@variables,这是一个快速的解决方案。
  • 无论如何改进大记录的查询?
【解决方案4】:

你可以像这样连接同一张表的两行:

    SELECT this.*, prev.*
      FROM tbl this
INNER JOIN tbl prev ON prev.id =
           ( 
               SELECT max(t.id) 
               FROM tbl t  
               WHERE t.id < this.id
           )     
     WHERE ...

所以你的情况看起来像:

    SELECT this.SN, this.Date, this.Value, (this.Value - prev.Value) AS consumption
      FROM EnergyLog this
INNER JOIN EnergyLog prev ON prev.Date =
           ( 
               SELECT max(t.Date) 
               FROM EnergyLog t  
               WHERE t.Date < this.Date
           )     

【讨论】:

    【解决方案5】:

    请您尝试以下查询一次。

    SELECT e1.*,
       (SELECT Value
        FROM EnergyLog e2
        WHERE e2.sn = e1.sn AND e2.date < e1.date
        ORDER BY date DESC
        LIMIT 1)-l.Value consumption
    FROM EnergyLog e1;
    

    【讨论】:

      猜你喜欢
      • 2021-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      • 2013-12-21
      相关资源
      最近更新 更多