获得10个不同的项目,其中包含相关任务的最新更新

时间:2016-09-12 11:09:20

标签: sql postgresql greatest-n-per-group postgresql-performance bigdata

我在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),我收到错误:

  

对于SELECT DISTINCT,ORDER BY表达式必须出现在选择列表

问题是,我不能(主要)按project_id排序,因为我需要按时间排序任务。按updated_at DESC, project_id ASC排序也不起作用,因为相同项目的几个任务可能是最新的。

我无法下载所有记录,因为有数百万条记录。

作为一种解决方法,我下载10x所需的行(没有明显的)范围,并在后端过滤它们。这适用于大多数情况,但它显然不可靠:有时我不会获得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个不同的项目,并使用相应的最新task.update_at

 project_id |   updated_at    
------------+-----------------
          3 | 13:13:04.384477
          2 | 13:12:54.680891  -- from Task 3

5 个答案:

答案 0 :(得分:2)

按表达方式尝试分组,以及它的目标:

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

如果您想避免全表扫描,请不要忘记放置一个以: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

答案 1 :(得分:2)

简单(逻辑上正确)的解决方案是聚合任务以获得每个项目的最新更新,然后选择最新的10,like @Nemeros提供。

但是,这会在task上产生顺序扫描,这对于 表来说是不合需要的(昂贵)。

如果项目相对较少(每个项目有很多任务条目),使用(位图)索引扫描的替代方案会更快。

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万个任务的设置(如您所评论的)是完美的候选者。

背景:

NULL"没有行"

以上解决方案假定updated_at已定义NOT NULL。否则,请使用ORDER BY updated_at DESC NULLS LAST ,最好使索引匹配。

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

相关:

答案 2 :(得分:1)

尝试使用

SELECT project_id, 
       Max (updated_at) 
FROM   task 
GROUP  BY project_id 
ORDER  BY Max(updated_at) DESC 
LIMIT  10 

答案 3 :(得分:0)

如何按最新更新排序记录,然后执行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中,但我认为无论如何这都更清晰。

答案 4 :(得分:0)

我相信 row_number()over()可用于此,但您仍需要最终的order by和limit子句:

    protected void Button1_Click(object sender, EventArgs e)
    {
        string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
        SqlConnection con = new SqlConnection(cs);

        string sqlQuery = "insert into Student values where = " + TextStudentID.Text;

        SqlDataAdapter da = new SqlDataAdapter(sqlQuery, con);

        SqlCommandBuilder builder = new SqlCommandBuilder(da);

        DataSet ds = (DataSet)ViewState["dataset"];

        SqlCommand cmd = builder.GetInsertCommand();
        cmd.Parameters["@StudentName"].Value = TextStudentName.Text;
        cmd.Parameters["@Gender"].Value = DropDownList1.SelectedValue;
        cmd.Parameters["@Studentmarks"].Value = TextStudentName.Text;

        cmd.ExecuteNonQuery();

        da.Fill(ds);
    }

此方法的优点使您可以访问与每个项目的最大updated_at相对应的完整行。您也可以选择加入项目表

结果:

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

请参阅:http://sqlfiddle.com/#!15/ee039/1