Richiesta migliore
Per cominciare puoi correggere la sintassi, semplificare e chiarire un bel po':
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;
-
Basandosi sul mio layout del tavolo aggiornato. Vedi violino sotto.
-
Non è necessaria la sottoquery aggiuntiva. Le funzioni della finestra vengono eseguite dopo funzioni di aggregazione, in modo da poterlo annidare come mostrato.
-
Mentre parli di "rank", probabilmente vorrai usare
rank()
, nonrow_number()
. -
Supponendo
people.people_id
è il PK, puoi semplificareGROUP BY
. -
Assicurati di qualificare come tabella tutti i nomi di colonna che potrebbero essere ambigui
Funzione PL/pgSQL
Quindi scriverei una funzione plpgsql che accetta parametri per le tue parti variabili. Implementazione di a
- c
dei tuoi punti d
non è chiaro, lasciandolo a te da aggiungere.
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;
Chiama:
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
-
È necessaria una solida conoscenza di PL/pgSQL. Altrimenti, c'è solo troppo da spiegare. Troverai le risposte correlate qui su SO in plpgsql per praticamente ogni dettaglio nella risposta.
-
Tutti i parametri sono trattati in modo sicuro, nessuna SQL injection possibile. Altro:
-
Nota in particolare come un
WHERE
la clausola viene aggiunta in modo condizionale (quando_where_name
è passato) con il parametro posizionale$1
nella query sting. Il valore viene passato aEXECUTE
come valore conUSING
clausola . Nessuna conversione di tipo, nessun escape, nessuna possibilità di SQL injection. Esempi: -
Usa
DEFAULT
valori per i parametri di funzione, quindi sei libero di fornire qualsiasi o nessuno. Altro: -
La funzione
format()
è fondamentale per costruire stringhe SQL dinamiche complesse in modo sicuro e pulito.