GIỚI HẠN được nhóm trong PostgreSQL: hiển thị N hàng đầu tiên cho mỗi nhóm?


179

Tôi cần lấy N hàng đầu tiên cho mỗi nhóm, được sắp xếp theo cột tùy chỉnh.

Cho bảng sau:

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)

Tôi cần 2 hàng đầu tiên (được sắp xếp theo tên ) cho mỗi phần_id , tức là một kết quả tương tự như:

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

Tôi đang sử dụng PostgreSQL 8.3.5.

Câu trả lời:


279

Giải pháp mới (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;

8
Điều này cũng hoạt động với PostgreSQL 8.4 (các chức năng của cửa sổ bắt đầu bằng 8.4).
Bruno

2
Sách giáo khoa trả lời để làm giới hạn được nhóm
piggybox

4
Tuyệt vời! Nó hoạt động hoàn hảo. Tôi tò mò mặc dù, có cách nào để làm điều này với group by?
NurShomik

1
Đối với những người làm việc với hàng triệu hàng và tìm kiếm cách thực sự hiệu quả để làm điều này - câu trả lời chắc chắn nhất là cách để đi. Chỉ cần đừng quên gia vị ti lên với chỉ mục thích hợp.
ép phím siêng năng

37

Kể từ phiên bản 9, bạn có thể tham gia bên

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;

có thể nhanh hơn , nhưng, tất nhiên, bạn nên kiểm tra hiệu suất cụ thể trên dữ liệu và trường hợp sử dụng của bạn.


4
IMO giải pháp rất khó hiểu, đặc biệt với những cái tên đó, nhưng là một cái tốt.
biệt thự

1
Giải pháp này với LATITH THAM GIA có thể nhanh hơn đáng kể so với giải pháp trên có chức năng cửa sổ (trong một số trường hợp) nếu bạn có chỉ mục theo t_inner.namecột
Artur Rashitov

Truy vấn sẽ dễ hiểu hơn nếu nó không chứa tự tham gia. Trong trường hợp đó distinctlà không cần thiết. Một ví dụ được hiển thị trong liên kết poshest được đăng.
gillesB

Anh bạn, đây là suy nghĩ. 120ms thay vì 9 giây mang lại giải pháp "ROW_NUMBER". Cảm ơn bạn!
ép phím siêng năng

Làm thế nào chúng ta có thể chọn tất cả các cột của t_top. Bảng t chứa cột json và tôi nhận được lỗi "không thể xác định toán tử đẳng thức cho loại json postgres" khi tôi chọndistinct t_outer.section_id, t_top.*
suat

12

Đây là một giải pháp khác (PostgreSQL <= 8.3).

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

2
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)

Truy vấn rất gần với truy vấn tôi cần, ngoại trừ việc nó không hiển thị các phần có ít hơn 2 hàng, tức là hàng có ID = 7 không được trả về. Nếu không tôi thích cách tiếp cận của bạn.
Kouber Saparev

Cảm ơn bạn, tôi vừa đến cùng một giải pháp với COALESCE, nhưng bạn đã nhanh hơn. :-)
Kouber Saparev

Trên thực tế, mệnh đề phụ THAM GIA cuối cùng có thể được đơn giản hóa thành: ... VÀ x.id <= (mlast) .id vì ID đã được chọn theo trường tên, phải không?
Kouber Saparev

@Kouber: trong ví dụ của bạn name, các và idđược sắp xếp theo cùng một thứ tự, vì vậy bạn sẽ không thấy nó. Đặt tên theo thứ tự ngược lại và bạn sẽ thấy rằng các truy vấn này mang lại kết quả khác nhau.
Quassnoi

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

Các chức năng CTE và Window được giới thiệu với cùng một phiên bản, vì vậy tôi không thấy lợi ích của giải pháp đầu tiên.
a_horse_with_no_name

Bài viết đã ba tuổi. Bên cạnh đó, vẫn có thể có những triển khai thiếu chúng (nudge nudge nói không hơn). Nó cũng có thể được coi là một bài tập trong xây dựng truy vấn cũ. (mặc dù CTE không phải là rất cũ)
wildplasser

Bài đăng được gắn thẻ "postgresql" và phiên bản PostgreSQL giới thiệu CTE cũng giới thiệu các chức năng cửa sổ. Do đó, nhận xét của tôi (tôi đã thấy nó cũ - và PG 8.3 không có)
a_horse_with_no_name

Bài viết đề cập đến 8.3.5, và tôi tin rằng chúng đã được giới thiệu trong 8.4. Bên cạnh đó: cũng tốt khi biết về các kịch bản thay thế, IMHO.
wildplasser

Đó chính xác là điều tôi muốn nói: 8.3 không có chức năng CTE hay cửa sổ. Vì vậy, giải pháp đầu tiên sẽ không hoạt động vào ngày 8.3
a_horse_with_no_name
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.