为每个用户的一天确定第一个 activity
Determining first activity for the day per user
我有一个 table "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
我正在寻找 return 来自每个用户的第一个输入(基于时间),但仅在最近的一天。所以我的输出会是这样的:
User DateTime Input
1 8/4/2019 0:55 Request
2 8/20/2019 2:10 Ticket
3 6/12/2020 2:22 Submit
我想我需要为每个日期分配一个等级,但不确定从哪里开始。
具有row_number()
window功能:
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 |
我了解您希望每个用户和每天的最早记录。在 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
SELECT *
FROM ( User,
Date,
Datetime,
Input,
ROW_NUMBER() OVER (
PARTITION BY User
ORDER BY Datetime DESC) dataOrder
FROM UserData) z
WHERE z.dataOrder = 1
我明白你想要:
每个用户最近一天时间最早的行
Table设计
对于初学者:删除 Date
列。冗余存储会增加比其价值更多的成本和复杂性:
CREATE TABLE userdata (
user_id int
, datetime timestamp
, input text
);
input
确实应该是一些廉价的枚举实现(enum
,FK,...)。
timestamptz
可能是 datetime
的合适类型。要看。参见:
- Ignoring time zones altogether in Rails and PostgreSQL
索引
无论如何,为了让您的操作更快,这是完美的索引:
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
,因此您在查询中需要它以防止无意义的结果 - 索引必须匹配以获得最佳结果.参见:
- PostgreSQL sort by datetime asc, null first?
查询
仅每个用户只有几行,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 first row in each GROUP BY group?
对于 每个用户的许多行,此替代查询应该(显着)更快:
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
:它 returns 每个用户一行,即使 userdata
中没有条目。如果这不是您想要的,请改用 CROSS JOIN
。相关:
这假设存在 users
table,通常存在。如果没有,我建议您添加它(出于多种原因)。如果那不是一个选项,仍然有快速的解决方法。参见:
- Optimize GROUP BY query to retrieve latest row per user
db<>fiddle here
旁白:我强烈建议始终使用 ISO 日期格式 (as does the manual)。区域格式取决于当前会话的设置,可能会以丑陋的方式失败。
我有一个 table "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
我正在寻找 return 来自每个用户的第一个输入(基于时间),但仅在最近的一天。所以我的输出会是这样的:
User DateTime Input
1 8/4/2019 0:55 Request
2 8/20/2019 2:10 Ticket
3 6/12/2020 2:22 Submit
我想我需要为每个日期分配一个等级,但不确定从哪里开始。
具有row_number()
window功能:
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 |
我了解您希望每个用户和每天的最早记录。在 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
SELECT *
FROM ( User,
Date,
Datetime,
Input,
ROW_NUMBER() OVER (
PARTITION BY User
ORDER BY Datetime DESC) dataOrder
FROM UserData) z
WHERE z.dataOrder = 1
我明白你想要:
每个用户最近一天时间最早的行
Table设计
对于初学者:删除 Date
列。冗余存储会增加比其价值更多的成本和复杂性:
CREATE TABLE userdata (
user_id int
, datetime timestamp
, input text
);
input
确实应该是一些廉价的枚举实现(enum
,FK,...)。
timestamptz
可能是 datetime
的合适类型。要看。参见:
- Ignoring time zones altogether in Rails and PostgreSQL
索引
无论如何,为了让您的操作更快,这是完美的索引:
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
,因此您在查询中需要它以防止无意义的结果 - 索引必须匹配以获得最佳结果.参见:
- PostgreSQL sort by datetime asc, null first?
查询
仅每个用户只有几行,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 first row in each GROUP BY group?
对于 每个用户的许多行,此替代查询应该(显着)更快:
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
:它 returns 每个用户一行,即使 userdata
中没有条目。如果这不是您想要的,请改用 CROSS JOIN
。相关:
这假设存在 users
table,通常存在。如果没有,我建议您添加它(出于多种原因)。如果那不是一个选项,仍然有快速的解决方法。参见:
- Optimize GROUP BY query to retrieve latest row per user
db<>fiddle here
旁白:我强烈建议始终使用 ISO 日期格式 (as does the manual)。区域格式取决于当前会话的设置,可能会以丑陋的方式失败。