Come posso (o posso) SELEZIONARE DISTINCT su più colonne?


415

Devo recuperare tutte le righe da una tabella in cui 2 colonne combinate sono tutte diverse. Quindi desidero tutte le vendite che non hanno altre vendite avvenute lo stesso giorno allo stesso prezzo. Le vendite uniche in base al giorno e al prezzo verranno aggiornate a uno stato attivo.

Quindi sto pensando:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

Ma il mio cervello fa male andando oltre.

Risposte:


436
SELECT DISTINCT a,b,c FROM t

è approssimativamente equivalente a:

SELECT a,b,c FROM t GROUP BY a,b,c

È una buona idea abituarsi alla sintassi GROUP BY, poiché è più potente.

Per la tua domanda, lo farei così:

UPDATE sales
SET status='ACTIVE'
WHERE id IN
(
    SELECT id
    FROM sales S
    INNER JOIN
    (
        SELECT saleprice, saledate
        FROM sales
        GROUP BY saleprice, saledate
        HAVING COUNT(*) = 1 
    ) T
    ON S.saleprice=T.saleprice AND s.saledate=T.saledate
 )

117
Questa query, benché corretta e accettata da anni, è estremamente inefficiente e inutilmente. Non usare questo. Ho fornito un'alternativa e alcune spiegazioni in un'altra risposta.
Erwin Brandstetter,

1
SELECT DISTINCT a, b, c FROM t non è esattamente la stessa cosa di SELECT a, b, c FROM t GROUP BY a, b, c?
Famargar,

8
@famargar per il semplice caso, tuttavia, hanno semanticamente significati diversi e sono diversi in termini di cosa si può fare per il passaggio quando si crea una query più grande. Inoltre, le persone nei forum tecnologici possono spesso essere estremamente pedanti riguardo alle cose, trovo spesso utile aggiungere parole weasel ai miei post in questo contesto.
Joel Coehoorn,

344

Se metti insieme le risposte finora, pulisci e migliora, arriveresti a questa domanda superiore:

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 1 
    );

Che è molto più veloce di entrambi. Nukes le prestazioni della risposta attualmente accettata dal fattore 10-15 (nei miei test su PostgreSQL 8.4 e 9.1).

Ma questo è ancora lungi dall'essere ottimale. Utilizzare un NOT EXISTSsemi-join (anti-) per prestazioni ancora migliori. EXISTSè SQL standard, esiste da sempre (almeno da PostgreSQL 7.2, molto prima che questa domanda fosse posta) e si adatta perfettamente ai requisiti presentati:

UPDATE sales s
SET    status = 'ACTIVE'
WHERE  NOT EXISTS (
   SELECT FROM sales s1                     -- SELECT list can be empty for EXISTS
   WHERE  s.saleprice = s1.saleprice
   AND    s.saledate  = s1.saledate
   AND    s.id <> s1.id                     -- except for row itself
   )
AND    s.status IS DISTINCT FROM 'ACTIVE';  -- avoid empty updates. see below

db <> violino qui
Vecchio SQL Fiddle

Chiave univoca per identificare la riga

Se non si dispone di una chiave primaria o unica per la tabella ( idnell'esempio), è possibile sostituire con la colonna di sistema ctidlo scopo di questa query (ma non per altri scopi):

   AND    s1.ctid <> s.ctid

Ogni tabella dovrebbe avere una chiave primaria. Aggiungine uno se non ne hai ancora uno. Suggerisco una serialo una IDENTITYcolonna in Postgres 10+.

Relazionato:

Come è più veloce?

La sottoquery EXISTSnell'anti-semi-join può interrompere la valutazione non appena viene trovato il primo duplicato (non è necessario guardare oltre). Per una tabella di base con pochi duplicati, questo è solo leggermente più efficiente. Con molti duplicati questo diventa molto più efficiente.

Escludere aggiornamenti vuoti

Per le righe che già dispongono di status = 'ACTIVE'questo aggiornamento non cambierebbe nulla, ma inseriva comunque una nuova versione di riga a costo pieno (si applicano eccezioni minori). Normalmente, non lo vuoi. Aggiungi un'altra WHEREcondizione come mostrato sopra per evitarlo e renderlo ancora più veloce:

Se statusdefinito NOT NULL, è possibile semplificare per:

AND status <> 'ACTIVE';

Il tipo di dati della colonna deve supportare l' <>operatore. Alcuni tipi come jsonno. Vedere:

Sottile differenza nella gestione NULL

Questa query (a differenza della risposta attualmente accettata da Joel ) non considera i valori NULL uguali. Le seguenti due righe per (saleprice, saledate)si qualificherebbero "distinte" (sebbene sembrino identiche all'occhio umano):

(123, NULL)
(123, NULL)

Passa anche in un indice univoco e quasi ovunque, poiché i valori NULL non si equivalgono in base allo standard SQL. Vedere:

OTOH, GROUP BY, DISTINCTo DISTINCT ON ()valori trattare NULL come uguali. Utilizzare uno stile di query appropriato a seconda di ciò che si desidera ottenere. Puoi comunque utilizzare questa query più veloce con IS NOT DISTINCT FROManziché =per qualsiasi o tutti i confronti per rendere NULL il confronto uguale. Di Più:

Se vengono definite tutte le colonne da confrontare NOT NULL, non c'è spazio per il disaccordo.


16
Buona risposta. Sono un ragazzo SQL Server, quindi il primo suggerimento di utilizzare una tupla con un controllo IN () non mi verrebbe in mente. Il suggerimento inesistente di solito finisce con lo stesso piano di esecuzione in sql server del join interno.
Joel Coehoorn,

2
Bello. La spiegazione aumenta notevolmente il valore della risposta. Sono quasi tentato di eseguire alcuni test con Oracle per vedere come i piani si confrontano con Postgres e SQLServer.
Peter,

2
@alairock: dove l'hai preso? Per Postgres, è vero il contrario . Contando tutte le righe, count(*)è più efficiente di count(<expression>). Provalo e basta. Postgres ha un'implementazione più rapida per questa variante della funzione aggregata. Forse stai confondendo Postgres con qualche altro RDBMS?
Erwin Brandstetter,

6
@alairock: mi capita di essere coautore di quella pagina e non dice nulla del genere.
Erwin Brandstetter,

2
@ErwinBrandstetter, sei sempre così a punto con le tue risposte sullo stack. Hai aiutato nel corso degli anni in una quantità quasi inimmaginabile di modi. Per quanto riguarda questo esempio, conoscevo diversi modi per risolvere il mio problema, ma volevo vedere che qualcuno aveva testato l'efficienza tra le possibilità. Grazie.
WebWanderer

24

Il problema con la query è che quando si utilizza una clausola GROUP BY (che essenzialmente si fa utilizzando distinti) è possibile utilizzare solo le colonne raggruppate o le funzioni aggregate. Non è possibile utilizzare l'id della colonna perché esistono valori potenzialmente diversi. Nel tuo caso c'è sempre un solo valore a causa della clausola HAVING, ma la maggior parte dei RDBMS non sono abbastanza intelligenti da riconoscerlo.

Questo dovrebbe funzionare comunque (e non ha bisogno di un join):

UPDATE sales
SET status='ACTIVE'
WHERE id IN (
  SELECT MIN(id) FROM sales
  GROUP BY saleprice, saledate
  HAVING COUNT(id) = 1
)

È inoltre possibile utilizzare MAX o AVG anziché MIN, è importante utilizzare solo una funzione che restituisce il valore della colonna se esiste solo una riga corrispondente.


1

Voglio selezionare i valori distinti da una colonna 'GrondOfLucht' ma dovrebbero essere ordinati nell'ordine indicato nella colonna 'sortering'. Non riesco a ottenere i valori distinti di una sola colonna usando

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

Darà anche la colonna 'sortering' e poiché 'GrondOfLucht' AND 'sortering' non è univoco, il risultato sarà TUTTE le righe.

usa il GRUPPO per selezionare i record di 'GrondOfLucht' nell'ordine dato da 'sortering

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)

Questo in sostanza spiega cosa fa la risposta accettata, ma consiglierei di non usare tali nomi per un esempio (almeno tradurli). PS: consiglio di nominare sempre tutto in inglese in tutti i progetti, anche se sei olandese.
Kerwin Sneijders,

0

Se il tuo DBMS non supporta distinti con più colonne come questa:

select distinct(col1, col2) from table

La selezione multipla in generale può essere eseguita in modo sicuro come segue:

select distinct * from (select col1, col2 from table ) as x

Poiché ciò può funzionare sulla maggior parte dei DBMS e si prevede che questo sarà più veloce del raggruppamento per soluzione poiché si sta evitando la funzionalità di raggruppamento.

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.