PostgreSQL'de LIMIT grubu: Her grup için ilk N satırı gösterilsin mi?


179

Her grup için özel sütunla sıralanan ilk N satırını almam gerekiyor.

Aşağıdaki tablo göz önüne alındığında:

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)

Her section_id , yani benzer bir sonuç için ilk 2 satır ( adıyla sipariş ) gerekir:

 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 kullanıyorum.

Yanıtlar:


279

Yeni çözüm (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
Bu PostgreSQL 8.4 ile de çalışır (pencere fonksiyonları 8.4 ile başlar).
Bruno

2
Ders kitabı cevabı gruplandırılmış limit yapmak için
piggybox

4
Müthiş! Kusursuz çalışır. Yine de merak ediyorum, bunu yapmanın bir yolu var group bymı?
NurShomik

1
Milyonlarca satır gibi çalışanlar ve bunu gerçekten başarılı bir şekilde arayanlar için - poshest'in cevabı gitmenin yoludur. Sadece uygun indeksleme ile ti baharat unutmayın.
Çalışkan Anahtar Baskı

37

V9.3'ten beri yanal birleştirme yapabilirsiniz

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;

Daha hızlı olabilir , ancak elbette performansı özellikle verilerinizde ve kullanım durumunuzda test etmelisiniz.


4
Çok şifreli çözüm IMO, özellikle bu isimlerle, ama iyi bir çözüm.
villasv

1
LATERAL JOIN ile bu çözüm, sütuna göre dizine sahipseniz (bazı durumlarda) pencereli işleve sahip olandan daha hızlı olabilirt_inner.name
Artur Rashitov

Kendi kendine birleştirme içermiyorsa sorguyu anlamak daha kolaydır. Bu durumda distinctgerekli değildir. Yayınlanan bağlantı poshest'inde bir örnek gösterilir.
gillesB

Dostum, bu akıl almaz. "ROW_NUMBER" çözeltisi ile 9 saniye yerine 120 ms çıktı. Teşekkür ederim!
Çalışkan Anahtar Baskı

T_top öğesinin tüm sütunlarını nasıl seçebiliriz. T tablo bir json sütun içeriyor ve ben "tip json postgres için eşitlik operatörü tanımlanamadı" hatası alıyorumdistinct t_outer.section_id, t_top.*
suat

12

İşte başka bir çözüm (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)

Sorgu, 2 satırdan daha az bölümleri göstermemesi dışında, ihtiyacım olana çok yakın, yani ID = 7 olan satır döndürülmüyor. Aksi takdirde yaklaşımınızı seviyorum.
Kouber Saparev

Teşekkürler, COALESCE ile aynı çözüme geldim, ama daha hızlıydınız. :-)
Kouber Saparev

Aslında son JOIN alt cümlesi şu şekilde basitleştirilebilir: ... VE x.id <= (mlast) .id, isim alanına göre zaten seçildiyse, hayır?
Kouber Saparev

@ Kouber: örneğinizde name've id' aynı sırada sıralanmıştır, bu yüzden görmezsiniz. İsimleri ters sırada yapın, bu sorguların farklı sonuçlar verdiğini göreceksiniz.
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'ler ve Pencere işlevleri aynı sürümle tanıtıldı, bu yüzden ilk çözümün faydasını görmüyorum.
a_horse_with_no_name

Görev üç yaşında. Ayrıca, hala eksik olan uygulamalar da olabilir (dürtmek artık söylemez). Aynı zamanda eski fashonlanmış sorgu oluşturmada da bir alıştırma olarak düşünülebilir. (CTE'ler çok eski olmasa da)
wildplasser

Gönderi "postgresql" olarak etiketlenir ve CTE'leri tanıtan PostgreSQL sürümü de pencereleme işlevlerini tanıttı. Bu yüzden benim yorum (eski olduğunu gördüm - ve PG 8.3 hiçbiri yoktu)
a_horse_with_no_name 7:01 '

Mesaj 8.3.5'ten bahsediyor ve bunların 8.4'te tanıtıldığına inanıyorum. Ayrıca: IMHO alternatif senaryoları bilmek de iyidir.
wildplasser

Demek istediğim tam olarak bu: 8.3 ne CTE'ler ne de pencereleme fonksiyonları yoktu. Yani ilk çözüm 8.3 üzerinde çalışmaz
a_horse_with_no_name
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.