Mejor consulta
Para empezar, puede corregir la sintaxis, simplificar y aclarar un poco:
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;
-
Sobre la base de mi diseño de tabla actualizado. Ver violín a continuación.
-
No necesita la subconsulta adicional. Las funciones de ventana se ejecutan después funciones agregadas, para que pueda anidarlas como se muestra.
-
Mientras habla de "rango", probablemente quiera usar
rank()
, norow_number()
. -
Asumiendo
people.people_id
es el PK, puedes simplificarGROUP BY
. -
Asegúrese de calificar en tabla todos los nombres de columna que puedan ser ambiguos
Función PL/pgSQL
Luego, escribiría una función plpgsql que tome parámetros para sus partes variables. Implementando a
- c
de tus puntos. d
no está claro, dejo eso para que usted lo agregue.
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;
Llamar:
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
-
Necesita un conocimiento firme de PL/pgSQL. De lo contrario, hay demasiado que explicar. Encontrará respuestas relacionadas aquí en SO en plpgsql prácticamente todos los detalles de la respuesta.
-
Todos los parámetros se tratan de forma segura, no es posible la inyección de SQL. Más:
-
Tenga en cuenta en particular, cómo un
WHERE
cláusula se agrega condicionalmente (cuando_where_name
se pasa) con el parámetro posicional$1
en la picadura de consulta. El valor se pasa aEXECUTE
como valor con el código <>USO cláusula . Sin conversión de tipo, sin escape, sin posibilidad de inyección de SQL. Ejemplos: -
Usar
DEFAULT
valores para los parámetros de la función, por lo que puede proporcionar cualquiera o ninguno. Más: -
La función
formato ()
es fundamental para construir cadenas SQL dinámicas complejas de una manera segura y limpia.