Melhor consulta
Para começar, você pode corrigir a sintaxe, simplificar e esclarecer um pouco:
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;
-
Com base no meu layout de tabela atualizado. Veja violino abaixo.
-
Você não precisa da subconsulta adicional. As funções da janela são executadas depois funções agregadas, para que você possa aninhar como demonstrado.
-
Ao falar sobre "rank", você provavelmente quer usarrank()
, nãorow_number()
.
-
Assumindopeople.people_id
é o PK, você pode simplificarGROUP BY
.
-
Certifique-se de qualificar para tabela todos os nomes de coluna que possam ser ambíguos
função PL/pgSQL
Então eu escreveria uma função plpgsql que recebe parâmetros para suas partes variáveis.Implementando
a
- c
dos seus pontos. d
não está claro, deixando isso para você adicionar. 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;
Ligar:
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
SQL Fiddle
-
Você precisa de um entendimento firme de PL/pgSQL. Caso contrário, há muito para explicar. Você encontrará respostas relacionadas aqui no SO em plpgsql para praticamente todos os detalhes da resposta.
-
Todos os parâmetros são tratados com segurança, sem possibilidade de injeção de SQL. Mais:
-
Observe em particular, como umWHERE
cláusula é adicionada condicionalmente (quando_where_name
é passado) com o parâmetro posicional$1
na picada de consulta. O valor é passado paraEXECUTE
como valor com oUSING
cláusula . Sem conversão de tipo, sem escape, sem chance de injeção de SQL. Exemplos:
-
UseDEFAULT
valores para parâmetros de função, então você está livre para fornecer qualquer um ou nenhum. Mais:
-
A funçãoformat()
é fundamental para a construção de cadeias SQL dinâmicas complexas de forma segura e limpa.