Групування LIMIT у PostgreSQL: показати перші 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)

Мені потрібні перші 2 рядки (упорядковані по імені ) для кожного розділу_id , тобто результат, подібний до:

 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).
Бруно

2
Відповідь підручника на груповий ліміт
скарбничка

4
Дивовижно! Це працює бездоганно. Мені цікаво, чи є спосіб це зробити group by?
NurShomik

1
Для тих, хто працює з мільйонами рядків і шукає дійсно ефективний спосіб зробити це - відповідь посту - це шлях. Просто не забувайте приправляти ті правильним індексуванням.
ключа

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
Дуже криптичне рішення ІМО, спеціально з тими назвами, але добре.
villasv

1
Це рішення з LATERAL JOIN може бути значно швидшим, ніж вище, яке має функція вікна (в деяких випадках), якщо у вас є індекс за t_inner.nameстовпцем
Артур Рашитов

Запит легше зрозуміти, якщо він не містить самоприєднання. У цьому випадку distinctце не потрібно. Приклад показаний у посиланні, розміщеному на пошті.
gillesB

Чувак, це думка. 120мм замість 9сек давали рішення "ROW_NUMBER". Дякую!
ключ

Як ми можемо вибрати всі стовпці t_top. Таблиця t містить стовпчик json, і я отримую помилку "не міг визначити оператора рівності для постгресів типу json"distinct t_outer.section_id, t_top.*
субота

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 рядки, тобто рядок з ідентифікатором = 7 не повертається. Інакше мені подобається ваш підхід.
Кубер Сапарев

Дякую, я просто прийшов до того ж рішення з COALESCE, але ви були швидшими. :-)
Kouber Saparev

Насправді останній підпункт JOIN міг бути спрощений до: ... AND x.id <= (mlast) .id, оскільки ідентифікатор вже обраний відповідно до поля імені, ні?
Кубер Сапарев

@Kouber: у вашому прикладі nameid" відсортовані в одному порядку, тому ви не побачите його. Складіть імена в зворотному порядку, і ви побачите, що ці запити дають різні результати.
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

Посаду три роки. Крім того, все ще можуть бути такі реалізації, яких їм не вистачає (натискання натискання не скажеш більше). Це також можна вважати вправою в побудові старовинних запитів. (хоча CTE не дуже старі)
wildplasser

Повідомлення позначено "postgresql", а версія PostgreSQL, яка запровадила CTE, також запровадила функції вікон. Звідси мій коментар (я бачив, що він такий старий - а PG 8.3 не мав жодного)
a_horse_with_no_name

Пост згадує 8.3.5, і я вважаю, що вони були введені в 8.4. Крім того: також добре знати альтернативні сценарії, ІМХО.
wildplasser

Це саме те, що я маю на увазі: 8.3 не мали CTE, ані функції вікон. Тож перше рішення не працюватиме о 8.3
a_horse_with_no_name
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.