Come implementare correttamente il massimo del filtraggio del composto
-
29-09-2020 - |
Domanda
Sì, più domande più grandi-n-per gruppo.
Data la tabella releases
con le seguenti colonne:
id | primary key |
volume | double precision |
chapter | double precision |
series | integer-foreign-key |
include | boolean | not null
.
Voglio selezionare il composto massimo del volume, quindi capitolo per un set di serie.
In questo momento, se query per la serie-distinzione-serie, posso facilmente realizzarlo come segue:
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;
.
Tuttavia, se ho un grande set di generatori series
(e lo faccio), questo corre rapidamente in problemi di efficienza in cui emetti 100 query per generare una singola pagina.
Io come per rotolare il tutto in una singola domanda, dove posso semplicemente dire WHERE releases.series IN (1,2,3....)
, ma non ho capito come convincere i postgres per farmi farlo. .
L'approccio ingenuo sarebbe:
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;
.
che ovviamente non funziona:
..ERROR: column "releases.volume" must appear in the GROUP BY clause or be used in an aggregate function
Senza il GROUP BY
, prende tutto, e con un semplice filtraggio procedurale funzionerà anche, ma ci deve essere un modo "corretto" per farlo in SQL.
A seguito degli errori e aggiungendo aggregati:
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;
.
per lo più funziona, ma il problema è che i due massimi non sono coerenti. Se ho due righe, uno in cui il volume: il capitolo è 1: 5 e 4: 1, ho bisogno di restituire 4: 1, ma i massimi indipendenti ritornano 4: 5.
Francamente, questo sarebbe così semplice da implementare nel mio codice applicativo che devo mancare qualcosa di ovvio qui. Come posso implementare una query che soddisfa effettivamente le mie esigenze?
Soluzione
La soluzione semplice in 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;
.
Dettagli:
A seconda della distribuzione dei dati ci possono essere tecniche più veloci:
Inoltre, ci sono Alternative più veloci per liste lunghe rispetto a IN ()
.
Combinazione di un array non credente con un join LATERAL
:
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;
.
è spesso più veloce. Per prestazioni migliori è necessario un indice di corrispondenza Multicolunn come:
CREATE INDEX releases_series_volume_chapter_idx
ON releases(series, volume DESC NULLS LAST, chapter DESC NULLS LAST);
.
Correlato:
E se ci sono più di pochi le righe in cui include
non è true
, mentre sei interessato solo alle righe con include = true
, prendono in considerazione un Parziale Indice multicolumne :
CREATE INDEX releases_series_volume_chapter_idx
ON releases(series, volume DESC NULLS LAST, chapter DESC NULLS LAST)
WHERE include;
.