我在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
答案 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