2012-03-20 13 views
121

voglio eseguire questa query:PostgreSQL distinti su con diversi ORDER BY

SELECT DISTINCT ON (address_id) purchases.address_id, purchases.* 
FROM purchases 
WHERE purchases.product_id = 1 
ORDER BY purchases.purchased_at DESC 

ma ottengo questo errore:

PG::Error: ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions

Aggiunta address_id come primo ORDER BY espressione silenzi l'errore, ma ho davvero non voglio aggiungere l'ordinamento su address_id. È possibile fare a meno dell'ordine address_id?

+0

tuo clausola ordine è purchased_at non address_id.Can fate la vostra domanda chiara. – Teja

+0

il mio ordine ha acquistato perché lo voglio, ma Postgres richiede anche l'indirizzo (vedi messaggio di errore). –

+0

Completamente risposto qui - http://stackoverflow.com/questions/9796078/selecting-rows-ordered-by-some-column-and-disctinct-on-another grazie a http://stackoverflow.com/users/ 268273/mosty-mostacho –

risposta

114

documentazione dice:

DISTINCT ON (expression [, ...]) keeps only the first row of each set of rows where the given expressions evaluate to equal. [...] Note that the "first row" of each set is unpredictable unless ORDER BY is used to ensure that the desired row appears first. [...] The DISTINCT ON expression(s) must match the leftmost ORDER BY expression(s).

Official documentation

quindi dovrete aggiungere il address_id all'ordine da.

In alternativa, se si sta cercando la riga completa contenente il prodotto acquistato più recente per ogni address_id e il risultato ordinato per purchased_at, si sta tentando di risolvere un problema N maggiore per gruppo che può essere risolto dal seguenti approcci:

La soluzione generale che dovrebbe funzionare nella maggior parte dei DBMS:

SELECT t1.* FROM purchases t1 
JOIN (
    SELECT address_id, max(purchased_at) max_purchased_at 
    FROM purchases 
    WHERE product_id = 1 
    GROUP BY address_id 
) t2 
ON t1.address_id = t2.address_id AND t1.purchased_at = t2.max_purchased_at 
ORDER BY t1.purchased_at DESC 

Una soluzione più PostgreSQL orientata in base alla risposta di @ HKF:

SELECT * FROM (
    SELECT DISTINCT ON (address_id) * 
    FROM purchases 
    WHERE product_id = 1 
    ORDER BY address_id, purchased_at DESC 
) t 
ORDER BY purchased_at DESC 

problema chiarito, esteso e risolto qui: Selecting rows ordered by some column and distinct on another

+36

Funziona, ma fornisce un ordine errato. Ecco perché voglio sbarazzarmi di address_id nell'articolo –

+0

La documentazione è chiara: non è possibile perché la riga selezionata sarà imprevedibile –

+2

Ma potrebbe esserci un altro modo per selezionare gli ultimi acquisti per gli indirizzi disticnt? –

47

È possibile ordinare da address_id in una sottoquery, quindi ordinare in base a ciò che si desidera in una query esterna.

SELECT * FROM 
    (SELECT DISTINCT ON (address_id) purchases.address_id, purchases.* 
    FROM "purchases" 
    WHERE "purchases"."product_id" = 1 ORDER BY address_id DESC) 
ORDER BY purchased_at DESC 
+2

Ma questo sarà più lento di una sola query, no? –

+2

Molto marginalmente si. Anche se dal momento che hai acquisti. * Nel tuo originale 'select', non penso che questo sia il codice di produzione? – hkf

+7

Vorrei aggiungere che per le versioni più recenti di postgres è necessario alias della sottoquery. Ad esempio: SELECT * FROM (SELECT DISTINCT ON (address_id) purchase.address_id, acquisti. * FROM "acquisti" WHERE "acquisti". "Product_id" = 1 ORDINE BY address_id DESC) AS tmp ORDINE BY tmp.purchased_at DESC – aembke

23

Un sottoquery può risolverlo:

SELECT * 
FROM (
    SELECT DISTINCT ON (address_id) * 
    FROM purchases 
    WHERE product_id = 1 
    ) p 
ORDER BY purchased_at DESC; 

espressioni di primo piano nel ORDER BY devono essere d'accordo con le colonne in DISTINCT ON, quindi non è possibile ordinare per diverse colonne nello stesso SELECT.

utilizzare solo una ulteriore ORDER BY nella subquery se si desidera scegliere una particolare riga da ogni set:

SELECT * 
FROM (
    SELECT DISTINCT ON (address_id) * 
    FROM purchases 
    WHERE product_id = 1 
    ORDER BY address_id, purchased_at DESC -- get "latest" row per address_id 
    ) p 
ORDER BY purchased_at DESC; 

Se purchased_at può essere NULL, prendere in considerazione DESC NULLS LAST.
correlate, con più spiegazioni:

+0

Non è possibile utilizzare 'DISTINCT ON' senza un' ORDER BY 'corrispondente. La prima query richiede un 'ORDER BY address_id' all'interno della subquery. –

+0

@AristotlePagaltzis: Ma tu * puoi *. Da dove lo hai preso, non è corretto. Puoi usare 'DISTINCT ON' senza' ORDER BY' nella stessa query. In questo caso si ottiene una riga arbitraria da ogni serie di peer definita dalla clausola 'DISTINCT ON'. Provalo o segui i link sopra per dettagli e collegamenti al manuale. 'ORDER BY' nella stessa query (lo stesso' SELECT') non può essere in disaccordo con 'DISTINCT ON'. L'ho spiegato anche io. –

+0

Huh, hai ragione. Sono stato cieco alle implicazioni dell '"imprevedibile a meno che non si usi" ORDER BY "nei documenti perché non ha senso per me che la funzione sia implementata per poter gestire insiemi di valori non consecutivi ... eppure ha vinto" Ti permettono di sfruttarlo con un ordine esplicito. Fastidioso. –

10

Funzione finestra potrebbe risolvere che in un solo passaggio:

SELECT DISTINCT ON (address_id) 
    LAST_VALUE(purchases.address_id) OVER wnd AS address_id 
FROM "purchases" 
WHERE "purchases"."product_id" = 1 
WINDOW wnd AS (
    PARTITION BY address_id ORDER BY purchases.purchased_at DESC 
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) 
+3

Sarebbe bello se qualcuno avesse spiegato la domanda. – Gajus

+0

@Gajus: breve spiegazione: non funziona, restituisce solo 'address_id' distinto. Il principio * potrebbe * funzionare, però. Esempi correlati: https://stackoverflow.com/a/22064571/939860 o https://stackoverflow.com/a/11533808/939860. Ma ci sono domande più brevi e/o più veloci per il problema in questione. –

1

per chiunque utilizzi Flask-SQLAlchemy, questo ha funzionato per me

from app import db 
from app.models import Purchases 
from sqlalchemy.orm import aliased 
from sqlalchemy import desc 

stmt = Purchases.query.distinct(Purchases.address_id).subquery('purchases') 
alias = aliased(Purchases, stmt) 
distinct = db.session.query(alias) 
distinct.order_by(desc(alias.purchased_at)) 
+0

Sì, o ancora più semplice, sono stato in grado di usare: 'query.distinct (foo) .from_self(). Order (bar)' –

+0

@LaurentMeyer intendi 'Purchases.query'? – reubano

+0

Sì, intendevo Purchases.query –

-2

È inoltre possibile fare ciò utilizzando clausola group by

SELECT purchases.address_id, purchases.* FROM "purchases" 
    WHERE "purchases"."product_id" = 1 GROUP BY address_id, 
purchases.purchased_at ORDER purchases.purchased_at DESC 
+0

Questo non è corretto (a meno che 'acquisti' abbia solo le due colonne' address_id' e 'purchase_at'). A causa di 'GROUP BY', sarà necessario utilizzare una funzione di aggregazione per ottenere il valore di ogni colonna non utilizzata per il raggruppamento, in modo che i valori provengano tutti da diverse file del gruppo a meno che non si passi attraverso una ginnastica brutta e inefficiente. Questo può essere risolto solo usando le funzioni della finestra piuttosto che 'GROUP BY'. –