Схема :
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
);
Данные :
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)
;
Итак, имеем:
- 3 предмета в CZ в 1 в PL
- 370 заработано в CZ и 25 в PL
- 350 стоит в CZ и 20 в PL
- 11 дополнительных заработанных в CZ и 5 дополнительных заработанных в PL
Теперь я хочу получить ответы на следующие вопросы:
- Сколько предметов у нас было в прошлом месяце в каждой стране?
- Какова была общая заработанная сумма (сумма платежей. Сумм) в каждой стране?
- Какова была общая стоимость (сумма items.price) в каждой стране?
- Каков был общий дополнительный заработок (сумма extras.amount) в каждой стране?
С помощью следующего запроса ( 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;
Результаты неверны:
group_by | item_count | cost | earned | extra_earned
----------+------------+--------+--------+--------------
CZ | 3 | 450.00 | 370.00 | 16.00
PL | 1 | 40.00 | 50.00 | 5.00
Стоимость и extra_earned для CZ недействительны - 450 вместо 350 и 16 вместо 11. Стоимость и заработанные за PL также недействительны - они удваиваются.
Я понимаю, что в случае LEFT OUTER JOIN
будет 2 строки для элемента с items.id = 1 (и так далее для других совпадений), но я не знаю, как построить правильный запрос.
Вопросы :
- Как избежать ошибочных результатов при агрегировании в запросах к нескольким таблицам?
- Каков наилучший способ расчета суммы по отдельным значениям (в этом случае items.id)?
Версия PostgreSQL : 9.6.1
postgresql
join
aggregate
Stranger6667
источник
источник
OUTER APPLY
и используяLATERAL
соединения вместо этого.Seq Scan
платежей, что означает, что статистика будет пересчитана по всем статьям. Я не упомянул об этом в этом вопросе, но я хочу также фильтровать элементы по времени создания, поэтому мне понадобится только конкретное подмножество агрегированных данных. Я обновлю вопросWHERE
предложения или объединения в подзапросах. Но проверить вариант 4 тоже используяLATERAL
.payments
иitems
в подзапросе и добавитьWHERE
к нему? Мне нужно будет сравнить все варианты :)items.created_at
, да.Ответы:
Так как может быть несколько
payments
и несколько дляextras
каждогоitem
, вы сталкиваетесь с «перекрестным соединением прокси» между этими двумя таблицами. Агрегируйте строки заitem_id
до присоединения,item
и все должно быть правильно:Рассмотрим пример "рыбного рынка":
Чтобы быть точным,
SUM(i.price)
будет неправильно после объединения в одну n-таблицу, которая умножает каждую цену на количество связанных строк. Если сделать это дважды, это только усугубит ситуацию, а также может привести к большим вычислительным затратам.Да, и так как мы не умножаем строки
items
сейчас, мы можем просто использовать более дешевыйcount(*)
вместоcount(DISTINCT i.id)
. (id
существоNOT NULL PRIMARY KEY
.)SQL Fiddle.
Но если я хочу отфильтровать
items.created
?Обращаясь к вашему комментарию.
Это зависит. Можем ли мы применить тот же фильтр к
payments.created
иextras.created
?Если да, то просто добавьте фильтры в подзапросах. (В данном случае не похоже.)
Если нет, но мы по-прежнему выбираем большинство элементов , приведенный выше запрос будет наиболее эффективным. Некоторые объединения в подзапросах исключаются в объединениях, но это все же дешевле, чем более сложные запросы.
Если нет, и мы выбираем небольшую часть элементов, я предлагаю соотнесенные подзапросы или
LATERAL
объединения. Примеры:источник
items.created
какой самый эффективный способ сделать это? Должен ли я добавить дополнительныйJOIN
наitems
для подзапросов (p
иe
в вашем примере) , чтобы выполнить такую фильтрацию , как @ ypercubeᵀᴹ упоминается?LATERAL JOIN
работает для меня! Спасибо за чистое объяснение :)