【问题标题】:Grouped LIMIT in PostgreSQL: show the first N rows for each group?PostgreSQL 中的分组限制:显示每个组的前 N ​​行?
【发布时间】:2010-11-10 14:38:03
【问题描述】:

我需要为每个组取前 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)

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

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

我使用的是 PostgreSQL 8.3.5。

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:
    SELECT  x.*
    FROM    (
            SELECT  section_id,
                    COALESCE
                    (
                    (
                    SELECT  xi
                    FROM    xxx xi
                    WHERE   xi.section_id = xo.section_id
                    ORDER BY
                            name, id
                    OFFSET 1 LIMIT 1
                    ),
                    (
                    SELECT  xi
                    FROM    xxx xi
                    WHERE   xi.section_id = xo.section_id
                    ORDER BY 
                            name DESC, id DESC
                    LIMIT 1
                    )
                    ) AS mlast
            FROM    (
                    SELECT  DISTINCT section_id
                    FROM    xxx
                    ) xo
            ) xoo
    JOIN    xxx x
    ON      x.section_id = xoo.section_id
            AND (x.name, x.id) <= ((mlast).name, (mlast).id)
    

    【讨论】:

    • 该查询非常接近我需要的查询,只是它没有显示少于 2 行的部分,即不返回 ID=7 的行。否则我喜欢你的方法。
    • 谢谢,我刚刚找到了与 COALESCE 相同的解决方案,但您更快。 :-)
    • 其实最后一个 JOIN 子句可以简化为: ... AND x.id
    • @Kouber:在您的示例中,nameid 按相同顺序排序,因此您不会看到它。将名称倒序排列,您将看到这些查询产生不同的结果。
    【解决方案2】:

    这是另一个解决方案 (PostgreSQL

    SELECT
      *
    FROM
      xxx a
    WHERE (
      SELECT
        COUNT(*)
      FROM
        xxx
      WHERE
        section_id = a.section_id
      AND
        name <= a.name
    ) <= 2
    

    【讨论】:

      【解决方案3】:

      新解决方案(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;
      

      【讨论】:

      • 这也适用于 PostgreSQL 8.4(窗口函数从 8.4 开始)。
      • 太棒了!它完美无缺。不过我很好奇,有没有办法用group by 做到这一点?
      • 对于那些使用数百万行并寻求真正高效的方法来做到这一点的人 - 最时髦的答案就是要走的路。只是不要忘记用适当的索引来增加趣味。
      • 这在mySQL 8.0.24中工作,快速将6M行关联的电子邮件地址压缩到每个类别中的五个(没有排序或标准,只需要五个电子邮件地址。少数类别(公司)有几千个地址。)
      【解决方案4】:
              -- ranking without WINDOW functions
      -- EXPLAIN ANALYZE
      WITH rnk AS (
              SELECT x1.id
              , COUNT(x2.id) AS rnk
              FROM xxx x1
              LEFT JOIN xxx x2 ON x1.section_id = x2.section_id AND x2.name <= x1.name
              GROUP BY x1.id
              )
      SELECT this.*
      FROM xxx this
      JOIN rnk ON rnk.id = this.id
      WHERE rnk.rnk <=2
      ORDER BY this.section_id, rnk.rnk
              ;
      
              -- The same without using a CTE
      -- EXPLAIN ANALYZE
      SELECT this.*
      FROM xxx this
      JOIN ( SELECT x1.id
              , COUNT(x2.id) AS rnk
              FROM xxx x1
              LEFT JOIN xxx x2 ON x1.section_id = x2.section_id AND x2.name <= x1.name
              GROUP BY x1.id
              ) rnk
      ON rnk.id = this.id
      WHERE rnk.rnk <=2
      ORDER BY this.section_id, rnk.rnk
              ;
      

      【讨论】:

      • CTEs 和 Window 函数是在同一个版本中引入的,所以我看不到第一个解决方案的好处。
      • 这个帖子已经三年了。此外,可能仍然存在缺少它们的实现(轻推轻推不再说)。它也可以被认为是老式查询构建的练习。 (尽管 CTE 不是很老套)
      • 该帖子被标记为“postgresql”,并且引入 CTE 的 PostgreSQL 版本也引入了窗口函数。因此我的评论(我确实看到它很旧 - 而 PG 8.3 两者都没有)
      • 帖子中提到了 8.3.5,我相信它们是在 8.4 中引入的。此外:了解替代方案也很好,恕我直言。
      • 这正是我的意思:8.3 既没有 CTE,也没有窗口函数。所以第一个解决方案不适用于 8.3
      【解决方案5】:

      从 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;
      

      might be faster 但是,当然,您应该专门针对您的数据和用例测试性能。

      【讨论】:

      • IMO 非常神秘的解决方案,特别是那些名字,但很好。
      • 如果您有t_inner.name 列的索引,则此带有横向连接的解决方案可能明显快于带有窗口函数的上述解决方案(在某些情况下)
      • 查询如果不包含自连接则更容易理解。在这种情况下,不需要distinct。发布的链接中显示了一个示例。
      • 伙计,这太令人费解了。 “ROW_NUMBER”解决方案产生了 120 毫秒而不是 9 秒。谢谢!
      • 如何选择t_top的所有列。 t 表包含一个 json 列,当我选择 distinct t_outer.section_id, t_top.* 时出现“无法识别类型 json postgres 的相等运算符”错误
      猜你喜欢
      • 2021-02-27
      • 2020-01-31
      • 2021-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-16
      • 2021-02-18
      相关资源
      最近更新 更多