PostgreSQL中的分组限制:显示每组的前N行?

Kou*_*rev 157 sql postgresql

我需要为每个组取前N行,按自定义列排序.

鉴于下表:

db=# SELECT * FROM xxx;
 id | section_id | name
----+------------+------
  1 |          1 | A
  2 |          1 | B
  3 |          1 | C
  4 |          1 | D
  5 |          2 | E
  6 |          2 | F
  7 |          3 | G
  8 |          2 | H
(8 rows)
Run Code Online (Sandbox Code Playgroud)

我需要每个section_id的前两行(按名称排序),即类似于的结果:

 id | section_id | name
----+------------+------
  1 |          1 | A
  2 |          1 | B
  5 |          2 | E
  6 |          2 | F
  7 |          3 | G
(5 rows)
Run Code Online (Sandbox Code Playgroud)

我正在使用PostgreSQL 8.3.5.

小智 260

新解决方案(PostgreSQL 8.4)

SELECT
  * 
FROM (
  SELECT
    ROW_NUMBER() OVER (PARTITION BY section_id ORDER BY name) AS r,
    t.*
  FROM
    xxx t) x
WHERE
  x.r <= 2;
Run Code Online (Sandbox Code Playgroud)

  • 这也适用于PostgreSQL 8.4(窗口函数从8.4开始). (8认同)
  • 对于那些处理数百万行并寻求真正高效的方法来做到这一点的人来说 - poshest 的答案就是要走的路。只是不要忘记用适当的索引来调味。 (5认同)
  • 真棒!它完美无瑕.我很好奇,有没有办法用`group by`做到这一点? (3认同)
  • 教科书答案做分组限制 (2认同)

pos*_*est 21

从v9.3开始,你可以进行横向连接

select distinct t_outer.section_id, t_top.id, t_top.name from t t_outer
join lateral (
    select * from t t_inner
    where t_inner.section_id = t_outer.section_id
    order by t_inner.name
    limit 2
) t_top on true
order by t_outer.section_id;
Run Code Online (Sandbox Code Playgroud)

可能更快,但当然,您应该专门针对您的数据和用例测试性能.

  • IMO非常神秘的解决方案,特别是那些名称,但是很好。 (2认同)
  • 如果您通过 `t_inner.name` 列建立索引,这种带有 LATERAL JOIN 的解决方案 ** 可能比上面带有窗口函数的解决方案快得多(在某些情况下) (2认同)
  • 老兄,这太伤脑筋了。使用“ROW_NUMBER”解决方案产生 120 毫秒而不是 9 秒。谢谢你! (2认同)

小智 14

横向联接是可行的方法,但您应该首先执行嵌套查询以提高大型表的性能。

SELECT t_limited.*
FROM (
        SELECT DISTINCT section_id
        FROM t
    ) t_groups
    JOIN LATERAL (
        SELECT *
        FROM t t_all
        WHERE t_all.section_id = t_groups.section_id
        ORDER BY t_all.name
        LIMIT 2
    ) t_limited ON true
Run Code Online (Sandbox Code Playgroud)

如果没有嵌套选择不同,即使section_id经常重复,横向连接也会针对表中的每一行运行。使用嵌套选择不同,连接横向对于每个不同的section_id运行一次且仅一次。


Kou*_*rev 10

这是另一个解决方案(PostgreSQL <= 8.3).

SELECT
  *
FROM
  xxx a
WHERE (
  SELECT
    COUNT(*)
  FROM
    xxx
  WHERE
    section_id = a.section_id
  AND
    name <= a.name
) <= 2
Run Code Online (Sandbox Code Playgroud)