获得 10 个不同的项目以及相关任务的最新更新
Get 10 distinct projects with the latest updates in related tasks
我在 PostgreSQL 9.5 数据库中有两个表:
project
- id
- name
task
- id
- project_id
- name
- updated_at
有 ~ 1000 个项目(很少更新)和~1000 万个任务(经常更新)。
我想列出这 10 个具有最新任务更新的不同项目。
基本查询是:
SELECT * FROM task ORDER BY updated_at DESC LIMIT 10;
但是,每个项目可以有很多更新任务。所以我不会得到 10 个独特的项目。
如果我尝试在查询中的某处添加 DISTINCT(project_id)
,则会出现错误:
for SELECT DISTINCT, ORDER BY expressions must appear in select list
问题是,我无法(主要)按 project_id
排序,因为我需要按时间对任务进行排序。按 updated_at DESC, project_id ASC
排序也不起作用,因为 相同 项目的几个任务可能是最新的。
我无法下载所有记录,因为有数百万条记录。
作为一种解决方法,我下载了 10 倍所需的行(没有不同的)范围,并在后端过滤它们。这适用于大多数情况,但显然不可靠:有时我没有得到 10 个独特的项目。
这个问题可以在 Postgres 9.5 中有效解决吗?
例子
id | name
----+-----------
1 | Project 1
2 | Project 2
3 | Project 3
id | project_id | name | updated_at
----+------------+--------+-----------------
1 | 1 | Task 1 | 13:12:43.361387
2 | 1 | Task 2 | 13:12:46.369279
3 | 2 | Task 3 | 13:12:54.680891
4 | 3 | Task 4 | 13:13:00.472579
5 | 3 | Task 5 | 13:13:04.384477
如果我查询:
SELECT project_id, updated_at FROM task ORDER BY updated_at DESC LIMIT 2
我得到:
project_id | updated_at
------------+-----------------
3 | 13:13:04.384477
3 | 13:13:00.472579
但我想获得 2 个 distinct 项目,它们各自的最新 task.update_at
像这样:
project_id | updated_at
------------+-----------------
3 | 13:13:04.384477
2 | 13:12:54.680891 -- from Task 3
如何按最近更新对记录进行排序,然后执行 distinct on
?
select distinct on (t.project_id) t.*
from tasks t
order by max(t.update_date) over (partition by t.project_id), t.project_id;
编辑:
我不知道 Postgres 做了那个检查。这是带有子查询的版本:
select distinct on (maxud, t.project_id) t.*
from (select t.*,
max(t.update_date) over (partition by t.project_id) as maxud
from tasks t
) t
order by maxud, t.project_id;
您可以将分析调用放在 distinct on
中,但我认为这样更清楚。
尝试按表达式分组,这就是它的目的:
SELECT project_id, max(update_date) as max_upd_date
FROM task t
GROUP BY project_id
order by max_upd_date DESC
LIMIT 10
如果您想避免完全 table 扫描,请不要忘记放置以 project_id、update_date 开头的索引。
嗯,使用索引的唯一方法似乎是使用相关子查询:
select p.id,
(select upd_dte from task t where p.id = t.prj_id order by upd_dte desc limit 1) as max_dte
from project p
order by max_dte desc
limit 10
尝试使用
SELECT project_id,
Max (updated_at)
FROM task
GROUP BY project_id
ORDER BY Max(updated_at) DESC
LIMIT 10
我相信 row_number() over() 可以用于此,但您仍然需要最终的 order by 和 limit 子句:
select
mt.*
from (
SELECT
* , row_number() over(partition by project_id order by updated_at DESC) rn
FROM tasks
) mt
-- inner join Projects p on mt.project_id = p.id
where mt.rn = 1
order by mt.updated_at DESC
limit 2
这种方法的优点是您可以访问与每个项目的最大值 updated_at 相对应的完整行。您也可以选择加入项目 table
结果:
| id | project_id | name | updated_at | rn |
|----|------------|--------|-----------------|----|
| 5 | 3 | Task 5 | 13:13:04.384477 | 1 |
| 3 | 2 | Task 3 | 13:12:54.680891 | 1 |
简单(逻辑上正确)的解决方案是聚合任务以获得每个项目的最新更新,然后选择最新的 10 个, 提供。
但是,这会在 task
上引发 顺序扫描 ,这对于 big[=61 来说是不可取的(昂贵的) =] 表。
如果您的项目相对较少(每个项目有很多任务条目),则可以使用(位图)索引扫描进行更快的替代。
SELECT *
FROM project p
, LATERAL (
SELECT updated_at AS last_updated_at
FROM task
WHERE project_id = p.id
ORDER BY updated_at DESC
LIMIT 1
) t
ORDER BY t.last_updated_at
LIMIT 10;
性能的关键是匹配的多列索引:
CREATE INDEX task_project_id_updated_at ON task (project_id, updated_at DESC);
具有 1000 个项目和 1000 万个任务的设置(如您评论的那样)是一个完美的选择。
背景:
- Optimize GROUP BY query to retrieve latest record per user
- Select first row in each GROUP BY group?
NULL
和 "no row"
以上解决方案假定 updated_at
定义为 NOT NULL
。否则使用 ORDER BY updated_at DESC
NULLS LAST
并理想地使索引匹配。
没有任何任务的项目通过隐式 CROSS JOIN
从结果中 消除。 NULL
价值观不能以这种方式蔓延。这与 等相关子查询略有不同:"no row" 的那些 return NULL 值(项目根本没有相关任务)。除非另有说明,否则外部降序排序顺序会在顶部列出 NULL
。很可能不是你想要的。
相关:
- PostgreSQL sort by datetime asc, null first?
我在 PostgreSQL 9.5 数据库中有两个表:
project
- id
- name
task
- id
- project_id
- name
- updated_at
有 ~ 1000 个项目(很少更新)和~1000 万个任务(经常更新)。
我想列出这 10 个具有最新任务更新的不同项目。
基本查询是:
SELECT * FROM task ORDER BY updated_at DESC LIMIT 10;
但是,每个项目可以有很多更新任务。所以我不会得到 10 个独特的项目。
如果我尝试在查询中的某处添加 DISTINCT(project_id)
,则会出现错误:
for SELECT DISTINCT, ORDER BY expressions must appear in select list
问题是,我无法(主要)按 project_id
排序,因为我需要按时间对任务进行排序。按 updated_at DESC, project_id ASC
排序也不起作用,因为 相同 项目的几个任务可能是最新的。
我无法下载所有记录,因为有数百万条记录。
作为一种解决方法,我下载了 10 倍所需的行(没有不同的)范围,并在后端过滤它们。这适用于大多数情况,但显然不可靠:有时我没有得到 10 个独特的项目。
这个问题可以在 Postgres 9.5 中有效解决吗?
例子
id | name
----+-----------
1 | Project 1
2 | Project 2
3 | Project 3
id | project_id | name | updated_at
----+------------+--------+-----------------
1 | 1 | Task 1 | 13:12:43.361387
2 | 1 | Task 2 | 13:12:46.369279
3 | 2 | Task 3 | 13:12:54.680891
4 | 3 | Task 4 | 13:13:00.472579
5 | 3 | Task 5 | 13:13:04.384477
如果我查询:
SELECT project_id, updated_at FROM task ORDER BY updated_at DESC LIMIT 2
我得到:
project_id | updated_at
------------+-----------------
3 | 13:13:04.384477
3 | 13:13:00.472579
但我想获得 2 个 distinct 项目,它们各自的最新 task.update_at
像这样:
project_id | updated_at
------------+-----------------
3 | 13:13:04.384477
2 | 13:12:54.680891 -- from Task 3
如何按最近更新对记录进行排序,然后执行 distinct on
?
select distinct on (t.project_id) t.*
from tasks t
order by max(t.update_date) over (partition by t.project_id), t.project_id;
编辑:
我不知道 Postgres 做了那个检查。这是带有子查询的版本:
select distinct on (maxud, t.project_id) t.*
from (select t.*,
max(t.update_date) over (partition by t.project_id) as maxud
from tasks t
) t
order by maxud, t.project_id;
您可以将分析调用放在 distinct on
中,但我认为这样更清楚。
尝试按表达式分组,这就是它的目的:
SELECT project_id, max(update_date) as max_upd_date
FROM task t
GROUP BY project_id
order by max_upd_date DESC
LIMIT 10
如果您想避免完全 table 扫描,请不要忘记放置以 project_id、update_date 开头的索引。
嗯,使用索引的唯一方法似乎是使用相关子查询:
select p.id,
(select upd_dte from task t where p.id = t.prj_id order by upd_dte desc limit 1) as max_dte
from project p
order by max_dte desc
limit 10
尝试使用
SELECT project_id,
Max (updated_at)
FROM task
GROUP BY project_id
ORDER BY Max(updated_at) DESC
LIMIT 10
我相信 row_number() over() 可以用于此,但您仍然需要最终的 order by 和 limit 子句:
select
mt.*
from (
SELECT
* , row_number() over(partition by project_id order by updated_at DESC) rn
FROM tasks
) mt
-- inner join Projects p on mt.project_id = p.id
where mt.rn = 1
order by mt.updated_at DESC
limit 2
这种方法的优点是您可以访问与每个项目的最大值 updated_at 相对应的完整行。您也可以选择加入项目 table
结果:
| id | project_id | name | updated_at | rn |
|----|------------|--------|-----------------|----|
| 5 | 3 | Task 5 | 13:13:04.384477 | 1 |
| 3 | 2 | Task 3 | 13:12:54.680891 | 1 |
简单(逻辑上正确)的解决方案是聚合任务以获得每个项目的最新更新,然后选择最新的 10 个,
但是,这会在 task
上引发 顺序扫描 ,这对于 big[=61 来说是不可取的(昂贵的) =] 表。
如果您的项目相对较少(每个项目有很多任务条目),则可以使用(位图)索引扫描进行更快的替代。
SELECT *
FROM project p
, LATERAL (
SELECT updated_at AS last_updated_at
FROM task
WHERE project_id = p.id
ORDER BY updated_at DESC
LIMIT 1
) t
ORDER BY t.last_updated_at
LIMIT 10;
性能的关键是匹配的多列索引:
CREATE INDEX task_project_id_updated_at ON task (project_id, updated_at DESC);
具有 1000 个项目和 1000 万个任务的设置(如您评论的那样)是一个完美的选择。
背景:
- Optimize GROUP BY query to retrieve latest record per user
- Select first row in each GROUP BY group?
NULL
和 "no row"
以上解决方案假定 updated_at
定义为 NOT NULL
。否则使用 ORDER BY updated_at DESC
NULLS LAST
并理想地使索引匹配。
没有任何任务的项目通过隐式 CROSS JOIN
从结果中 消除。 NULL
价值观不能以这种方式蔓延。这与 NULL
。很可能不是你想要的。
相关:
- PostgreSQL sort by datetime asc, null first?