【问题标题】:Determining first activity for the day per user确定每个用户当天的第一个活动
【发布时间】:2020-06-22 20:13:39
【问题描述】:

我有一个表“UserData”,其中包含以下信息:

User    Date    DateTime        Input
1   8/4/2019    8/4/2019 0:55   Request
1   8/4/2019    8/4/2019 0:56   Ticket
1   8/4/2019    8/4/2019 2:08   Submit
1   8/4/2019    8/4/2019 2:21   Submit
2   8/4/2019    8/4/2019 13:10  Submit
2   8/20/2019   8/20/2019 2:10  Ticket
2   8/20/2019   8/20/2019 2:12  Ticket
2   8/20/2019   8/20/2019 2:13  Request
3   8/20/2019   8/20/2019 2:15  Request
3   8/19/2019   8/19/2019 2:16  Ticket
3   6/12/2020   6/12/2020 2:22  Submit
3   6/12/2020   6/12/2020 2:26  Submit
3   6/12/2020   6/12/2020 3:26  Ticket

我希望返回每个用户的第一个输入(基于时间),但仅在最新的一天。所以我的输出会是这样的:

User    DateTime    Input
1   8/4/2019 0:55   Request
2   8/20/2019 2:10  Ticket
3   6/12/2020 2:22  Submit

我想我需要为每个日期分配一个排名,但不确定从哪里开始。

【问题讨论】:

  • 您的 Postgres 版本和基本数据分布对于确定最佳解决方案至关重要。总共有多少行,有多少不同的用户,大致?另外,是否有一个单独的 users 表,每个相关用户一行?

标签: sql postgresql date greatest-n-per-group window-functions


【解决方案1】:

我了解您希望每个用户每天都有最早的记录。在 Postgres 中,您可以简单地使用 distinct on 来解决这个 top-1-per-group 问题:

select distinct on (u.user, u.date) u.*
from userData u
order by u.user, u.date, u.datetime

如果您想要每个用户的最早记录,无论是哪一天,那么只需:

select distinct on (u.user) u.*
from userData u
order by u.user, u.datetime

编辑:如果你想要最近一天的最早记录,那么:

select distinct on (u.user) u.*
from userData u
order by u.user, u.date desc, u.datetime

【讨论】:

  • “仅在最新的一天”是他们所要求的。 (不是我的反对票)
  • 这个结构很棒。我从来没有遇到过。然而,他想要的输出是“最近一天的最旧记录”。所以排序是u.user, u.date DESC, u.datetime
  • @ChrisHep:是的,我认为你是对的。这只是查询中的一个微小变化,我编辑了我的答案。谢谢。
【解决方案2】:

带有row_number()窗口功能:

select t."User", t."Date", t."DateTime", t."Input"
from (
  select *, row_number() over (partition by "User" order by "Date" desc, "DateTime") rn
  from UserData
) t
where t.rn = 1

对于每个用户,该行按"Date"降序排序以查找最新日期,然后按"DateTime"升序排序以获取当天的第一个输入。
请参阅demo
结果:

| User | Date       | DateTime         | Input   |
| ---- | ---------- | ---------------- | ------- |
| 1    | 2019-08-04 | 2019-08-04 00:55 | Request |
| 2    | 2019-08-20 | 2019-08-20 02:10 | Ticket  |
| 3    | 2020-06-12 | 2020-06-12 02:22 | Submit  |

【讨论】:

    【解决方案3】:
    SELECT * 
    FROM ( User,
           Date,
           Datetime, 
           Input,
           ROW_NUMBER() OVER (
               PARTITION BY User 
               ORDER BY Datetime DESC) dataOrder
           FROM UserData) z
    WHERE z.dataOrder = 1
    

    【讨论】:

    • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
    【解决方案4】:

    我明白你想要:

    每个用户最近一天时间最早的行

    餐桌设计

    对于初学者:删除 Date 列。多余地存储它会增加成本和复杂性,而不是它的价值:

    CREATE TABLE userdata (
      user_id  int
    , datetime timestamp
    , input    text
    );
    

    input 确实应该是一些廉价的枚举实现(enum,FK,...)。
    timestamptz 可能是datetime 的合适类型。要看。见:

    索引

    无论哪种方式,为了让您的操作更快,这是一个完美的索引:

    CREATE INDEX userdata_special_idx ON userdata
    (user_id, (datetime::date) DESC NULLS LAST, datetime);
    

    datetime::date 是一个非常廉价的演员,取代了你多余的日期栏。我仍然将日期添加到multicolumn expression index 以提高性能。 (日期取决于使用timestamptz时的时区。如果您处理多个时区,则需要做更多。)

    注意添加的NULLS LAST:因为您的问题中没有任何内容表明时间戳是NOT NULL,所以您需要在查询中使用它来防止无意义的结果 - 并且索引必须匹配以获得最佳结果。见:

    查询

    每个用户只有几行DISTINCT ON 应该是最佳选择(就像 GMB 已经建议的那样)- 简单而快速:

    SELECT DISTINCT ON (user_id)
           user_id, datetime, input 
    FROM   userdata
    ORDER  BY user_id, datetime::date DESC NULLS LAST, datetime;
    

    见:

    对于每个用户有很多行,这个替代查询应该(基本上)更快:

    SELECT u.user_id, d.*
    FROM   users u
    LEFT   JOIN LATERAL (
       SELECT d.datetime, d.input 
       FROM   userdata d
       WHERE  d.user_id = u.user_id         -- lateral reference
       ORDER  BY d.datetime::date DESC NULLS LAST, d.datetime
       LIMIT  1
       ) d ON true;
    

    通常情况下,这是适合您的方案的方法

    注意LEFT JOIN:它为每个用户返回一行,即使userdata 中没有条目。如果不希望这样做,请改用CROSS JOIN。相关:

    这假设存在users 表,该表通常存在。如果没有,我建议您添加它(出于多种原因)。如果这不是一个选项,仍然有快速的解决方法。见:

    db小提琴here

    旁白:我强烈建议始终使用 ISO 日期格式 (as does the manual)。区域格式取决于当前会话的设置,可能会以丑陋的方式失败。

    【讨论】:

      猜你喜欢
      • 2018-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多