Esquema :
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
);
Dados :
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)
;
Então nós temos:
- 3 itens em CZ em 1 em PL
- 370 ganhos em CZ e 25 em PL
- Custo de 350 em CZ e 20 em PL
- 11 ganhos extras em CZ e 5 ganhos extras em PL
Agora, quero obter respostas para as seguintes perguntas:
- Quantos itens tivemos no mês passado em todos os países?
- Qual foi o valor total ganho (soma dos pagamentos. montantes) em cada país?
- Qual foi o custo total (soma dos itens.preço) em cada país?
- Qual foi o total de ganhos extras (soma dos valores extras) em cada país?
Com a seguinte consulta ( 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;
Os resultados estão errados:
group_by | item_count | cost | earned | extra_earned
----------+------------+--------+--------+--------------
CZ | 3 | 450.00 | 370.00 | 16.00
PL | 1 | 40.00 | 50.00 | 5.00
Custo e extra_ aprendido para CZ são inválidos - 450 em vez de 350 e 16 em vez de 11. Custo e ganhos para PL também são inválidos - eles são duplicados.
Entendo que, no caso de LEFT OUTER JOIN
, haverá 2 linhas para o item com items.id = 1 (e assim por diante para outras correspondências), mas não sei como criar uma consulta adequada.
Perguntas :
- Como evitar resultados errados na agregação de consultas em várias tabelas?
- Qual é a melhor maneira de calcular a soma sobre valores distintos (items.id nesse caso)?
Versão do PostgreSQL : 9.6.1
postgresql
join
aggregate
Stranger6667
fonte
fonte
OUTER APPLY
e usandoLATERAL
junções.Seq Scan
pagamentos, o que significa que a estatística será recalculada em todos os itens. Eu não mencionei isso na pergunta, mas quero filtrar itens também na hora da criação, portanto, precisarei apenas de um subconjunto específico dos dados agregados. Atualizo a perguntaWHERE
cláusulas ou junções nas subconsultas. Mas verifique também a opção 4 usandoLATERAL
.payments
eitems
na subconsulta e adicionarWHERE
a ele? Eu vou ter de referência todas as opções :)items.created_at
, sim.Respostas:
Como pode haver vários
payments
e múltiplosextras
poritem
, você executa uma "junção cruzada de proxy" entre essas duas tabelas. Agregue linhas poritem_id
antes de ingressaritem
e tudo deve estar correto:Considere o exemplo "mercado de peixe":
Para ser mais preciso,
SUM(i.price)
seria incorreto depois de ingressar em uma única tabela n, que multiplica cada preço pelo número de linhas relacionadas. Fazer isso duas vezes só piora - e também é potencialmente caro em termos de computação.Ah, e como não multiplicamos linhas
items
agora, podemos usar o mais barato emcount(*)
vez decount(DISTINCT i.id)
. (id
sendoNOT NULL PRIMARY KEY
.)SQL Fiddle.
Mas se eu quiser filtrar por
items.created
?Endereçando seu comentário.
Depende. Podemos aplicar o mesmo filtro a
payments.created
eextras.created
?Se sim, adicione também os filtros nas subconsultas. (Não parece provável neste caso.)
Se não, mas ainda estamos selecionando a maioria dos itens , a consulta acima ainda seria mais eficiente. Algumas agregações nas subconsultas são eliminadas nas junções, mas isso ainda é mais barato que as consultas mais complexas.
Se não, e estamos selecionando uma pequena fração de itens, sugiro subconsultas ou
LATERAL
junções correlatas . Exemplos:fonte
items.created
qual é a maneira mais eficiente de fazer isso? Devo acrescentar maisJOIN
sobreitems
a subconsultas (p
ee
no seu exemplo) para executar tais filtração como @ ypercubeᵀᴹ mencionado?LATERAL JOIN
funciona para mim! Obrigado pela explicação limpo :)