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.

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;

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.


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

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)

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
        ;

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.