Cómo implementar correctamente el compuesto de mayor-n de filtrado
-
29-09-2020 - |
Pregunta
Sí, la más grande de n por grupo de preguntas.
Dada la tabla releases
con las siguientes columnas:
id | primary key |
volume | double precision |
chapter | double precision |
series | integer-foreign-key |
include | boolean | not null
Quiero seleccionar el compuesto max de volumen, a continuación, el capítulo de un conjunto de series.
Ahora mismo, si me consulta por distintos-serie, me puede lograr esto fácilmente de la siguiente manera:
SELECT
releases.chapter AS releases_chapter,
releases.include AS releases_include,
releases.series AS releases_series
FROM releases
WHERE releases.series = 741
AND releases.include = TRUE
ORDER BY releases.volume DESC NULLS LAST, releases.chapter DESC NULLS LAST LIMIT 1;
Sin embargo, si tengo un gran conjunto de series
(y lo hago), este rápidamente se ejecuta en temas de eficiencia donde estoy emitiendo 100+ consultas para generar una sola página.
Me gustaría como para rodar la cosa entera en una sola consulta, donde puedo simplemente decir WHERE releases.series IN (1,2,3....)
, pero no he averiguado cómo convencer a Postgres para que me deje hacer eso.
El enfoque ingenuo sería:
SELECT releases.volume AS releases_volume,
releases.chapter AS releases_chapter,
releases.series AS releases_series
FROM
releases
WHERE
releases.series IN (12, 17, 44, 79, 88, 110, 129, 133, 142, 160, 193, 231, 235, 295, 340, 484, 499,
556, 581, 664, 666, 701, 741, 780, 790, 796, 874, 930, 1066, 1091, 1135, 1137,
1172, 1331, 1374, 1418, 1435, 1447, 1471, 1505, 1521, 1540, 1616, 1702, 1768,
1825, 1828, 1847, 1881, 2007, 2020, 2051, 2085, 2158, 2183, 2190, 2235, 2255,
2264, 2275, 2325, 2333, 2334, 2337, 2341, 2343, 2348, 2370, 2372, 2376, 2606,
2634, 2636, 2695, 2696 )
AND releases.include = TRUE
GROUP BY
releases_series
ORDER BY releases.volume DESC NULLS LAST, releases.chapter DESC NULLS LAST;
Que, obviamente, no funciona:
ERROR: column "releases.volume" must appear in the GROUP BY clause or be used in an aggregate function
Sin el GROUP BY
, no recuperar todo, y con unas simples procedimiento de filtrado incluso podría trabajar, pero no debe ser un "buen" manera de hacer esto en SQL.
Tras los errores, y la adición de agregados:
SELECT max(releases.volume) AS releases_volume,
max(releases.chapter) AS releases_chapter,
releases.series AS releases_series
FROM
releases
WHERE
releases.series IN (12, 17, 44, 79, 88, 110, 129, 133, 142, 160, 193, 231, 235, 295, 340, 484, 499,
556, 581, 664, 666, 701, 741, 780, 790, 796, 874, 930, 1066, 1091, 1135, 1137,
1172, 1331, 1374, 1418, 1435, 1447, 1471, 1505, 1521, 1540, 1616, 1702, 1768,
1825, 1828, 1847, 1881, 2007, 2020, 2051, 2085, 2158, 2183, 2190, 2235, 2255,
2264, 2275, 2325, 2333, 2334, 2337, 2341, 2343, 2348, 2370, 2372, 2376, 2606,
2634, 2636, 2695, 2696 )
AND releases.include = TRUE
GROUP BY
releases_series;
En su mayoría funciona, pero el problema es que los dos máximos no son coherentes.Si tengo dos filas, una en volumen:capítulo 1:5 y 4:1, necesito volver 4:1, pero el independiente máximos de retorno 4:5.
Francamente, esto sería tan simple de implementar en mi código de la aplicación que me falta algo obvio aquí.¿Cómo puedo implementar una consulta que realmente satisfaga mis necesidades?
Solución
La solución más simple en Postgres con DISTINCT ON
:
SELECT DISTINCT ON (r.series)
r.volume AS releases_volume
, r.chapter AS releases_chapter
, r.series AS releases_series
FROM releases r
WHERE r.series IN (
12, 17, 44, 79, 88, 110, 129, 133, 142, 160, 193, 231, 235, 295, 340, 484, 499
, 556, 581, 664, 666, 701, 741, 780, 790, 796, 874, 930, 1066, 1091, 1135, 1137
, 1172, 1331, 1374, 1418, 1435, 1447, 1471, 1505, 1521, 1540, 1616, 1702, 1768
, 1825, 1828, 1847, 1881, 2007, 2020, 2051, 2085, 2158, 2183, 2190, 2235, 2255
, 2264, 2275, 2325, 2333, 2334, 2337, 2341, 2343, 2348, 2370, 2372, 2376, 2606
, 2634, 2636, 2695, 2696)
AND r.include
ORDER BY r.series, r.volume DESC NULLS LAST, r.chapter DESC NULLS LAST;
Detalles:
Dependiendo de la distribución de los datos no puede ser más rápido técnicas:
También, hay más rápido alternativas para largas listas de IN ()
.
La combinación de una unnested matriz con un LATERAL
unirse:
SELECT r.*
FROM unnest('{12, 17, 44, 79, 88, 110, 129}'::int[]) t(i) -- or many more items
, LATERAL (
SELECT volume AS releases_volume
, chapter AS releases_chapter
, series AS releases_series
FROM releases
WHERE series = t.i
AND include
ORDER BY series, volume DESC NULLS LAST, chapter DESC NULLS LAST
LIMIT 1
) r;
Es a menudo más rápido.Para un mejor rendimiento que necesita una coincidencia índice de varias columnas como:
CREATE INDEX releases_series_volume_chapter_idx
ON releases(series, volume DESC NULLS LAST, chapter DESC NULLS LAST);
Relacionado con:
Y si hay más de un par de las filas en las que include
no es true
, mientras que usted está interesado sólo en las filas include = true
, a continuación, considere la posibilidad de un parcial de varias columnas de índice:
CREATE INDEX releases_series_volume_chapter_idx
ON releases(series, volume DESC NULLS LAST, chapter DESC NULLS LAST)
WHERE include;