【问题标题】:SQL the next nearest recordSQL 下一个最近的记录
【发布时间】:2014-05-19 19:45:45
【问题描述】:

我有表act_detail:

+------+------+--------+
| id   | name | action |
+------+------+--------+
|    1 | Tom  | eat    |
|    2 | Jack | eat    |
|    3 | Tom  | play   |
|    4 | Tom  | sleep  |
|    5 | Tom  | eat    |
|    6 | Jack | sleep  |
|    7 | Tom  | sleep  |
|    8 | Tom  | eat    |
+------+------+--------+

我想获取“吃”的摘要和下一个最近的同名“睡眠”信息:

+------+--------+----------+
| name | eat_id | sleep_id |
+------+--------+----------+
| Tom  |      1 |        4 |
| Jack |      2 |        6 |
| Tom  |      5 |        7 |
| Tom  |      8 |     NULL |
+------+--------+----------+

我发现我可以使用下面的 SQL 得到结果:

SELECT 
    a.name, 
    a.id AS eat_id, 
    (SELECT MIN(id) FROM act_detail b WHERE a.name = b.name AND b.id > a.id AND b.action = 'sleep') AS sleep_id 
FROM act_detail a 
WHERE a.action = 'eat'
ORDER BY a.id;

但是这个 SQL 需要子查询,当需要获取表 b 中的更多列时,需要更多的子查询。有很多记录会很慢。
假设我们可以添加任何索引。 有没有什么有效的方法可以用标准的SQL(可能一个left join,一个临时表,一个group by statement)来解决这个问题?

【问题讨论】:

  • 它不仅是一个子查询,还是一个相关的子查询,因此您正在为父查询中的每一行运行该子查询。但实际上并没有办法解决它,因为您的“下一个更高的 sleep_id”要求需要它。
  • 什么是“大量记录”?请记住:对计算机来说慢的东西通常对我们来说足够快。对我们来说很多的东西,对电脑来说往往是一点点。
  • 似乎您可以在子查询中包含ORDER BY idLIMIT 1,然后去掉MIN()。适当的索引当然会有所帮助。
  • @MarcB 我认为接受的答案是一种有效的方法:)

标签: mysql sql record next


【解决方案1】:

首先获取所有进食动作和所有睡眠动作。加入两者,以便名称匹配并在进食后进行睡眠。然后找到最小距离并添加该距离。

select eat.name, eat.id as eat_id, eat.id + min(sleep.id - eat.id) as sleep_id
from
(
  select id 
  from act_detail
  where action = 'eat'
) eat
left join
(
  select id 
  from act_detail
  where action = 'sleep'
) sleep on sleep.name = eat.name and sleep.id > eat.id
group by eat.name, eat.id;

【讨论】:

    【解决方案2】:

    尝试这个微小的改变来避免聚合函数(每条记录扫描多个子查询记录):

    SELECT 
        a.name, 
        a.id AS eat_id, 
        (SELECT b.id FROM act_detail b
         WHERE b.action = 'sleep'
           AND b.name = a.name
           AND b.id > a.id
         ORDER BY b.id
         LIMIT 1) AS sleep_id 
    FROM act_detail a 
    WHERE a.action = 'eat'
    ORDER BY a.id;
    

    具有以下“覆盖”索引:

    act_detail(action, name, id)
    

    如果这是 InnoDB 并且 id 是您的主键,那么您可以从上述索引中删除 id

    【讨论】:

      【解决方案3】:

      没有子查询:-

      SELECT a.name, a.id AS eat_id, MIN(b.id) AS sleep_id
      FROM act_detail a
      LEFT OUTER JOIN act_detail b 
      ON a.name = b.name
      AND b.action = 'sleep'
      AND b.id > a.id
      WHERE a.action = 'eat'
      GROUP BY a.name, eat_id
      ORDER BY a.id;
      

      这里的 SQL 小提琴:-

      http://www.sqlfiddle.com/#!2/11834/2

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-25
        • 2016-07-23
        • 1970-01-01
        • 2012-11-30
        相关资源
        最近更新 更多