PostgreSQL - 不同月份的最大总和与多年的关系

Vér*_*ace 1 postgresql cte greatest-n-per-group

这个问题是关于MySQL 5.6这里的一个问题的PostgreSQL 版本。最初,这是两个 RDBMS 的一个问题,但有人建议我,鉴于两个系统的不同功能,我应该拆分问题 - 特别是我认为 CTE(WITH 子句)应该使查询更加优雅和可读!

假设我有一个肿瘤列表(这个数据是根据真实数据模拟的):

CREATE table illness (nature_of_illness VARCHAR(25), created_at DATETIME);

INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Lung',   '2018-01-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2018-02-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2018-02-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2018-02-03 17:50:32');
INSERT INTO illness VALUES ('Cervix', '2018-02-03 17:50:32');
-- 2017, with 1 Cervix and Lung each for the month of Jan - tie!
INSERT INTO illness VALUES ('Cervix', '2017-01-03 15:45:40');
INSERT INTO illness VALUES ('Lung',   '2017-01-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2017-02-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2017-02-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2017-02-03 17:50:32');
INSERT INTO illness VALUES ('Cervix', '2017-02-03 17:50:32');
Run Code Online (Sandbox Code Playgroud)

您想找出在给定月份中哪个特定肿瘤最常见 - 到目前为止一切顺利!

现在,您会注意到,对于 2017 年的第 1 个月,存在平局 - 因此随机选择一个并给出答案是没有意义的- 因此必须包括平局- 这使问题更具挑战性。

我有一个解决方案,但它非常复杂 - 我想知道我的解决方案是否最佳。PostgreSQL 小提琴来!小提琴中的查询非常麻烦 - 我会看看使用 CTE。

我的第一个答案(适用于 PostgreSQL 和 MySQL)包含在小提琴中,但我不会在这里发布它,因为我相信它将被 PostgreSQL 的卓越功能所取代,它只是我对 MySQL 的答案的副本题!

Erw*_*ter 5

对于给定的月份

SELECT tumour_count, illness
FROM (
   SELECT count(*) AS tumour_count, illness
        , rank() OVER (ORDER BY count(*) DESC) AS rnk
   FROM   illness
   WHERE  created_at  >= '2017-01-01'  -- given month: 2007-01
   AND    created_at  <  '2017-02-01'  -- optimized for index lookup
   GROUP  BY illness
   ) sub
WHERE  rnk = 1;
Run Code Online (Sandbox Code Playgroud)

应该有一个索引(created_at),或者甚至(created_at, illness)可能允许仅索引扫描。

子查询比 Postgres 中的 CTE 快一点。所以只在需要的地方使用 CTE ,或者在性能不重要的时候使用。

有关的:

对于任何给定的时间段

根据评论中的要求

SELECT to_char(mon, 'YYYY-MM') AS month, tumour_count, illness
FROM  (
   SELECT date_trunc('month', created_at) AS mon
        , illness
        , count(*) AS tumour_count
        , rank() OVER (PARTITION BY date_trunc('month', created_at)
                       ORDER BY count(*) DESC) AS rnk
   FROM   illness
   WHERE  created_at  >= '2017-01-01'  -- period from 2007-01 to 2019-01
   AND    created_at  <  '2019-02-01'
   GROUP  BY 1, 2
   ) sub
WHERE  rnk = 1
ORDER  BY mon, illness;
Run Code Online (Sandbox Code Playgroud)

如果您有领先或悬空的部分月份,请小心,计数可能会产生误导。

这在功能上等同于ypercube 已经提供的。只是为了更短/更快一些简化。并在给定的时间段内添加过滤器。

随着从表中读取的行的份额不断增加,索引支持变得不那么重要 - 并且在超过大约 5% 的情况下根本没有用。(例外情况适用,例如仅索引扫描。)

通过聚合绑定对等点,您仍然可以每月拥有1 行。喜欢:

SELECT to_char(mon, 'YYYY-MM') AS month, tumour_count, string_agg(illness, ' | ')
FROM  (
   SELECT date_trunc('month', created_at) AS mon
        , illness
        , count(*) AS tumour_count
        , rank() OVER (PARTITION BY date_trunc('month', created_at)
                       ORDER BY count(*) DESC) AS rnk
   FROM   illness
   WHERE  created_at  >= '2017-01-01'  -- period from 2007-01 to 2019-01
   AND    created_at  <  '2019-02-01'
   GROUP  BY 1, 2
   ) sub
WHERE  rnk = 1
GROUP  BY mon, tumour_count
ORDER  BY mon;
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄