PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Seleziona la prima riga in ogni gruppo GROUP BY?

DISTINCT ON è in genere il più semplice e veloce per questo in PostgreSQL .
(Per l'ottimizzazione delle prestazioni per determinati carichi di lavoro, vedere di seguito.)

SELECT DISTINCT ON (customer)
       id, customer, total
FROM   purchases
ORDER  BY customer, total DESC, id;

O più breve (se non così chiaro) con numeri ordinali di colonne di output:

SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

Se total può essere NULL (non farà male in entrambi i casi, ma ti consigliamo di abbinare gli indici esistenti):

...
ORDER  BY customer, total DESC NULLS LAST, id;

Punti principali

DISTINCT ON è un'estensione PostgreSQL dello standard (dove solo DISTINCT nel complesso SELECT elenco è definito).

Elenca un numero qualsiasi di espressioni in DISTINCT ON clausola, il valore di riga combinato definisce i duplicati. Il manuale:

Ovviamente, due righe sono considerate distinte se differiscono per almeno un valore di colonna. I valori nulli sono considerati uguali in questo confronto.

Enfasi in grassetto la mia.

DISTINCT ON può essere combinato con ORDER BY . Espressioni principali in ORDER BY deve essere nel set di espressioni in DISTINCT ON , ma puoi riordinare liberamente tra quelli. Esempio.
Puoi aggiungere ulteriori espressioni in ORDER BY per scegliere una riga particolare da ciascun gruppo di peer. Oppure, come dice il manuale:

Il DISTINCT ON le espressioni devono corrispondere al ORDER BY più a sinistra espressione/i. Il ORDER BY La clausola normalmente conterrà espressioni aggiuntive che determinano la precedenza desiderata delle righe all'interno di ogni DISTINCT ON gruppo.

Ho aggiunto id come ultimo elemento per rompere i legami:
"Scegli la riga con il id più piccolo da ogni gruppo che condivide il total più alto ."

Per ordinare i risultati in un modo che non sia d'accordo con l'ordinamento che determina il primo per gruppo, puoi annidare la query sopra in una query esterna con un altro ORDER BY . Esempio.

Se total può essere NULL, tu molto probabilmente desidera la riga con il valore non nullo maggiore. Aggiungi NULLS LAST come dimostrato. Vedi:

  • Ordina per colonna ASC, ma prima i valori NULL?

Il SELECT elenco non è vincolato dalle espressioni in DISTINCT ON o ORDER BY in ogni modo. (Non necessario nel caso semplice sopra):

  • Non devi includi una qualsiasi delle espressioni in DISTINCT ON o ORDER BY .

  • puoi includere qualsiasi altra espressione in SELECT elenco. Questo è fondamentale per sostituire query molto più complesse con sottoquery e funzioni di aggregazione/finestra.

Ho testato con Postgres versioni 8.3 – 13. Ma la funzionalità è presente almeno dalla versione 7.1, quindi praticamente sempre.

Indice

Il perfetto l'indice per la query precedente sarebbe un indice a più colonne che si estende su tutte e tre le colonne nella sequenza corrispondente e con un ordinamento corrispondente:

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

Potrebbe essere troppo specializzato. Ma usalo se le prestazioni di lettura per la query specifica sono cruciali. Se hai DESC NULLS LAST nella query, usa lo stesso nell'indice in modo che l'ordinamento corrisponda e l'indice sia applicabile.

Efficacia/Ottimizzazione delle prestazioni

Pesare costi e vantaggi prima di creare indici personalizzati per ogni query. Il potenziale dell'indice di cui sopra dipende in gran parte dalla distribuzione dei dati .

L'indice viene utilizzato perché fornisce dati preordinati. In Postgres 9.2 o versioni successive la query può anche beneficiare di una scansione solo indice se l'indice è più piccolo della tabella sottostante. Tuttavia, l'indice deve essere scansionato nella sua interezza.

Per pochi righe per cliente (alta cardinalità nella colonna customer ), questo è molto efficiente. Ancora di più se hai comunque bisogno di un output ordinato. Il vantaggio si riduce con un numero crescente di righe per cliente.
Idealmente, hai abbastanza work_mem per elaborare il passaggio di ordinamento coinvolto nella RAM e non riversarlo su disco. Ma generalmente impostando work_mem anche alto può avere effetti negativi. Considera SET LOCAL per query eccezionalmente grandi. Trova quanto ti serve con EXPLAIN ANALYZE . Menzione di "Disco: " nel passaggio di ordinamento indica la necessità di altro:

  • Parametro di configurazione work_mem in PostgreSQL su Linux
  • Ottimizza query semplici utilizzando ORDER BY data e testo

Per molti righe per cliente (bassa cardinalità nella colonna customer ), una scansione dell'indice allentata (noto anche come "salta scansione") sarebbe (molto) più efficiente, ma non è implementato fino a Postgres 14. (Un'implementazione per scansioni solo indice è in fase di sviluppo per Postgres 15. Vedi qui e qui.)
Per ora ci sono tecniche di query più veloci per sostituire questo. In particolare se hai una tabella separata che contiene clienti unici, che è il tipico caso d'uso. Ma anche se non lo fai:

  • SELECT DISTINCT è più lento del previsto sulla mia tabella in PostgreSQL
  • Ottimizza la query GROUP BY per recuperare l'ultima riga per utente
  • Ottimizza la query massima per gruppo
  • Interroga le ultime N righe correlate per riga

Benchmark

Vedi risposta separata.