Comment implémenter correctement composé, plus-n filtrage
-
29-09-2020 - |
Question
Yep, plus plus-n-p-groupe des questions.
Compte tenu de la une table releases
avec les colonnes suivantes:
id | primary key |
volume | double precision |
chapter | double precision |
series | integer-foreign-key |
include | boolean | not null
Je veux choisir le composé max de volume, puis le chapitre pour un jeu de la série.
Maintenant, si j'ai une requête par-distincte de la série, je peux facilement faire le comme suit:
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;
Cependant, si j'ai un grand ensemble de series
(et je le fais), c'rapidement heurte à des problèmes d'efficacité, là où je suis émission 100+ des requêtes afin de générer une seule page.
J'avais comme rouler le tout en une seule requête, où je peux simplement vous dire WHERE releases.series IN (1,2,3....)
, mais je n'ai pas compris comment convaincre Postgres de me laisser faire.
L'approche naïve serait:
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;
Ce qui évidemment ne fonctionne pas:
ERROR: column "releases.volume" must appear in the GROUP BY clause or be used in an aggregate function
Sans l' GROUP BY
, il ne s'extraire de tout, et avec une simple procédure de filtrage, il serait même travail, mais il doit y avoir une "bonne" façon de le faire en SQL.
Suivant les erreurs, et l'ajout d'agrégats:
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;
La plupart fonctionne, mais le problème est que les deux maximums ne sont pas cohérentes.Si j'ai deux lignes, l'une où le volume:chapitre 1:5 et 4:1, j'ai besoin de retour 4:1, mais les indépendants, les maximums de retour 4:5.
Franchement, ce serait tellement simple à mettre en œuvre dans ma demande de code que j'ai manquer quelque chose d'évident ici.Comment puis-je mettre en œuvre une requête qui fait satisfait mes exigences?
La solution
La solution la plus simple dans Postgres est avec 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;
Détails:
Selon les données sur la distribution, il peut être plus rapide des techniques:
Aussi, il y a plus rapide des solutions de rechange pour les longues listes de IN ()
.
Combinant une désemboîtées tableau avec une LATERAL
joindre:
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;
Est souvent plus rapide.Pour de meilleures performances, vous avez besoin d'un correspondant index multicolonne comme:
CREATE INDEX releases_series_volume_chapter_idx
ON releases(series, volume DESC NULLS LAST, chapter DESC NULLS LAST);
Connexes:
Et si il y a plus que quelques les lignes où include
n'est pas true
, pendant que vous êtes seulement intéressé dans les lignes include = true
, puis envisager un partielle index multicolonne:
CREATE INDEX releases_series_volume_chapter_idx
ON releases(series, volume DESC NULLS LAST, chapter DESC NULLS LAST)
WHERE include;