Meilleure requête
Pour commencer, vous pouvez corriger la syntaxe, simplifier et clarifier un peu :
SELECT *
FROM (
SELECT p.person_id, p.name, p.team, sum(s.score)::int AS score
,rank() OVER (PARTITION BY p.team
ORDER BY sum(s.score) DESC)::int AS rnk
FROM person p
JOIN score s USING (person_id)
GROUP BY 1
) sub
WHERE rnk < 3;
-
S'appuyant sur ma disposition de table mise à jour. Voir violon ci-dessous.
-
Vous n'avez pas besoin de la sous-requête supplémentaire. Les fonctions de fenêtre sont exécutées après fonctions d'agrégation, de sorte que vous pouvez l'imbriquer comme démontré.
-
En parlant de "rang", vous voulez probablement utiliser
rank()
, pasrow_number()
. -
En supposant
people.people_id
est le PK, vous pouvez simplifierGROUP BY
. -
Assurez-vous de qualifier de table tous les noms de colonne qui pourraient être ambigus
Fonction PL/pgSQL
Ensuite, j'écrirais une fonction plpgsql qui prend des paramètres pour vos parties variables.Implémentation de a
- c
de vos points. d
n'est pas clair, vous laissant le soin d'ajouter.
CREATE OR REPLACE FUNCTION f_demo(_agg text DEFAULT 'sum'
, _left_join bool DEFAULT FALSE
, _where_name text DEFAULT NULL)
RETURNS TABLE(person_id int, name text, team text, score int, rnk int) AS
$func$
DECLARE
_agg_op CONSTANT text[] := '{count, sum, avg}'; -- allowed functions
_sql text;
BEGIN
-- assert --
IF _agg ILIKE ANY (_agg_op) THEN
-- all good
ELSE
RAISE EXCEPTION '_agg must be one of %', _agg_op;
END IF;
-- query --
_sql := format('
SELECT *
FROM (
SELECT p.person_id, p.name, p.team, %1$s(s.score)::int AS score
,rank() OVER (PARTITION BY p.team
ORDER BY %1$s(s.score) DESC)::int AS rnk
FROM person p
%2$s score s USING (person_id)
%3$s
GROUP BY 1
) sub
WHERE rnk < 3
ORDER BY team, rnk'
, _agg
, CASE WHEN _left_join THEN 'LEFT JOIN' ELSE 'JOIN' END
, CASE WHEN _where_name <> '' THEN 'WHERE p.name LIKE $1' ELSE '' END
);
-- debug -- quote when tested ok
-- RAISE NOTICE '%', _sql;
-- execute -- unquote when tested ok
RETURN QUERY EXECUTE _sql
USING _where_name; -- $1
END
$func$ LANGUAGE plpgsql;
Appel :
SELECT * FROM f_demo();
SELECT * FROM f_demo('sum', TRUE, '%2');
SELECT * FROM f_demo('avg', FALSE);
SELECT * FROM f_demo(_where_name := '%1_'); -- named param
-
Vous avez besoin d'une solide compréhension de PL/pgSQL. Sinon, il y a trop de choses à expliquer. Vous trouverez des réponses connexes ici sur SO sous plpgsql pour pratiquement tous les détails de la réponse.
-
Tous les paramètres sont traités en toute sécurité, aucune injection SQL possible. Plus :
-
Notez en particulier comment un
WHERE
clause est ajoutée conditionnellement (lorsque_where_name
est passé) avec le paramètre positionnel$1
dans la chaîne de requête. La valeur est passée àEXECUTE
comme valeur avec leUSING
clause . Aucune conversion de type, aucun échappement, aucune chance d'injection SQL. Exemples : -
Utilisez
DEFAULT
valeurs pour les paramètres de fonction, vous êtes donc libre d'en fournir ou d'en fournir aucune. Plus : -
La fonction
format()
est essentiel pour créer des chaînes SQL dynamiques complexes de manière sûre et propre.