获取每个 ID 的最后 5 个不同值

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

ype*_*eᵀᴹ 9

可能有很多方法可以做到这一点。首先想到的是使用窗口函数:

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 和第 7postcodeid相同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)


Erw*_*ter 5

通常,您会有另一个表(让我们将其命名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)

假设postcodetextvarchar。如果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)

SQL小提琴。

NULLS LAST如果date_created已定义,您可以跳过任何地方NOT NULL

如果每个显着超过 5 行id是一种罕见的情况,@ypercube 的查询会更快。用 测试EXPLAIN ANALYZE

区别在于:我的 rCTE 带来了更多的开销,但性能几乎不受较旧的剩余行(查询中未涉及的行)的影响。@ypercube 的两个查询都有较少的开销,但随着每个id.

带有链接和更多解释的基础知识:

如果您没有 table tbl,您可以使用类似的技术id从一开始就提取不同的内容postcode

  • @ypercube 将我们关于带有类型修饰符的可能错误的评论移至 [chat](http://chat.stackexchange.com/rooms/29540/discussion-between-ypercube-and-erwin-brandstetter)。这是由此产生的 [后续问题](http://dba.stackexchange.com/q/116218/3684)。 (2认同)