Schéma :
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
);
Données :
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)
;
Donc nous avons:
- 3 articles en CZ en 1 en PL
- 370 gagnés en CZ et 25 en PL
- 350 en CZ et 20 en PL
- 11 supplémentaires gagnés en CZ et 5 supplémentaires gagnés en PL
Maintenant, je veux obtenir des réponses aux questions suivantes:
- Combien d'articles nous avions le mois dernier dans chaque pays?
- Quel était le montant total gagné (somme des paiements, montants) dans chaque pays?
- Quel était le coût total (somme des articles.prix) dans chaque pays?
- Quel a été le total des gains supplémentaires (somme des extras.montant) dans chaque pays?
Avec la requête suivante ( 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;
Les résultats sont faux:
group_by | item_count | cost | earned | extra_earned
----------+------------+--------+--------+--------------
CZ | 3 | 450.00 | 370.00 | 16.00
PL | 1 | 40.00 | 50.00 | 5.00
Le coût et extra_earned pour CZ sont invalides - 450 au lieu de 350 et 16 au lieu de 11. Le coût et gagné pour PL sont également invalides - ils sont doublés.
Je comprends qu'en cas LEFT OUTER JOIN
il y aura 2 lignes pour l'élément avec items.id = 1 (et ainsi de suite pour les autres correspondances), mais je ne sais pas comment construire une requête appropriée.
Questions :
- Comment éviter les mauvais résultats d'agrégation dans les requêtes sur plusieurs tables?
- Quelle est la meilleure façon de calculer la somme sur des valeurs distinctes (items.id dans ce cas)?
Version PostgreSQL : 9.6.1
Seq Scan
paiements, ce qui signifie que les statistiques seront recalculées sur tous les articles. Je ne l'ai pas mentionné dans la question, mais je veux également filtrer les éléments par heure de création, donc je n'aurai besoin que d'un sous-ensemble spécifique des données agrégées. Je mettrai à jour la question
WHERE
clauses ou des jointures dans les sous-requêtes. Mais cochez également l'option 4 en utilisant LATERAL
.
payments
et items
dans la sous-requête et y ajouter WHERE
? Je devrai comparer toutes les options :)
items.created_at
, oui.
OUTER APPLY
et en utilisant desLATERAL
jointures à la place.