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
);
Datos :
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)
;
Entonces tenemos:
- 3 artículos en CZ en 1 en PL
- 370 ganados en CZ y 25 en PL
- 350 costo en CZ y 20 en PL
- 11 extra ganados en CZ y 5 extra ganados en PL
Ahora quiero obtener respuestas a las siguientes preguntas:
- ¿Cuántos artículos tuvimos el mes pasado en cada país?
- ¿Cuál fue el monto total ganado (suma de pagos. Montos) en cada país?
- ¿Cuál fue el costo total (suma de artículos.precio) en cada país?
- ¿Cuál fue el total de ganancias adicionales (suma de cantidades adicionales) en cada país?
Con la siguiente 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;
Los resultados son incorrectos:
group_by | item_count | cost | earned | extra_earned
----------+------------+--------+--------+--------------
CZ | 3 | 450.00 | 370.00 | 16.00
PL | 1 | 40.00 | 50.00 | 5.00
El costo y extra_earned para CZ no son válidos: 450 en lugar de 350 y 16 en lugar de 11. El costo y el ganado para PL también no son válidos: se duplican.
Entiendo que en caso de LEFT OUTER JOIN
que haya 2 filas para el elemento con items.id = 1 (y así sucesivamente para otras coincidencias), pero no sé cómo construir una consulta adecuada.
Preguntas :
- ¿Cómo evitar resultados incorrectos en la agregación de consultas en varias tablas?
- ¿Cuál es la mejor manera de calcular la suma sobre valores distintos (items.id en ese caso)?
Versión PostgreSQL : 9.6.1
postgresql
join
aggregate
Extraño6667
fuente
fuente
OUTER APPLY
y utilizandoLATERAL
combinaciones en su lugar.Seq Scan
pagos, lo que significa que la estadística se volverá a calcular en todos los artículos. No mencioné esto en la pregunta, pero también quiero filtrar los elementos por tiempo de creación, por lo que solo necesitaré un subconjunto específico de los datos agregados. Actualizaré la preguntaWHERE
cláusulas o uniones en las subconsultas. Pero marque la opción 4, también, usandoLATERAL
.payments
yitems
en subconsulta y agregarleWHERE
? Tendré que comparar todas las opciones :)items.created_at
, sí.Respuestas:
Como puede haber múltiples
payments
y múltiplesextras
poritem
, se encuentra con una "unión cruzada de proxy" entre esas dos tablas. Agregue filas poritem_id
antes de unirseitem
y todo debería ser correcto:Considere el ejemplo del "mercado de pescado":
Para ser precisos,
SUM(i.price)
sería incorrecto después de unirse a una sola n-tabla, que multiplica cada precio por el número de filas relacionadas. Hacerlo dos veces solo lo empeora, y también es potencialmente costoso computacionalmente.Ah, y dado que no multiplicamos las filas
items
ahora, podemos usar el más barato encount(*)
lugar decount(DISTINCT i.id)
. (id
serNOT NULL PRIMARY KEY
)SQL Fiddle.
Pero si quiero filtrar por
items.created
?Dirigiendo tu comentario.
Depende. ¿Podemos aplicar el mismo filtro a
payments.created
yextras.created
?En caso afirmativo, simplemente agregue los filtros en las subconsultas también. (No parece probable en este caso).
Si no, pero todavía estamos seleccionando la mayoría de los elementos , la consulta anterior aún sería más eficiente. Algunas de las agregaciones en las subconsultas se eliminan en las uniones, pero eso sigue siendo más barato que las consultas más complejas.
Si no, y estamos seleccionando una pequeña fracción de elementos, sugiero subconsultas o
LATERAL
uniones correlacionadas . Ejemplos:fuente
items.created
¿cuál es la forma más eficiente de hacer esto? Debo añadir el suplementoJOIN
deitems
a subconsultas (p
ye
en su ejemplo) para efectuar dicho filtración como @ ypercubeᵀᴹ mencionado?LATERAL JOIN
¡funciona para mi! Gracias por la explicación limpia :)