Rob*_*Bex 7 postgresql greatest-n-per-group distinct
我正在使用 PostgreSQL 9.4。
我有一个包含以下条目的表:
id | postcode | date_created
---+----------+-----------------
14 | al2 2qp | 2015-09-23 14:46:57
14 | al2 2qp | 2015-09-23 14:51:07
14 | sp2 8ag | 2015-09-23 14:56:11
14 | se4 | 2015-09-23 16:12:05
17 | e2 | 2015-09-23 16:15:35
17 | fk20 8ru | 2015-09-23 16:28:35
17 | fk20 8ru | 2015-09-23 16:35:51
17 | se2 | 2015-09-23 16:36:17
17 | fk20 8ru | 2015-09-23 16:36:22
17 | fk20 8ru | 2015-09-23 16:37:04
17 | se1 | 2015-09-23 16:37:11
17 | fk20 8ru | 2015-09-23 16:37:15
17 | se1 8ga | 2015-09-24 09:52:46
17 | se1 | 2015-09-24 10:01:19
17 | hp27 9rz | 2015-09-24 10:05:27
17 | hp27 9rz | 2015-09-24 10:05:29
17 | se1 | 2015-09-24 10:19:46
14 | tn21 8qb | 2015-09-24 14:49:05
14 | tn21 8qb | 2015-09-24 15:42:45
14 | tn21 8qb | 2015-09-24 17:38:06
14 | n4 1ny | 2015-09-25 14:49:10
Run Code Online (Sandbox Code Playgroud)
我想要实现的是一个查询,它为每个 id返回 5 个最近的唯一邮政编码记录:
id | postcode
---+---------
14 | n4 1ny
14 | tn21 8qb
14 | se4
14 | sp2 8ag
14 | al2 2qp
17 | se1
17 | hp27 9rz
17 | se1 8ga
17 | fk20 8ru
17 | se2
Run Code Online (Sandbox Code Playgroud)
实现这一目标的最佳方法是什么?我一直在玩子查询,但是在执行DISTINCTand 的同时订购它们时一直在碰壁GROUP BY。
可能有很多方法可以做到这一点。首先想到的是使用窗口函数:
SELECT
id, postcode
FROM
( SELECT id, postcode,
ROW_NUMBER() OVER (PARTITION BY id
ORDER BY MAX(date_created) DESC
) AS rn
FROM tablename
GROUP BY id, postcode
) AS t
WHERE
rn <= 5
ORDER BY
id, rn ;
Run Code Online (Sandbox Code Playgroud)
在SQLfiddle测试。
如果有平局,比如说第 5、第 6 和第 7postcode个id相同date_created,则结果中只有其中一个(选择将是任意的)。如果您想要在这些情况下所有绑定的邮政编码,请使用RANK()代替ROW_NUMBER()。
另一种选择是使用LATERAL语法。我不确定哪个更有效,它可能取决于两列 ( idand postcode)的值分布,即整个表中有多少个不同的 id,每个 id 有多少个不同的邮政编码以及每个 (id , 邮政编码) 组合。
SELECT
t.id, ti.postcode
FROM
( SELECT DISTINCT id
FROM tablename
) AS t
CROSS JOIN LATERAL
( SELECT tt.postcode,
MAX(tt.date_created) AS date_created
FROM tablename AS tt
WHERE tt.id = t.id
GROUP BY tt.postcode
ORDER BY date_created DESC
LIMIT 5
) AS ti
ORDER BY
t.id, ti.date_created DESC;
Run Code Online (Sandbox Code Playgroud)
在 on(id, postcode, date_created)或 on 上添加索引也是一个好主意(id, postcode, date_created DESC)。
通常,您会有另一个表(让我们将其命名tbl),其中所有不同的id值都位于不同的行中。如果没有,请创建它:
CREATE TABLE tbl AS
SELECT DISTINCT id FROM postcode ORDER BY id; -- ORDER is optional
Run Code Online (Sandbox Code Playgroud)
或者将tbl下面的查询替换为与SELECT子查询相同的查询,但这(很多)更昂贵。
如果per可以有很多行id,那么递归 CTE应该是最快的:
WITH RECURSIVE cte AS (
SELECT t.id, 1 AS rnk, p.*, ARRAY[postcode] AS arr
FROM tbl t
, LATERAL (
SELECT postcode, date_created
FROM postcode
WHERE id = t.id
ORDER BY date_created DESC NULLS LAST
LIMIT 1
) p
UNION ALL
SELECT t.id, rnk + 1, p.*, arr || p.postcode
FROM cte t
, LATERAL (
SELECT postcode, date_created
FROM postcode
WHERE id = t.id
AND date_created < t.date_created
AND postcode <> ALL (t.arr)
ORDER BY date_created DESC NULLS LAST
LIMIT 1
) p
WHERE rnk < 5
)
SELECT id, rnk, postcode, date_created
FROM cte
ORDER BY id, rnk;
Run Code Online (Sandbox Code Playgroud)
假设postcode为text或varchar。如果postcode具有类型修饰符(类似varchar(50)或类似),则此特定查询中可能存在问题:
索引(id, date_created)对于大表的性能至关重要:
CREATE INDEX postcode_foo_idx ON postcode(id, date_created DESC NULLS LAST);
Run Code Online (Sandbox Code Playgroud)
NULLS LAST如果date_created已定义,您可以跳过任何地方NOT NULL。
如果每个显着超过 5 行id是一种罕见的情况,@ypercube 的查询会更快。用 测试EXPLAIN ANALYZE。
区别在于:我的 rCTE 带来了更多的开销,但性能几乎不受较旧的剩余行(查询中未涉及的行)的影响。@ypercube 的两个查询都有较少的开销,但随着每个id.
带有链接和更多解释的基础知识:
如果您没有 table tbl,您可以使用类似的技术id从一开始就提取不同的内容postcode:
| 归档时间: |
|
| 查看次数: |
11036 次 |
| 最近记录: |