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
oORDER 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.