Que depende mucho de las circunstancias y requisitos exactos. Considera mi comentario a la pregunta .
Solución simple
Con DISTINCT ON
en Postgres:
SELECT DISTINCT ON (i.good, i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good, i.the_date, p.the_date DESC;
Resultado ordenado.
O con NOT EXISTS
SQL estándar (funciona con todos los RDBMS que conozco):
SELECT i.the_date, p.the_date AS pricing_date, i.good, i.quantity, p.price
FROM inventory i
LEFT JOIN price p ON p.good = i.good AND p.the_date <= i.the_date
WHERE NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good = p.good
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
Mismo resultado, pero con un orden de clasificación arbitrario, a menos que agregue ORDER BY
.
Dependiendo de la distribución de datos, requisitos e índices exactos, cualquiera de estos puede ser más rápido.
En general, DISTINCT ON
es el vencedor y obtienes un resultado ordenado además de él. Pero para ciertos casos, otras técnicas de consulta son (mucho) más rápidas todavía. Vea abajo.
Las soluciones con subconsultas para calcular valores máximos / mínimos son generalmente más lentas. Las variantes con CTE son generalmente más lentas, todavía.
Las vistas simples (como propone otra respuesta) no ayudan en absoluto al rendimiento en Postgres.
SQL Fiddle.
Solución adecuada
Cuerdas y colación
En primer lugar, sufres de un diseño de tabla subóptimo. Puede parecer trivial, pero normalizar su esquema puede ser muy útil.
La clasificación por tipos de caracteres ( text
, varchar
, ...) tiene que ser hecho de acuerdo con la configuración regional - el COTEJO en particular. Lo más probable es que su base de datos use un conjunto local de reglas (como, en mi caso:) de_AT.UTF-8
. Descubre con:
SHOW lc_collate;
Esto hace que la clasificación y las búsquedas de índice sean más lentas . Cuanto más largas sean sus cadenas (nombres de productos), peor. Si en realidad no le interesan las reglas de intercalación en su salida (o el orden de clasificación), esto puede ser más rápido si agrega COLLATE "C"
:
SELECT DISTINCT ON (i.good COLLATE "C", i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good COLLATE "C", i.the_date, p.the_date DESC;
Tenga en cuenta cómo agregué la colación en dos lugares.
El doble de rápido en mi prueba con 20k filas cada una y nombres muy básicos ('good123').
Índice
Si se supone que su consulta debe usar un índice, las columnas con datos de caracteres deben usar una intercalación coincidente ( good
en el ejemplo):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
Asegúrese de leer los últimos dos capítulos de esta respuesta relacionada en SO:
Incluso puede tener múltiples índices con diferentes clasificaciones en las mismas columnas, si también necesita productos ordenados de acuerdo con otra clasificación (o la predeterminada) en otras consultas.
Normalizar
Las cadenas redundantes (nombre del bien) también hinchan sus tablas e índices, lo que hace que todo sea aún más lento. Con un diseño de tabla adecuado, puede evitar la mayor parte del problema para empezar. Podría verse así:
CREATE TABLE good (
good_id serial PRIMARY KEY
, good text NOT NULL
);
CREATE TABLE inventory (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, quantity int NOT NULL
, PRIMARY KEY(good_id, the_date)
);
CREATE TABLE price (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, price numeric NOT NULL
, PRIMARY KEY(good_id, the_date));
Las claves principales proporcionan automáticamente (casi) todos los índices que necesitamos.
Según los detalles faltantes, un índice de price
varias columnas con orden descendente en la segunda columna puede mejorar el rendimiento:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
Nuevamente, la clasificación debe coincidir con su consulta (ver arriba).
En Postgres 9.2 o posterior, los "índices de cobertura" para escaneos de solo índice podrían ayudar un poco más, especialmente si sus tablas contienen columnas adicionales, lo que hace que la tabla sea sustancialmente más grande que el índice de cobertura.
Estas consultas resultantes son mucho más rápidas:
NO EXISTE
SELECT i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
AND NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good_id = p.good_id
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
DISTINTO EN
SELECT DISTINCT ON (i.the_date)
i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
ORDER BY i.the_date, p.the_date DESC;
SQL Fiddle.
Soluciones más rápidas
Si eso todavía no es lo suficientemente rápido, puede haber soluciones más rápidas.
CTE recursiva / JOIN LATERAL
/ subconsulta correlacionada
Especialmente para distribuciones de datos con muchos precios por bien :
Vista materializada
Si necesita ejecutar esto con frecuencia y rapidez, le sugiero que cree una vista materializada. Creo que es seguro asumir que los precios e inventarios de fechas pasadas rara vez cambian. Calcule el resultado una vez y almacene una instantánea como vista materializada.
Postgres 9.3+ tiene soporte automatizado para vistas materializadas. Puede implementar fácilmente una versión básica en versiones anteriores.