LIMIT raggruppati in PostgreSQL: mostrare le prime N righe per ciascun gruppo?


179

Devo prendere le prime N righe per ciascun gruppo, ordinate per colonna personalizzata.

Data la seguente tabella:

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)

Ho bisogno delle prime 2 righe (ordinate per nome ) per ogni section_id , ovvero un risultato simile a:

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

Sto usando PostgreSQL 8.3.5.

Risposte:


279

Nuova soluzione (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
Funziona anche con PostgreSQL 8.4 (le funzioni della finestra iniziano con 8.4).
Bruno

2
Risposta del libro di testo per fare limite raggruppato
salvadanaio

4
Eccezionale! Funziona perfettamente. Sono curioso però, c'è un modo per farlo group by?
NurShomik,

1
Per coloro che lavorano con milioni di file e cercano un modo davvero performante per farlo - la risposta più elegante è la strada da percorrere. Non dimenticarti di aggiungere spezie con una corretta indicizzazione.
Diligent Key Presser,

37

Dalla v9.3 è possibile eseguire un join laterale

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;

Si potrebbe essere più veloce , ma, naturalmente, è necessario verificare le prestazioni specificamente sui vostri dati e dei casi d'uso.


4
Soluzione molto criptica IMO, specialmente con quei nomi, ma buona.
villasv,

1
Questa soluzione con LATERAL JOIN potrebbe essere significativamente più veloce di quella precedente con la funzione a finestra (in alcuni casi) se si dispone di indice per t_inner.namecolonna
Artur Rashitov

La query è più semplice da capire se non contiene l'auto-join. In tal caso distinctnon è necessario. Un esempio è mostrato nel link più posh pubblicato.
gillesB,

Amico, questo è sbalorditivo. 120ms anziché 9sec hanno prodotto la soluzione "ROW_NUMBER". Grazie!
Diligent Key Presser

Come possiamo selezionare tutte le colonne di t_top. La tabella t contiene una colonna json e ottengo l'errore "impossibile identificare l'operatore di uguaglianza per il tipo json postgres" quando selezionodistinct t_outer.section_id, t_top.*
suat

12

Ecco un'altra soluzione (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)

La query è molto simile a quella di cui ho bisogno, tranne per il fatto che non mostra sezioni con meno di 2 righe, ovvero la riga con ID = 7 non viene restituita. Altrimenti mi piace il tuo approccio.
Kouber Saparev,

Grazie, sono appena arrivato alla stessa soluzione con COALESCE, ma tu eri più veloce. :-)
Kouber Saparev,

In realtà l'ultima sotto-clausola JOIN potrebbe essere semplificata in: ... AND x.id <= (mlast) .id poiché l'ID è già stato scelto in base al campo del nome, no?
Kouber Saparev,

@Kouber: nel tuo esempio gli name'e id' sono ordinati nello stesso ordine, quindi non lo vedrai. Crea i nomi in ordine inverso e vedrai che queste query producono risultati diversi.
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
        ;

Le funzioni CTE e Window sono state introdotte con la stessa versione, quindi non vedo il vantaggio della prima soluzione.
a_horse_with_no_name

Il posto ha tre anni. Inoltre, potrebbero esserci ancora implementazioni che mancano di loro (nudge nudge non dice altro). Potrebbe anche essere considerato un esercizio di querybuilding vecchio stile. (anche se i CTE non sono molto vecchi)
wildplasser il

Il post è taggato "postgresql" e la versione PostgreSQL che ha introdotto i CTE ha anche introdotto funzioni di windowing. Da qui il mio commento (ho visto che è così vecchio - e PG 8.3 non aveva nessuno dei due)
a_horse_with_no_name

Il post menziona 8.3.5 e credo che siano stati introdotti in 8.4. Inoltre: è anche utile conoscere scenari alternativi, IMHO.
wildplasser,

Questo è esattamente ciò che intendo: 8.3 non aveva né CTE né funzioni di finestre. Quindi la prima soluzione non funzionerà su 8.3
a_horse_with_no_name il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.