Betere zoekopdracht
Om te beginnen kun je de syntaxis aanpassen, een beetje vereenvoudigen en verduidelijken:
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;
-
Voortbouwend op mijn bijgewerkte tafelindeling. Zie viool hieronder.
-
U hebt de aanvullende subquery niet nodig. Vensterfuncties worden na . uitgevoerd geaggregeerde functies, zodat u het kunt nesten zoals aangetoond.
-
Als je het over "rang" hebt, wil je waarschijnlijk
rank()
. gebruiken , nietrow_number()
. -
Ervan uitgaande dat
people.people_id
is de PK, je kuntGROUP BY
vereenvoudigen . -
Zorg ervoor dat u alle kolomnamen die dubbelzinnig kunnen zijn, in een tabel kwalificeert
PL/pgSQL-functie
Dan zou ik een plpgsql-functie schrijven die parameters nodig heeft voor je variabele delen. a
implementeren - c
van uw punten. d
is onduidelijk, laat dat aan jou over om toe te voegen.
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;
Bel:
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
-
Je hebt een goed begrip van PL/pgSQL nodig. Anders is er gewoon te veel om uit te leggen. U vindt gerelateerde antwoorden hier op SO onder plpgsql voor praktisch elk detail in het antwoord.
-
Alle parameters worden veilig behandeld, geen SQL-injectie mogelijk. Meer:
-
Merk in het bijzonder op hoe een
WHERE
clausule wordt voorwaardelijk toegevoegd (wanneer_where_name
wordt doorgegeven) met de positionele parameter$1
in de query-sting. De waarde wordt doorgegeven aanEXECUTE
als waarde met deUSING
clausule . Geen typeconversie, geen ontsnapping, geen kans op SQL-injectie. Voorbeelden: -
Gebruik
DEFAULT
waarden voor functieparameters, dus u bent vrij om er een of geen op te geven. Meer: -
De functie
format()
is essentieel voor het bouwen van complexe dynamische SQL-strings op een veilige en schone manier.