postgresql 为一个给定的user_id选择排名

4zcjmb1e  于 2023-08-04  发布在  PostgreSQL
关注(0)|答案(2)|浏览(142)

我有一个表示API端点中的用户活动的下表。

create table crud_logs (
    id           bigint generated always as identity
        constraint pk_crud_logs
            primary key,
    object_type  varchar(255)                           not null,
    object_id    bigint                                 not null,
    action       crudtypes                              not null,
    operation_ts timestamp with time zone default now() not null,
    user_id      bigint
        constraint fk_crud_logs_user_id_users
            references users
            on delete set null
);

字符串
作为用户统计API的一部分,我希望获得给定user_id的排名(数字或%),根据给定时间段内每个用户的行数进行排名。为了简单起见,让我们说从永远。
示例如下:
| 对象类型|对象标识|动作|操作ts|用户id| user_id |
| --|--|--|--|--| ------------ |
| 企业级|五十六|更新了|2023-07-21 12:51:43.904511 +00:00|三十四| 34 |
| 企业级|五十六|更新了|2023-07-21 12:52:46.472828 +00:00|三十四| 34 |
| 企业级|五十六|删除|2023-07-21 13:00:26.499430 +00:00|三十四| 34 |
| 企业级|五十六|更新了|2023-07-21 12:51:43.904511 +00:00|三十四| 34 |
| 企业级|五十六|更新了|2023-07-21 12:52:46.472828 +00:00|三十四| 34 |
| 企业级|五十六|删除|2023-07-21 13:00:26.499430 +00:00|三十四| 34 |
| 企业级|五十六|更新了|2023-07-21 12:51:43.904511 +00:00|三十四| 34 |
| 企业级|五十六|更新了|2023-07-21 12:52:46.472828 +00:00|三十四| 34 |
| 企业级|五十六|删除|2023-07-21 13:00:26.499430 +00:00|五十九| 59 |
| 企业级|五十六|更新了|2023-07-21 12:51:43.904511 +00:00|五十八| 58 |
| 企业级|五十六|更新了|2023-07-21 12:52:46.472828 +00:00|五十九| 59 |
| 企业级|五十六|删除|2023-07-21 13:00:26.499430 +00:00|五十八| 58 |
在这里,user_id = 34将排在第一位,因为该用户具有最多的条目。
我已经设法编写了以下查询:

select user_id, rank() over (order by cnt desc )
from (select user_id, count(*) cnt from crud_logs group by user_id) sq


它适用于所有用户,而我只需要1个特定用户的结果。例如,user_id = 58的预期输出为:

user_id = 58, rank = 2

edqdpe6u

edqdpe6u1#

你写的查询很接近。要获得特定用户的排名,只需在外部SQL查询中添加WHERE子句,以筛选所需的user_id。
以下是如何修改查询:

SELECT user_id, rank 
FROM (
    SELECT user_id, RANK() OVER (ORDER BY cnt DESC) as rank 
    FROM (
        SELECT user_id, COUNT(*) cnt 
        FROM crud_logs 
        GROUP BY user_id
    ) sq
) result
WHERE user_id = 58

字符串
在这个查询中,最里面的子查询按user_id对crud_logs表进行分组,并计算每个用户的条目数。中间的子查询按用户数量降序排列这些用户。最后,外部查询过滤这些结果以返回具有user_id = 58的用户的排名。

xxe27gdn

xxe27gdn2#

基本查询

可以在同一查询级别中将窗口函数rank() Package 在聚合count(*)周围:

SELECT *
FROM  (
   SELECT user_id, rank() OVER (ORDER BY count(*) DESC) AS rank
   FROM   crud_logs 
   GROUP  BY 1
   ) sub
WHERE  user_id = 58;

字符串

微妙问题一:是否忽略null值?

您在user_idON DELETE SET NULL上有一个FK约束-这表明可以删除用户并保留其日志条目,同时将user_id重置为null。“NULL”用户将像任何其他用户一样进行排名,表示已删除用户的集合。我猜你会想把它排除在外。于是:

SELECT *
FROM  (
   SELECT user_id, rank() OVER (ORDER BY count(*) DESC) AS rank
   FROM   crud_logs 
   WHERE  user_id IS NOT NULL  -- !
   GROUP  BY 1
   ) sub
WHERE  user_id = 58;

微妙问题二:固有时间周期

这些时间段(为了简单起见,我们忽略了它们)对于timestamptz类型来说可能很棘手。通常情况下,您需要给定时区中的天/周/月的统计信息。通过正确地考虑时区因素,避免鬼鬼祟祟的角落案例错误。
例如2023年7月在澳大利亚:

SELECT *
FROM  (
   SELECT user_id
        , rank() OVER (ORDER BY count(*) DESC) AS rank
   FROM   crud_logs 
   WHERE  user_id IS NOT NULL  -- !
   AND    operation_ts >= timestamp '2023-07-01' AT TIME ZONE 'Australia/Sydney'  -- !
   AND    operation_ts <  timestamp '2023-08-01' AT TIME ZONE 'Australia/Sydney'  -- !
   GROUP  BY 1
   ) sub
WHERE  user_id = 58;


fiddle
请参阅:

  • 在Rails和PostgreSQL中完全忽略时区
  • 按时间间隔对事件进行分组和计数,加上运行总数

如果表很大,(operation_ts)上的索引应该会有所帮助。

相关问题