Wie kann (oder kann) ich DISTINCT für mehrere Spalten auswählen?

413

Ich muss alle Zeilen aus einer Tabelle abrufen, in der zwei Spalten zusammen unterschiedlich sind. Ich möchte also alle Verkäufe, die keine anderen Verkäufe haben, die am selben Tag zum gleichen Preis getätigt wurden. Die Verkäufe, die basierend auf Tag und Preis eindeutig sind, werden auf einen aktiven Status aktualisiert.

Also denke ich:

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

Aber mein Gehirn tut weh, wenn ich noch weiter gehe.

sheats
quelle

Antworten:

436
SELECT DISTINCT a,b,c FROM t

ist ungefähr gleichbedeutend mit:

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

Es ist eine gute Idee, sich an die GROUP BY-Syntax zu gewöhnen, da diese leistungsfähiger ist.

Für Ihre Anfrage würde ich es so machen:

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
 )
Joel Coehoorn
quelle
117
Diese Abfrage ist zwar korrekt und wird seit einem Jahr akzeptiert, ist jedoch äußerst ineffizient und unnötig. Benutze das nicht. Ich gab eine Alternative und eine Erklärung in einer anderen Antwort.
Erwin Brandstetter
1
Ist SELECT DISTINCT a, b, c FROM t nicht genau dasselbe wie SELECT a, b, c FROM t GROUP BY a, b, c?
Famargar
8
@famargar für den einfachen Fall, aber sie haben semantisch unterschiedliche Bedeutungen und unterscheiden sich darin, was Sie für den Schritt beim Erstellen einer größeren Abfrage tun können. Außerdem können Leute in Tech-Foren oft sehr umständlich sein. Ich finde es oft nützlich, meinen Posts in diesem Zusammenhang Wieselwörter hinzuzufügen.
Joel Coehoorn
344

Wenn Sie die bisherigen Antworten zusammenstellen, aufräumen und verbessern, gelangen Sie zu dieser überlegenen Frage:

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

Welches ist viel schneller als beide. Nukes die Leistung der aktuell akzeptierten Antwort um den Faktor 10 - 15 (in meinen Tests auf PostgreSQL 8.4 und 9.1).

Dies ist jedoch noch lange nicht optimal. Verwenden Sie einen NOT EXISTS(Anti-) Semi-Join für eine noch bessere Leistung. EXISTSist Standard-SQL, gibt es schon immer (zumindest seit PostgreSQL 7.2, lange bevor diese Frage gestellt wurde) und passt perfekt zu den vorgestellten Anforderungen:

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 <> hier fummeln
Alte SQL-Geige

Eindeutiger Schlüssel zur Identifizierung der Zeile

Wenn Sie ( idim Beispiel) keinen Primär- oder eindeutigen Schlüssel für die Tabelle haben , können Sie ctidden Zweck dieser Abfrage durch die Systemspalte ersetzen (jedoch nicht für andere Zwecke):

   AND    s1.ctid <> s.ctid

Jede Tabelle sollte einen Primärschlüssel haben. Fügen Sie eine hinzu, wenn Sie noch keine hatten. Ich schlage ein serialoder ein vorIDENTITY Spalte in Postgres 10+ vor.

Verbunden:

Wie geht das schneller?

Die Unterabfrage im EXISTSAnti-Semi-Join kann die Auswertung beenden, sobald der erste Betrüger gefunden wird (es macht keinen Sinn, weiter zu suchen). Für eine Basistabelle mit wenigen Duplikaten ist dies nur geringfügig effizienter. Mit vielen Duplikaten wird diese Art und Weise effizienter zu gestalten .

Leere Updates ausschließen

Für Zeilen, die status = 'ACTIVE'dieses Update bereits haben, würde sich nichts ändern, aber dennoch eine neue Zeilenversion zum vollen Preis einfügen (kleinere Ausnahmen gelten). Normalerweise willst du das nicht. Fügen Sie eine weitere WHEREBedingung wie oben gezeigt hinzu, um dies zu vermeiden und noch schneller zu machen:

Wenn statusdefiniert ist NOT NULL, können Sie vereinfachen, um:

AND status <> 'ACTIVE';

Subtiler Unterschied in der NULL-Behandlung

Diese Abfrage behandelt (im Gegensatz zu der derzeit von Joel akzeptierten Antwort ) NULL-Werte nicht als gleich. Die folgenden zwei Zeilen für (saleprice, saledate)würden als "verschieden" qualifiziert (obwohl sie mit dem menschlichen Auge identisch aussehen):

(123, NULL)
(123, NULL)

Übergibt auch einen eindeutigen Index und fast überall sonst, da NULL-Werte gemäß dem SQL-Standard nicht gleich sind. Sehen:

OTOH, GROUP BY, DISTINCToder DISTINCT ON ()treat NULL - Werte als gleich. Verwenden Sie einen geeigneten Abfragestil, je nachdem, was Sie erreichen möchten. Sie können diese schnellere Abfrage weiterhin mit IS NOT DISTINCT FROManstelle von =für einen oder alle Vergleiche verwenden, um den NULL-Vergleich gleich zu machen. Mehr:

Wenn alle verglichenen Spalten definiert sind NOT NULL, gibt es keinen Raum für Meinungsverschiedenheiten.

Erwin Brandstetter
quelle
16
Gute Antwort. Ich bin ein SQL Server-Typ, daher würde mir der erste Vorschlag, ein Tupel mit einer IN () - Prüfung zu verwenden, nicht einfallen. Der nicht vorhandene Vorschlag führt normalerweise zum gleichen Ausführungsplan auf dem SQL Server wie der innere Join.
Joel Coehoorn
2
Nett. Die Erklärung erhöht den Wert der Antwort erheblich. Ich bin fast versucht, einige Tests mit Oracle durchzuführen, um zu sehen, wie die Pläne mit Postgres und SQLServer verglichen werden.
Peter
2
@alairock: Woher hast du das? Für Postgres ist das Gegenteil der Fall. Während alle Zeilen zu zählen, count(*)ist mehr effizienter als count(<expression>). Probier es einfach. Postgres hat eine schnellere Implementierung für diese Variante der Aggregatfunktion. Vielleicht verwechseln Sie Postgres mit einem anderen RDBMS?
Erwin Brandstetter
6
@alairock: Ich bin Co-Autor dieser Seite und sie sagt nichts dergleichen aus.
Erwin Brandstetter
2
@ErwinBrandstetter, Sie sind mit Ihren Antworten immer auf dem neuesten Stand. Sie haben im Laufe der Jahre auf nahezu unvorstellbare Weise geholfen. In diesem Beispiel kannte ich verschiedene Möglichkeiten, um mein Problem zu lösen, aber ich wollte sehen, dass jemand die Effizienz zwischen den Möglichkeiten getestet hat. Vielen Dank.
WebWanderer
24

Das Problem bei Ihrer Abfrage besteht darin, dass Sie bei Verwendung einer GROUP BY-Klausel (die Sie im Wesentlichen mit einer eindeutigen Klausel ausführen) nur Spalten verwenden können, nach denen Sie Funktionen gruppieren oder aggregieren. Sie können die Spalten-ID nicht verwenden, da möglicherweise unterschiedliche Werte vorhanden sind. In Ihrem Fall gibt es aufgrund der HAVING-Klausel immer nur einen Wert, aber die meisten RDBMS sind nicht intelligent genug, um dies zu erkennen.

Dies sollte jedoch funktionieren (und benötigt keinen Join):

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

Sie können auch MAX oder AVG anstelle von MIN verwenden. Es ist nur wichtig, eine Funktion zu verwenden, die den Wert der Spalte zurückgibt, wenn nur eine übereinstimmende Zeile vorhanden ist.

Christian Berg
quelle
1

Ich möchte die unterschiedlichen Werte aus einer Spalte 'GrondOfLucht' auswählen, aber sie sollten in der Reihenfolge sortiert werden, die in der Spalte 'Sortieren' angegeben ist. Ich kann nicht die eindeutigen Werte nur einer Spalte verwenden

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

Außerdem wird die Spalte "Sortieren" angezeigt. Da "GrondOfLucht" UND "Sortieren" nicht eindeutig ist, werden ALLE Zeilen ausgegeben.

Verwenden Sie die GRUPPE, um die Datensätze von 'GrondOfLucht' in der durch 'Sortieren angegebenen Reihenfolge' auszuwählen

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)
frans eilering
quelle
Dies erklärt im Grunde, was die akzeptierte Antwort bewirkt, aber ich würde empfehlen, solche Namen nicht als Beispiel zu verwenden (zumindest zu übersetzen). PS: Ich empfehle, in allen Projekten immer alles auf Englisch zu benennen, auch wenn Sie Niederländer sind.
Kerwin Sneijders
0

Wenn Ihr DBMS die Unterscheidung mit mehreren Spalten wie folgt nicht unterstützt:

select distinct(col1, col2) from table

Mehrfachauswahl kann im Allgemeinen wie folgt sicher ausgeführt werden:

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

Da dies auf den meisten DBMS funktionieren kann und dies voraussichtlich schneller als die Gruppierung nach Lösung ist, vermeiden Sie die Gruppierungsfunktionalität.

Abdulhafeth Sartawi
quelle