2012-09-17 6 views
18

Ich habe 3 Tabellen:Zwei SQL LEFT JOINS falsches Ergebnis erzeugen

users(id, account_balance) 
grocery(user_id, date, amount_paid) 
fishmarket(user_id, date, amount_paid) 

Sowohl fishmarket und grocery Tabellen können mehrere Vorkommen für die gleiche User_id mit unterschiedlichen Daten und Beträge bezahlt haben oder gar nichts für einen bestimmten Benutzer . Wenn ich versuche, die folgende Abfrage:

SELECT 
    t1."id" AS "User ID", 
    t1.account_balance AS "Account Balance", 
    count(t2.user_id) AS "# of grocery visits", 
    count(t3.user_id) AS "# of fishmarket visits" 
FROM users t1 
LEFT OUTER JOIN grocery t2 ON (t2.user_id=t1."id") 
LEFT OUTER JOIN fishmarket t3 ON (t3.user_id=t1."id") 
GROUP BY t1.account_balance,t1.id 
ORDER BY t1.id 

es sich um eine falsche Ergebnisse: "1", "12", "12".
Aber wenn ich zu LEFT JOIN zu nur einer Tabelle versuche, produziert es ein korrektes Ergebnis für entweder grocery oder fishmarket Besuche, die "1", "3", "4" sind.

Was mache ich hier falsch?
Ich benutze PostgreSQL 9.1.

Antwort

37

Joins werden von links nach rechts verarbeitet (außer wenn die Klammern etwas anderes vorschreiben). Wenn Sie LEFT JOIN (oder nur JOIN, ähnlicher Effekt) drei Lebensmittel zu einem Benutzer erhalten Sie 3 Zeilen (1 x 3). Wenn Sie dann 4 Fischmärkte für den gleichen Benutzer beitreten, erhalten Sie 12 (3 x 4) Zeilen, multiplizieren die vorherige Zählung im Ergebnis, nicht Hinzufügen von zu ihm, wie Sie es sich erhofft haben.
Dadurch multipliziert die Besuche für Lebensmittel und Fischmärkte gleichermaßen.

Es sollte so funktionieren:

SELECT u.id 
    , u.account_balance 
    , g.grocery_visits 
    , f.fishmarket_visits 
FROM users u 
LEFT JOIN (
    SELECT user_id, count(*) AS grocery_visits 
    FROM grocery 
    GROUP BY user_id 
    ) g ON g.user_id = u.id 
LEFT JOIN (
    SELECT user_id, count(*) AS fishmarket_visits 
    FROM fishmarket 
    GROUP BY user_id 
    ) f ON f.user_id = u.id 
ORDER BY u.id; 

Um aggregierte Werte für einen oder wenige Benutzer, korrelierte Unterabfragenlike @Vince provided sind ganz gut zu sehen. Für eine ganze Tabelle oder größere Teile davon ist es (viel) effizienter, die N-Tabellen zu aggregieren und sich dem Ergebnis einmal anzuschließen. Auf diese Weise brauchen wir auch keinen weiteren GROUP BY in der äußeren Abfrage.

+2

Wir gratulieren tatsächlich Beantwortung der Frage nicht nur geben, eine Lösung. – xception

+0

Arbeitscode in diesem [link] (http://rexttester.com/ZFFE32806). – HeyJude

+0

@ErwinBrandstetter Ich habe so viel über Postgres von deinen Posts gelernt. Hast du jemals darüber nachgedacht, ein Buch zu diesem Thema zu schreiben? –

2

Es ist, weil, wenn die Benutzertabelle mit dem Lebensmittelgeschäft Tisch verbindet, gibt es 3 Datensätze übereinstimmen. Dann stimmt jede dieser drei Aufzeichnungen mit den 4 Aufzeichnungen im Fischmarkt überein und produziert 12 Aufzeichnungen. Sie benötigen Unterabfragen, um das zu erhalten, wonach Sie suchen.

7

Für Ihre ursprüngliche Abfrage, wenn Sie die Gruppe wegnehmen, um das vorgruppierte Ergebnis zu sehen, werden Sie sehen, warum die Zählungen, die Sie erhalten haben, erstellt wurden.

Vielleicht ist die folgende Abfrage Subqueries verwendet würde Ihr gewünschte Ergebnis erzielen:

SELECT 
t1."id" AS "User ID", 
t1.account_balance AS "Account Balance", 
(SELECT count(*) FROM grocery  t2 ON (t2.user_id=t1."id")) AS "# of grocery visits", 
(SELECT count(*) FROM fishmarket t3 ON (t3.user_id=t1."id")) AS "# of fishmarket visits" 
FROM users t1 
ORDER BY t1.id