2010-10-05 6 views
11

Sto provando a scrivere una query in Postgresql che estrae un insieme di dati ordinati e li filtra per un campo distinto. Devo anche prelevare diversi altri campi dalla stessa riga della tabella, ma devono essere esclusi dalla valutazione distinta. Esempio:Uso di una clausola DISTINCT per filtrare i dati, ma tirare ancora altri campi che non sono DISTINCT

SELECT DISTINCT(user_id) user_id, 
     created_at 
    FROM creations 
ORDER BY created_at 
    LIMIT 20 

ho bisogno del user_id essere DISTINCT, ma non importa se la data created_at è unica o meno. Poiché la data created_at viene inclusa nella valutazione, sto ottenendo il duplicato user_id nel mio set di risultati.

Inoltre, i dati devono essere ordinati in base alla data, in modo da utilizzare DISTINCT ON non è un'opzione qui. È necessario che il campo DISTINCT ON sia il primo campo nella clausola ORDER BY e che non fornisca i risultati che cerco.

Come utilizzare correttamente la clausola DISTINCT ma limitare l'ambito a un solo campo mentre si selezionano altri campi?

+0

Il concetto di 'DISTINCT' si applica intrinsecamente a tutte le colonne selezionate perché altrimenti ci sarebbe un'aggregazione inerente ... da qui la funzione' GROUP'. Che tipo di set di risultati stai guardando? Puoi dare un esempio di ciò che i dati potrebbero contenere e quali risultati desideri? – Matthew

+0

La cosa interessante (e non correlata alla risposta) è che ho già eseguito il suddetto tentativo di avere "DISTINCT (column1), column2" su una singola colonna. Tuttavia, i database lo analizzano come "DISTINCT column1, column2" - fortunatamente per te ha restituito risultati errati - altrimenti ti avrebbe potuto morsi molto più tardi (la prima volta che l'ho visto era in un database di produzione). – Unreason

+0

"i dati devono essere ordinati entro la data" - ** quale ** data? La prima data_satificata per ogni utente? L'ultimo? Qualcos'altro? –

risposta

5

Come hai scoperto, standard SQL tratta DISTINCT in riferimento a tutta la select-list, non solo una colonna o un paio di colonne. La ragione di ciò è che è ambiguo il valore da inserire nelle colonne che escludi dallo DISTINCT. Per lo stesso motivo, SQL standard non consente di avere colonne ambigue in una query con GROUP BY.

Ma PostgreSQL ha un'estensione non standard per SQL per consentire quello che stai chiedendo: DISTINCT ON (expr).

SELECT DISTINCT ON (user_id) user_id, created_at 
FROM creations 
ORDER BY user_id, created_at 
LIMIT 20 

È necessario includere le espressioni distinte come la parte più a sinistra della clausola ORDER BY.

Vedere il manuale su DISTINCT Clause per ulteriori informazioni.

+0

SQL lo fa perché il concetto di 'DISTINCT' si applica naturalmente a intere liste di selezione; l'alternativa sarebbe una clausola 'GROUP BY'. La selezione di soli risultati distinti da una singola colonna richiede fondamentalmente un'aggregazione se sono presenti altre colonne nella query – Matthew

+0

"È necessario includere le espressioni distinte come la parte più a sinistra della clausola ORDER BY." Questo è il motivo per cui non posso usare DISTINCT ON ... Ho bisogno che i risultati siano in un ordine specifico che è definito da un campo non correlato alla clausola DISTINCT ON. – mindtonic

+0

+1, ah così postgres ha DISTINTO SU ... l'apprendimento. Grazie. – Unreason

2

L'utilizzo di una sottoquery è stato suggerito da qualcuno sul canale irC#postgresql. Ha funzionato:

SELECT user_id 
FROM (SELECT DISTINCT ON (user_id) * FROM creations) ss 
ORDER BY created_at DESC 
LIMIT 20; 
+0

Questo produrrà ancora user_id duplicati se lo stesso ID utente ha due dei primi 20 valori created_at – Matthew

+0

@mindtonic, sebbene questa sia essenzialmente la risposta di Bill, questa risposta potrebbe essere sbagliata - se non usi ORDER BY con DISTINCT ON allora la scelta dei valori per gli altri campi sono 'specifici dell'implementazione' (leggi: molto probabilmente corrisponde all'ordine degli inserimenti, ma non è garantito, vedi documentazione) – Unreason

+0

@Matthew PK, non - subquery otterrà distinti user_id. Periodo. – Unreason

3

Il GROUP BY dovrebbe garantire valori distinti delle colonne raggruppate, questo potrebbe darvi quello che state cercando.

(Nota sto mettendo nei miei 2 centesimi, anche se non ho familiarità con PostgreSQL, ma piuttosto MySQL e Oracle)

in MySQL

SELECT user_id, created_at 
FROM creations 
GROUP BY user_id 
ORDER BY user_id 

In Oracle sqlplus

SELECT user_id, FIRST(created_at) 
FROM creations 
GROUP BY user_id 
ORDER BY user_id 

Questi ti daranno il user_id seguito dallo primocreated_at associato a quello user_id. Se si desidera un diverso created_at si ha la possibilità di sostituire in primo luogo con le altre funzioni come AVG, MIN, MAX o LAST in Oracle, si può anche provare ad aggiungere ORDER BY su altre colonne (compresi quelli che non sono tornati, per darvi un diverso created_at.

3

La tua domanda non è ben definita - quando dici che hai bisogno anche di altri dati dalla stessa riga non stai definendo quale riga.

Non dici è necessario ordinare i risultati per created_at, quindi mi supporre che si desidera valori della riga con min created_at (prima).

Questa ora diventa una delle domande SQL più comuni, ovvero il recupero di righe contenenti un valore aggregato (MIN, MAX).

Per esempio

SELECT user_id, MIN(created_at) AS created_at 
FROM creations 
GROUP BY user_id 
ORDER BY MIN(create_at) 
LIMIT 20 

Questo approccio non ti consente di (facilmente) scegliere altri valori dalla stessa riga.

Un approccio che vi permetterà di scegliere altri valori è

SELECT c.user_id, c.created_at, c.other_columns 
FROM creations c LEFT JOIN creation c_help 
    ON c.user_id = c_help.user_id AND c.created_at > c_help.create_at 
WHERE c_help IS NULL 
ORDER BY c.created_at 
LIMIT 20 
+0

+1 Bingo. Manca il punto di aggregazione. Senza aggregazione non vi è motivo per cui ci sia un 'DISTINCT' su una singola colonna meno l'intero set di risultati. – Matthew

+0

Ok, diciamo che la tabella delle creazioni ha i seguenti campi: 'id, user_id, created_at, foo, bar, long_description'. Quello che voglio è tirare le 20 creazioni più recenti 'ORDER BY created_at DESC', ma filtrare i risultati per utente in modo che ci possa essere una sola creazione per utente nel set di risultati. Vorrei anche portare con me altri campi come 'foo' e' bar', ma lasciare 'long_descrpition'. La risposta è davvero quella di unire una creazione a se stessa? – mindtonic

+0

@mindtonic, è una risposta, forse non è la risposta per te. Inoltre, dovresti provarlo e testarlo. Ci sono altri approcci: ovviamente DISTINCT ON funziona (con un po 'di riordino), anche la subquery correlata funzionerebbe, ecc ... – Unreason

3

Se si desidera che la created_at più recente per ogni utente, allora ti suggerisco di aggregare in questo modo:

SELECT user_id, MAX(created_at) 
FROM creations 
WHERE .... 
GROUP BY user_id 
ORDER BY created_at DESC 

Ciò restituirà la più recente created_at per ogni user_id Se si desidera solo la top 20, quindi aggiungere

LIMIT 20 

EDIT: Questa è fondamentalmente la stessa cosa sopra descritta da Unreason ... definire da quale riga si vogliono i dati per aggregazione.