Schema :
CREATE TABLE "items" (
"id" SERIAL NOT NULL PRIMARY KEY,
"country" VARCHAR(2) NOT NULL,
"created" TIMESTAMP WITH TIME ZONE NOT NULL,
"price" NUMERIC(11, 2) NOT NULL
);
CREATE TABLE "payments" (
"id" SERIAL NOT NULL PRIMARY KEY,
"created" TIMESTAMP WITH TIME ZONE NOT NULL,
"amount" NUMERIC(11, 2) NOT NULL,
"item_id" INTEGER NULL
);
CREATE TABLE "extras" (
"id" SERIAL NOT NULL PRIMARY KEY,
"created" TIMESTAMP WITH TIME ZONE NOT NULL,
"amount" NUMERIC(11, 2) NOT NULL,
"item_id" INTEGER NULL
);
Daten :
INSERT INTO items VALUES
(1, 'CZ', '2016-11-01', 100),
(2, 'CZ', '2016-11-02', 100),
(3, 'PL', '2016-11-03', 20),
(4, 'CZ', '2016-11-04', 150)
;
INSERT INTO payments VALUES
(1, '2016-11-01', 60, 1),
(2, '2016-11-01', 60, 1),
(3, '2016-11-02', 100, 2),
(4, '2016-11-03', 25, 3),
(5, '2016-11-04', 150, 4)
;
INSERT INTO extras VALUES
(1, '2016-11-01', 5, 1),
(2, '2016-11-02', 1, 2),
(3, '2016-11-03', 2, 3),
(4, '2016-11-03', 3, 3),
(5, '2016-11-04', 5, 4)
;
Also haben wir:
- 3 Artikel in CZ in 1 in PL
- 370 in CZ und 25 in PL verdient
- 350 Kosten in CZ und 20 in PL
- 11 extra verdient in CZ und 5 extra verdient in PL
Jetzt möchte ich Antworten auf folgende Fragen erhalten:
- Wie viele Artikel hatten wir letzten Monat in jedem Land?
- Was war der insgesamt verdiente Betrag (Summe der Zahlungen. Beträge) in jedem Land?
- Was waren die Gesamtkosten (Summe der Artikel.Preis) in jedem Land?
- Wie hoch war der zusätzliche Gesamtverdienst (Summe der Extras) in jedem Land?
Mit der folgenden Abfrage ( SQLFiddle ):
SELECT
country AS "group_by",
COUNT(DISTINCT items.id) AS "item_count",
SUM(items.price) AS "cost",
SUM(payments.amount) AS "earned",
SUM(extras.amount) AS "extra_earned"
FROM items
LEFT OUTER JOIN payments ON (items.id = payments.item_id)
LEFT OUTER JOIN extras ON (items.id = extras.item_id)
GROUP BY 1;
Die Ergebnisse sind falsch:
group_by | item_count | cost | earned | extra_earned
----------+------------+--------+--------+--------------
CZ | 3 | 450.00 | 370.00 | 16.00
PL | 1 | 40.00 | 50.00 | 5.00
Kosten und extra_earned für CZ sind ungültig - 450 statt 350 und 16 statt 11. Kosten und verdient für PL sind ebenfalls ungültig - sie werden verdoppelt.
Ich verstehe, dass es im Fall von LEFT OUTER JOIN
2 Zeilen für Artikel mit items.id = 1 gibt (und so weiter für andere Übereinstimmungen), aber ich weiß nicht, wie man eine richtige Abfrage erstellt.
Fragen :
- Wie vermeide ich falsche Ergebnisse bei der Aggregation in Abfragen in mehreren Tabellen?
- Was ist der beste Weg, um die Summe über verschiedene Werte zu berechnen (in diesem Fall items.id)?
PostgreSQL-Version : 9.6.1
postgresql
join
aggregate
Fremder6667
quelle
quelle
OUTER APPLY
und stattdessenLATERAL
Joins verwenden.Seq Scan
Zahlungen erforderlich , was bedeutet, dass die Statistik für alle Elemente neu berechnet wird. Ich habe dies in der Frage nicht erwähnt, aber ich möchte Elemente auch nach Erstellungszeit filtern, sodass ich nur eine bestimmte Teilmenge der aggregierten Daten benötige. Ich werde die Frage aktualisierenWHERE
den Unterabfragen Klauseln oder Verknüpfungen hinzufügen. Aktivieren Sie aber auch Option 4 mitLATERAL
.payments
unditems
in Unterabfrage , und fügen SieWHERE
es? Ich muss alle Optionen vergleichen :)items.created_at
, ja.Antworten:
Da es mehrere
payments
und mehrereextras
pro geben kann, stoßenitem
Sie auf einen "Proxy-Cross-Join" zwischen diesen beiden Tabellen. Aggregieren Sie die Zeilenitem_id
vor dem Beitritt zuitem
und es sollte alles korrekt sein:Betrachten Sie das Beispiel "Fischmarkt":
Um genau zu sein,
SUM(i.price)
wäre es falsch , wenn man sich einer einzelnen n-Tabelle anschließt, die jeden Preis mit der Anzahl der zugehörigen Zeilen multipliziert. Wenn Sie es zweimal machen, wird es nur noch schlimmer - und möglicherweise auch rechenintensiv.Oh, und da wir jetzt keine Zeilen multiplizieren
items
, können wircount(*)
stattdessen einfach die billigeren verwendencount(DISTINCT i.id)
. (id
SeinNOT NULL PRIMARY KEY
.)SQL Fiddle.
Aber wenn ich filtern will
items.created
?Adressierung Ihres Kommentars.
Es hängt davon ab, ob. Können wir den gleichen Filter auf
payments.created
und anwendenextras.created
?Wenn ja, fügen Sie einfach die Filter auch in die Unterabfragen ein. (Scheint in diesem Fall nicht wahrscheinlich.)
Wenn nein, aber wir wählen immer noch die meisten Elemente aus , wäre die obige Abfrage immer noch am effizientesten. Einige der Aggregationen in den Unterabfragen werden in den Joins entfernt, aber das ist immer noch billiger als komplexere Abfragen.
Wenn nein, und wir einen kleinen Teil der Elemente auswählen , schlage ich korrelierte Unterabfragen oder Verknüpfungen vor
LATERAL
. Beispiele:quelle
items.created
wie geht das am effizientesten? Soll ich hinzufügen , zusätzlicheJOIN
aufitems
Unterabfragen (p
unde
in Ihrem Beispiel) , wie Filtration , wie ausführen @ ypercubeᵀᴹ erwähnt?LATERAL JOIN
funktioniert bei mir! Vielen Dank für die saubere Erklärung :)