PostgreSQL에서 그룹화 된 LIMIT : 각 그룹의 첫 번째 N 행을 표시합니까?


179

사용자 정의 열을 기준으로 각 그룹에 대해 첫 번째 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를 사용하고 있습니다.

답변:


279

새로운 솔루션 (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
이것은 PostgreSQL 8.4에서도 작동합니다 (창 기능은 8.4로 시작).
Bruno

2
교과서 답변 그룹화 제한
piggybox

4
대박! 완벽하게 작동합니다. 그래도 궁금합니다. 어떻게 할 수 group by있습니까?
NurShomik

1
수백만 행과 같이 일하고 실제로 수행 할 수있는 방법을 찾는 사람들에게는 포쉬 스트의 대답이 있습니다. 적절한 인덱싱으로 묶는 것을 잊지 마십시오.
Diligent Key Presser

37

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;

그것은 빠를 수도 있지만, 물론, 당신은 당신의 데이터와 사용 사례에 특히 성능을 테스트해야합니다.


4
특별히 그 이름을 가진 매우 암호 솔루션 IMO이지만 좋은 것입니다.
villasv

1
LATERAL JOIN을 사용하는이 솔루션 은 열 별로 인덱스를 사용하는 경우 창 기능 (일부 경우)을 사용하는 솔루션 보다 훨씬 빠를 수 있습니다.t_inner.name
Artur Rashitov

자체 조인이 포함되지 않은 쿼리는 이해하기 쉽습니다. 이 경우 distinct에는 필요하지 않습니다. 게시 된 링크 poshest에 예제가 표시됩니다.
gillesB

야, 이건 마음에 들어 "ROW_NUMBER"솔루션으로 9 초 대신 120ms를 생성했습니다. 감사합니다!
Diligent Key Presser

t_top의 모든 열을 어떻게 선택할 수 있습니까? t 테이블에 json 열이 포함되어 있는데 "선택할 때 json postgres 유형에 대한 항등 연산자를 식별 할 수 없습니다"라는 오류가 표시됩니다.distinct t_outer.section_id, t_top.*
suat

12

다른 솔루션이 있습니다 (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)

쿼리는 2 행 미만의 섹션을 표시하지 않는 것, 즉 ID = 7 인 행이 반환되지 않는다는 점을 제외하고는 필요한 쿼리에 매우 가깝습니다. 그렇지 않으면 나는 당신의 접근 방식을 좋아합니다.
Kouber Saparev

감사합니다. 방금 COALESCE와 동일한 솔루션을 사용했지만 더 빨랐습니다. :-)
Kouber Saparev

실제로 마지막 JOIN 하위 절은 다음과 같이 단순화 할 수 있습니다. ... 이름 필드에 따라 ID가 이미 선택되었으므로 AND x.id <= (mlast) .id?
Kouber Saparev

@Kouber : 당신의 예에서 name의 그리고 id당신이 그것을 볼 수 없습니다, 그래서의이 같은 순서로 정렬됩니다. 이름을 역순으로하면 이러한 쿼리가 다른 결과를 낳게됩니다.
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
        ;

CTE와 Window 기능은 동일한 버전으로 도입되었으므로 첫 번째 솔루션의 이점을 보지 못했습니다.
a_horse_with_no_name

게시물은 3 살입니다. 게다가, 구현이 부족한 구현이 여전히있을 수 있습니다 (너지 넛지). 또한 오래된 fashoned querybuilding에서 연습으로 간주 될 수 있습니다. (CTE가 그리 오래되지는 않았지만)
wildplasser

게시물에는 "postgresql"이라는 태그가 지정되어 있으며 CTE를 도입 한 PostgreSQL 버전에도 윈도우 기능이 도입되었습니다. 따라서 내 의견 (나는 그것이 오래된 것을 보았습니다-PG 8.3에는
아무것도

이 게시물에는 8.3.5가 언급되어 있으며 8.4에 소개 된 것으로 생각합니다. 게다가 : 대안 적 시나리오 인 IMHO에 대해서도 아는 것이 좋습니다.
wildplasser

그것은 정확히 내가 의미하는 바입니다. 8.3 CTE 나 윈도우 기능이 없었습니다. 따라서 첫 번째 솔루션은 8.3에서 작동하지 않습니다
a_horse_with_no_name
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.