it-swarm.com.de

SUMME über verschiedene Zeilen mit mehreren Verknüpfungen

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:

  1. Wie viele Artikel hatten wir letzten Monat in jedem Land?
  2. Was war der insgesamt verdiente Betrag (Summe der Zahlungen. Beträge) in jedem Land?
  3. Was waren die Gesamtkosten (Summe der Artikel.Preis) in jedem Land?
  4. 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 im Fall von LEFT OUTER JOIN Es gibt 2 Zeilen für Elemente mit items.id = 1 (und so weiter für andere Übereinstimmungen), aber ich weiß nicht, wie ich eine richtige Abfrage erstellen soll.

Fragen :

  1. Wie vermeide ich falsche Ergebnisse bei der Aggregation in Abfragen in mehreren Tabellen?
  2. Was ist der beste Weg, um die Summe über verschiedene Werte zu berechnen (in diesem Fall items.id)?

PostgreSQL-Version : 9.6.1

10
Stranger6667

Da es mehrere payments und mehrere extras pro item geben kann, stoßen Sie auf einen "Proxy Cross Join" zwischen diesen beiden Tabellen. Aggregierte Zeilen pro item_id vor Verbinden mit item und alles sollte korrekt sein:

SELECT i.country         AS group_by
     , COUNT(*)          AS item_count
     , SUM(i.price)      AS cost
     , SUM(p.sum_amount) AS earned
     , SUM(e.sum_amount) AS extra_earned
FROM  items i
LEFT  JOIN (
   SELECT item_id, SUM(amount) AS sum_amount
   FROM   payments
   GROUP  BY 1
   ) p ON p.item_id = i.id
LEFT  JOIN (
   SELECT item_id, SUM(amount) AS sum_amount
   FROM   extras
   GROUP  BY 1
   ) e ON e.item_id = i.id
GROUP BY 1;

Betrachten Sie das Beispiel "Fischmarkt":

Um genau zu sein, wäre SUM(i.price) nach dem Zusammenfügen zu einer einzelnen n-Tabelle falsch, 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 in items multiplizieren, können wir einfach die billigere count(*) anstelle von count(DISTINCT i.id) verwenden. (id ist NOT NULL PRIMARY KEY.)

SQL Fiddle.

Aber wenn ich nach items.created Filtern möchte?

Adressiert Ihren Kommentar.

Es hängt davon ab, ob. Können wir den gleichen Filter auf payments.created Und extras.created Anwenden?

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 Bruchteil von Elementen auswählen, schlage ich korrelierte Unterabfragen oder LATERAL -Verbindungen vor. Beispiele:

9