获得 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 |

参见:http://sqlfiddle.com/#!15/ee039/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 DESCNULLS LAST 并理想地使索引匹配。

没有任何任务的项目通过隐式 CROSS JOIN 从结果中 消除NULL 价值观不能以这种方式蔓延。这与 等相关子查询略有不同:"no row" 的那些 return NULL 值(项目根本没有相关任务)。除非另有说明,否则外部降序排序顺序会在顶部列出 NULL。很可能不是你想要的。

相关:

  • PostgreSQL sort by datetime asc, null first?