LIMIT Dikelompokkan dalam PostgreSQL: perlihatkan baris N pertama untuk setiap grup?

179

Saya perlu mengambil baris N pertama untuk setiap grup, dipesan dengan kolom khusus.

Diberikan tabel berikut:

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)

Saya memerlukan 2 baris pertama (dipesan dengan nama ) untuk setiap section_id , yaitu hasil yang mirip dengan:

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

Saya menggunakan PostgreSQL 8.3.5.

Kouber Saparev
sumber

Jawaban:

279

Solusi baru (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;
Dave
sumber
8
Ini berfungsi dengan PostgreSQL 8.4 juga (fungsi jendela mulai dengan 8.4).
Bruno
2
Jawaban buku teks untuk dilakukan batas dikelompokkan
piggybox
4
Luar biasa! Ini bekerja dengan sempurna. Saya penasaran, apakah ada cara untuk melakukan ini group by?
NurShomik
1
Bagi mereka yang bekerja dengan jutaan baris dan mencari cara yang benar-benar performant untuk melakukan ini - jawaban poshest adalah cara untuk pergi. Hanya saja, jangan lupa membumbui dengan pengindeksan yang tepat.
Presser Kunci Rajin
37

Sejak v9.3 Anda dapat melakukan join lateral

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;

Ini mungkin akan lebih cepat tetapi, tentu saja, Anda harus menguji kinerja khusus pada data dan menggunakan kasus.

poshest
sumber
4
Solusi IMO yang sangat samar, khususnya dengan nama-nama itu, tetapi bagus.
villasv
1
Solusi ini dengan LATERAL GABUNG mungkin jauh lebih cepat daripada yang di atas dengan fungsi berjendela (dalam beberapa kasus) jika Anda memiliki indeks dengan t_inner.namekolom
Artur Rashitov
Query lebih mudah dipahami jika tidak mengandung self-join. Dalam hal distinctini tidak diperlukan. Contohnya ditunjukkan pada tautan poshest yang diposting.
gillesB
Bung, ini melelahkan. 120ms bukannya 9sec yang dihasilkan dengan solusi "ROW_NUMBER". Terima kasih!
Presser Kunci Rajin
Bagaimana kita bisa memilih semua kolom t_top. Tabel t berisi kolom json dan saya mendapatkan "tidak dapat mengidentifikasi operator kesetaraan untuk tipe json postgres" ketika saya memilihdistinct t_outer.section_id, t_top.*
suat
12

Ini solusi lain (PostgreSQL <= 8.3).

SELECT
  *
FROM
  xxx a
WHERE (
  SELECT
    COUNT(*)
  FROM
    xxx
  WHERE
    section_id = a.section_id
  AND
    name <= a.name
) <= 2
Kouber Saparev
sumber
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)
Quassnoi
sumber
Permintaan sangat dekat dengan yang saya butuhkan, kecuali bahwa itu tidak menunjukkan bagian dengan kurang dari 2 baris, yaitu baris dengan ID = 7 tidak dikembalikan. Kalau tidak, saya suka pendekatan Anda.
Kouber Saparev
Terima kasih, saya baru saja datang ke solusi yang sama dengan COALESCE, tetapi Anda lebih cepat. :-)
Kouber Saparev
Sebenarnya sub-klausa JOIN terakhir bisa disederhanakan menjadi: ... AND x.id <= (mlast) .id karena ID sudah dipilih sesuai dengan bidang nama, bukan?
Kouber Saparev
@Kouber: dalam contoh Anda, name'dan id' diurutkan dalam urutan yang sama, sehingga Anda tidak akan melihatnya. Buat nama-nama dalam urutan terbalik dan Anda akan melihat bahwa kueri ini menghasilkan hasil yang berbeda.
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
        ;
wildplasser
sumber
Fungsi CTE dan Window diperkenalkan dengan versi yang sama, jadi saya tidak melihat manfaat dari solusi pertama.
a_horse_with_no_name
Posnya berumur tiga tahun. Selain itu, mungkin masih ada implementasi yang tidak memilikinya (nudge nudge mengatakan tidak lebih). Ini juga bisa dianggap sebagai latihan dalam pembuatan kueri yang lama. (meskipun CTE tidak terlalu ketinggalan zaman)
wildplasser
Posting ini ditandai "postgresql" dan versi PostgreSQL yang memperkenalkan CTE juga memperkenalkan fungsi windowing. Oleh karena itu komentar saya (saya memang melihatnya setua itu - dan PG 8.3 tidak memiliki keduanya)
a_horse_with_no_name
Posting menyebutkan 8.3.5, dan saya percaya mereka diperkenalkan pada 8.4. Selain itu: juga baik untuk mengetahui tentang skenario alternatif, IMHO.
wildplasser
Itulah yang saya maksud: 8.3 tidak memiliki CTE atau fungsi windowing. Jadi solusi pertama tidak akan bekerja pada 8.3
a_horse_with_no_name