Bedre forespørgsel
Til at begynde med kan du rette syntaksen, forenkle og præcisere en hel del:
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;
-
Bygger videre på mit opdaterede bordlayout. Se violin nedenfor.
-
Du behøver ikke den ekstra underforespørgsel. Vinduesfunktioner udføres efter samlede funktioner, så du kan indlejre det som vist.
-
Mens du taler om "rang", vil du sandsynligvis bruge
rank()
, ikkerow_number()
. -
Forudsat
people.people_id
er PK, kan du forenkleGROUP BY
. -
Sørg for at tabelkvalificere alle kolonnenavne, der kan være tvetydige
PL/pgSQL-funktion
Så ville jeg skrive en plpgsql-funktion, der tager parametre for dine variable dele.Implementering af a
- c
af dine point. d
er uklart, så du kan tilføje det.
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;
Ring til:
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
-
Du har brug for en solid forståelse af PL/pgSQL. Ellers er der bare for meget at forklare. Du finder relaterede svar her på SO under plpgsql for praktisk talt alle detaljer i svaret.
-
Alle parametre behandles sikkert, ingen SQL-injektion mulig. Mere:
-
Bemærk især hvordan en
WHERE
klausul tilføjes betinget (når_where_name
er bestået) med positionsparameteren$1
i forespørgslen stikker. Værdien sendes tilEXECUTE
som værdi med <-koden>BRUGER klausul . Ingen typekonvertering, ingen escape, ingen chance for SQL-injektion. Eksempler: -
Brug
DEFAULT
værdier for funktionsparametre, så du kan frit angive nogen eller ingen. Mere: -
Funktionen
format ()
er medvirkende til at bygge komplekse dynamiske SQL-strenge på en sikker og ren måde.