【问题标题】:Select row with most recent date per user选择每个用户最近日期的行
【发布时间】:2013-06-06 22:53:12
【问题描述】:

我有一个用户签入和签出时间表(“lms_attendance”),如下所示:

id  user    time    io (enum)
1   9   1370931202  out
2   9   1370931664  out
3   6   1370932128  out
4   12  1370932128  out
5   12  1370933037  in

我正在尝试创建此表的视图,该视图将仅输出每个用户 ID 的最新记录,同时给我“输入”或“输出”值,例如:

id  user    time    io
2   9   1370931664  out
3   6   1370932128  out
5   12  1370933037  in

到目前为止,我已经很接近了,但我意识到视图不会接受子查询,这使得它变得更加困难。我得到的最接近的查询是:

select 
    `lms_attendance`.`id` AS `id`,
    `lms_attendance`.`user` AS `user`,
    max(`lms_attendance`.`time`) AS `time`,
    `lms_attendance`.`io` AS `io` 
from `lms_attendance` 
group by 
    `lms_attendance`.`user`, 
    `lms_attendance`.`io`

但我得到的是:

id  user    time    io
3   6   1370932128  out
1   9   1370931664  out
5   12  1370933037  in
4   12  1370932128  out

这很接近,但并不完美。我知道最后一个 group by 不应该在那里,但是没有它,它会返回最近的时间,但不会返回它的相对 IO 值。

有什么想法吗? 谢谢!

【问题讨论】:

  • 返回手册。您会看到,无论有无(相关和不相关)子查询,它都提供了解决此问题的方法。
  • @Barmar,从技术上讲,正如我在回答中指出的那样,这是带有 greatest-n-per-group 标签的所有 700 个问题的副本。
  • @Prodikl,什么是“io(枚举)”?
  • 我有一个名为“IO”的列,代表“输入或输出”,它是一个枚举类型,可能值为“输入”或“输出”。这用于跟踪人们何时签入和签出班级。

标签: mysql sql greatest-n-per-group


【解决方案1】:

您可以按用户分组,然后按时间顺序排序。如下所示

  SELECT * FROM lms_attendance group by user order by time desc;

【讨论】:

    【解决方案2】:
    select b.* from 
    
        (select 
            `lms_attendance`.`user` AS `user`,
            max(`lms_attendance`.`time`) AS `time`
        from `lms_attendance` 
        group by 
            `lms_attendance`.`user`) a
    
    join
    
        (select * 
        from `lms_attendance` ) b
    
    on a.user = b.user
    and a.time = b.time
    

    【讨论】:

    • 谢谢。我知道我可以使用子查询来做到这一点,但我希望把它变成一个视图,它不允许在视图 AFAIK 中使用子查询。我是否必须将每个子查询转换为视图等?
    • join (select * from lms_attendance ) b = join lms_attendance b
    【解决方案3】:

    查询:

    SQLFIDDLEExample

    SELECT t1.*
    FROM lms_attendance t1
    WHERE t1.time = (SELECT MAX(t2.time)
                     FROM lms_attendance t2
                     WHERE t2.user = t1.user)
    

    结果:

    | ID | USER |       TIME |  IO |
    --------------------------------
    |  2 |    9 | 1370931664 | out |
    |  3 |    6 | 1370932128 | out |
    |  5 |   12 | 1370933037 |  in |
    

    请注意,如果用户有多个“最大”时间相同的记录,则上面的查询将返回多个记录。如果您只希望每个用户有 1 条记录,请使用以下查询:

    SQLFIDDLEExample

    SELECT t1.*
    FROM lms_attendance t1
    WHERE t1.id = (SELECT t2.id
                     FROM lms_attendance t2
                     WHERE t2.user = t1.user            
                     ORDER BY t2.id DESC
                     LIMIT 1)
    

    【讨论】:

    • 哇!不仅这样做了,我还被允许使用这个查询创建一个视图,即使它包含子查询。以前,当我试图创建一个包含子查询的视图时,它没有让我这样做。是否有关于为什么允许这样做但另一个不允许的规则?
    • 很奇怪。万分感谢!也许是因为我的子查询是我选择 FROM 的伪表,在此示例中它用于 WHERE 子句。
    • 不需要子查询!此外,这个解决方案doesn't work if there are two records with exactly the same time。无需每次都尝试重新发明轮子,因为这是常见问题 - 相反,请选择已经测试和优化的解决方案 - @Prodikl 看到我的答案。
    • 啊,感谢您的洞察力!我明天在办公室时会尝试新代码。
    • @TMS 如果记录具有完全相同的时间,则此解决方案确实有效,因为查询正在查找具有最大 id 的记录。这意味着表中的时间是插入时间,这可能不是一个好的假设。您的解决方案改为比较时间戳,当两个时间戳相同时,您也返回具有最大 id 的行。因此,您的解决方案还假设此表中的时间戳与插入顺序有关,这是您的两个查询的最大缺陷。
    【解决方案4】:

    无需尝试重​​新发明轮子,因为这很常见greatest-n-per-group problem。非常好solution is presented

    我更喜欢没有子查询的最简单的解决方案 (see SQLFiddle, updated Justin's)(因此易于在视图中使用):

    SELECT t1.*
    FROM lms_attendance AS t1
    LEFT OUTER JOIN lms_attendance AS t2
      ON t1.user = t2.user 
            AND (t1.time < t2.time 
             OR (t1.time = t2.time AND t1.Id < t2.Id))
    WHERE t2.user IS NULL
    

    这也适用于同一组中有两个具有相同最大值的不同记录的情况 - 这要归功于 (t1.time = t2.time AND t1.Id &lt; t2.Id) 的技巧。我在这里所做的只是确保如果同一用户的两条记录具有相同的时间,则只选择一条。标准是 Id 还是其他东西实际上并不重要 - 基本上任何保证唯一的标准都可以在这里工作。

    【讨论】:

    • 最大值使用t1.time &lt; t2.time,最小值是t1.time &gt; t2.time,这与我最初的直觉相反。
    • @J.Money 因为隐藏了隐式否定:您从 t1 中选择所有记录,这些记录 没有 来自 t2 的对应记录,其中 t1.time &lt; t2.time 条件适用:- )
    • WHERE t2.user IS NULL 有点奇怪。这条线起什么作用?
    • 贾斯汀发布的公认答案可能更理想。接受的答案是对表的主键使用反向索引扫描,然后是一个限制,然后是对表的顺序扫描。因此,可以通过附加索引大大优化接受的答案。该查询也可以通过索引进行优化,因为它执行两次序列扫描,还包括序列扫描结果的哈希和“哈希反连接”以及另一个序列扫描的哈希。我有兴趣解释哪种方法真正更优化。
    • @TMS 你能澄清一下OR (t1.time = t2.time AND t1.Id &lt; t2.Id)) 部分吗?
    【解决方案5】:

    已经解决了,但为了记录,另一种方法是创建两个视图...

    CREATE TABLE lms_attendance
    (id int, user int, time int, io varchar(3));
    
    CREATE VIEW latest_all AS
    SELECT la.user, max(la.time) time
    FROM lms_attendance la 
    GROUP BY la.user;
    
    CREATE VIEW latest_io AS
    SELECT la.* 
    FROM lms_attendance la
    JOIN latest_all lall 
        ON lall.user = la.user
        AND lall.time = la.time;
    
    INSERT INTO lms_attendance 
    VALUES
    (1, 9, 1370931202, 'out'),
    (2, 9, 1370931664, 'out'),
    (3, 6, 1370932128, 'out'),
    (4, 12, 1370932128, 'out'),
    (5, 12, 1370933037, 'in');
    
    SELECT * FROM latest_io;
    

    Click here to see it in action at SQL Fiddle

    【讨论】:

    • 感谢您的跟进!是的,如果没有更简单的方法,我将创建多个视图。再次感谢
    【解决方案6】:

    基于@TMS 的回答,我喜欢它,因为不需要子查询,但我认为省略'OR' 部分就足够了,而且更易于理解和阅读。

    SELECT t1.*
    FROM lms_attendance AS t1
    LEFT JOIN lms_attendance AS t2
      ON t1.user = t2.user 
            AND t1.time < t2.time
    WHERE t2.user IS NULL
    

    如果您对具有空时间的行不感兴趣,可以在 WHERE 子句中过滤它们:

    SELECT t1.*
    FROM lms_attendance AS t1
    LEFT JOIN lms_attendance AS t2
      ON t1.user = t2.user 
            AND t1.time < t2.time
    WHERE t2.user IS NULL and t1.time IS NOT NULL
    

    【讨论】:

    • 如果两条记录可以有相同的time,那么省略OR 部分是一个非常糟糕的主意。
    • 出于性能考虑,我会避免使用此解决方案。正如@OlegKuts 提到的,这在大中型数据集上变得非常缓慢。
    【解决方案7】:

    试试这个查询:

      select id,user, max(time), io 
      FROM lms_attendance group by user;
    

    【讨论】:

    • 尝试制作一个 SQLFiddle。您可能会发现idio 是非聚合列,不能在group by 中使用。
    • 不保证 id 会是 max(time) 的 id,它可以是组内的任何 id。这是我来这里要解决的问题,仍在寻找
    【解决方案8】:

    这对我有用:

    SELECT user, time FROM 
    (
        SELECT user, time FROM lms_attendance --where clause
    ) AS T 
    WHERE (SELECT COUNT(0) FROM table WHERE user = T.user AND time > T.time) = 0
    ORDER BY user ASC, time DESC
    

    【讨论】:

      【解决方案9】:
       select result from (
           select vorsteuerid as result, count(*) as anzahl from kreditorenrechnung where kundeid = 7148
           group by vorsteuerid
       ) a order by anzahl desc limit 0,1
      

      【讨论】:

        【解决方案10】:

        好的,这可能是黑客攻击或容易出错,但不知何故这也能正常工作-

        SELECT id, MAX(user) as user, MAX(time) as time, MAX(io) as io FROM lms_attendance GROUP BY id;
        

        【讨论】:

          【解决方案11】:

          如果您使用的是 MySQL 8.0 或更高版本,您可以使用 Window functions:

          查询:

          DBFiddleExample

          SELECT DISTINCT
          FIRST_VALUE(ID) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS ID,
          FIRST_VALUE(USER) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS USER,
          FIRST_VALUE(TIME) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS TIME,
          FIRST_VALUE(IO) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS IO
          FROM lms_attendance;
          

          结果:

          | ID | USER |       TIME |  IO |
          --------------------------------
          |  2 |    9 | 1370931664 | out |
          |  3 |    6 | 1370932128 | out |
          |  5 |   12 | 1370933037 |  in |
          

          我看到使用 solution proposed by Justin 的优势在于,它使您能够选择具有每个用户(或每个 id,或任何其他)最新数据的行,甚至可以从子查询中选择,而无需中间视图或表.

          如果您运行 HANA,它也会快 7 倍:D

          【讨论】:

          • 是否需要将FIRST_VALUE() 添加到要提取的每个字段中?
          • 由于 OP 询问获取每个用户最近日期的值,这需要按日期排序并取第一个值。如果您没有以某种方式将每个窗口函数的结果集减少到 1 行,那么我想使用它是没有意义的
          • 我的意思是,有没有办法避免在您想要提取的每个值上重复 FIRST_VALUE()PARTITION BY &lt;x&gt; ORDER BY &lt;y&gt; DESC
          • 我想是的.. 但我不确定。也许这会是一个很好的 SO 问题?
          【解决方案12】:

          我也做过类似下面的事情

          选择 t1.* 来自 lms_attendance t1 WHERE t1.id in (SELECT max(t2.id) as id 来自 lms_attendance t2 由 t2.user 分组)

          这也会降低内存使用率。

          谢谢。

          【讨论】:

            【解决方案13】:

            我尝试了一种适合我的解决方案

                SELECT user, MAX(TIME) as time
                  FROM lms_attendance
                  GROUP by user
                  HAVING MAX(time)
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-10-04
              • 2017-11-22
              • 1970-01-01
              • 2016-05-02
              • 1970-01-01
              • 2014-09-17
              • 1970-01-01
              相关资源
              最近更新 更多