【问题标题】:How to get only latest record from different ranges?如何只获取不同范围的最新记录?
【发布时间】:2017-08-11 08:27:52
【问题描述】:

我正在研究一个案例,其中我们有许多装满液体的水箱。测量液体量并将信息存储在数据库中。此更新每 5 分钟完成一次。这里存储了以下信息:

  • 坦克ID
  • 填充级别
  • 时间戳

每个水箱都归类在以下“液位”范围之一:

  • 范围 A:0 - 40%
  • 范围 B:40 - 75%
  • 范围 C:75 - 100%

每个范围我计算每个 tankId 的事件数量。

SELECT sum(
        CASE
            WHEN filllevel>=0 and filllevel<40 
            THEN 1
            ELSE 0
        END) AS 'Range A',
        sum(
        CASE
            WHEN filllevel>=40 and filllevel<=79 
            THEN 1
            ELSE 0
        END) AS 'Range B',
        sum(
        CASE
            WHEN filllevel>79 and filllevel<=100 
            THEN 1
            ELSE 0
        END) AS 'Range C'
FROM TEST ;

挑战在于只计算每个坦克的最新记录。因此,对于每个 tankId,只有一个计数(并且必须是具有最新时间戳的记录)。

对于以下数据:

insert into tank_db1.`TEST` (ts, tankId, fill_level) values 
('2017-08-11 03:31:18', 'tank1', 10),
('2017-08-11 03:41:18', 'tank1', 45),
('2017-08-11 03:51:18', 'tank1', 95),
('2017-08-11 03:31:18', 'tank2', 20),
('2017-08-11 03:41:18', 'tank2', 30),
('2017-08-11 03:51:18', 'tank2', 80),
('2017-08-11 03:31:18', 'tank3', 30),
('2017-08-11 03:41:18', 'tank3', 45),
('2017-08-11 03:51:18', 'tank4', 55);

我希望结果是(仅计算每个 tankId 具有最新时间戳的记录):

- RANGE A: 0
- RANGE B: 1 (tankdId 3)
- RANGE C: 2 (tankId 1 and tankId2)

如果您是专家,这可能很容易,但对我来说,很难看到有哪些选择。

谢谢

【问题讨论】:

  • 您需要坦克的数量还是坦克的名称?
  • “只是”每个范围的坦克总数。但是,如果坦克名称列表很简单,我也许可以使用它:-)
  • 范围B 的值应该是2,因为还有tankId=4
  • 锐利的眼睛!。 tankId=4 确实是一个错字(但应该可以)

标签: mysql range


【解决方案1】:

您可以使用以下查询来获取最新的每组时间戳值:

select tankId, max(ts) as max_ts
from test
group by tankId;

输出:

    tankId  max_ts
--------------------------------
1   tank1   11.08.2017 03:51:18
2   tank2   11.08.2017 03:51:18
3   tank3   11.08.2017 03:41:18
4   tank4   11.08.2017 03:51:18

使用上述查询作为派生表,您可以提取每组的最新fill_level 值。这样您就可以应用计算每个范围级别的逻辑:

select sum(
    CASE
        WHEN t1.fill_level>=0 and t1.fill_level<40 
        THEN 1
        ELSE 0
    END) AS 'Range A',
    sum(
    CASE
        WHEN t1.fill_level>=40 and t1.fill_level<=79 
        THEN 1
        ELSE 0
    END) AS 'Range B',
    sum(
    CASE
        WHEN t1.fill_level>79 and t1.fill_level<=100 
        THEN 1
        ELSE 0
    END) AS 'Range C'
from test as t1
join (
   select tankId, max(ts) as max_ts
   from test
   group by tankId
) as t2 on t1.tankId = t2.tankId and t1.ts = t2.max_ts

输出:

    Range A Range B Range C
---------------------------
1   0       2       2

Demo here

【讨论】:

  • 哇!非常感谢。我印象深刻,这将花费我至少一天的时间来弄清楚这一点。我将尝试实施它。稍后让您知道结果!
【解决方案2】:

我得到了不同的结果(哦,好吧,结果与 GB 相同):

SELECT GROUP_CONCAT(CASE WHEN fill_level < 40 THEN x.tankid END) range_a
     , GROUP_CONCAT(CASE WHEN fill_level BETWEEN 40 AND 75 THEN x.tankid END) range_b
     , GROUP_CONCAT(CASE WHEN fill_level > 75 THEN x.tankid END) range_c
  FROM test x
  JOIN (SELECT tankid,MAX(ts) ts FROM test GROUP BY tankid) y
    ON y.tankid = x.tankid AND y.ts = x.ts;
+---------+-------------+-------------+
| range_a | range_b     | range_c     |
+---------+-------------+-------------+
| NULL    | tank3,tank4 | tank1,tank2 |
+---------+-------------+-------------+

编辑:

如果我正在解决这个问题,并且想在结果中包含坦克名称,那么我可能会执行以下...

   SELECT x.* 
     FROM test x 
     JOIN 
        ( SELECT tankid,MAX(ts) ts FROM test GROUP BY tankid) y
       ON y.tankid = x.tankid 
      AND y.ts = x.ts

...并处理所有其他问题,包括应用程序代码中的计数、范围和缺失/'0' 值。

【讨论】:

  • 这也包括坦克的名称。我很确定我也会使用该信息。谢谢
  • @Jeroen 我这样写只是为了说明你的结果集是错误的。如果您真的想在结果中包含名称,那么就个人而言,我会以稍微不同的方式进行处理。我可以详细说明,但基本上我认为(几乎)没有适合在工作查询中使用 GROUP_CONCAT(或 CONCAT)的实例。
  • 是的,我打错了字并创建了另一个 tankId。为什么不使用 GROUP_CONCAT,还有什么其他方式更合适?
  • 见上面的编辑。 GROUP_CONCAT 问题与查询的可伸缩性和结果集的灵活性/延展性有关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-13
  • 2018-10-17
  • 1970-01-01
  • 2013-06-25
  • 2018-10-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多