По-добра заявка
Като за начало можете да поправите синтаксиса, да опростите и изясните доста:
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;
-
Надграждам върху моето актуализирано оформление на таблицата. Вижте цигулката по-долу.
-
Не се нуждаете от допълнителна подзаявка. Функциите на прозореца се изпълняват след агрегатни функции, така че можете да го вложите, както е показано.
-
Докато говорим за "ранг", вероятно искате да използвате
rank()
, а неrow_number()
. -
Приемайки
people.people_id
е PK, можете да опроститеGROUP BY
. -
Уверете се, че сте квалифицирали в таблица всички имена на колони, които може да са двусмислени
PL/pgSQL функция
След това бих написал plpgsql функция, която приема параметри за вашите променливи части. Внедряване на a
- c
от вашите точки. d
е неясно, оставям това за вас да добавите.
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;
Обаждане:
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
-
Имате нужда от твърдо разбиране на PL/pgSQL. Иначе има твърде много за обяснение. Ще намерите свързани отговори тук на SO под plpgsql за почти всеки детайл в отговора.
-
Всички параметри се обработват безопасно, не е възможно SQL инжектиране. Още:
-
Обърнете внимание по-специално как
WHERE
клауза се добавя условно (когато_where_name
се предава) с позиционния параметър$1
в ужилването на заявката. Стойността се предава наEXECUTE
като стойност сUSING
клауза . Няма преобразуване на типа, няма екраниране, няма шанс за SQL инжектиране. Примери: -
Използвайте
DEFAULT
стойности за функционални параметри, така че можете да предоставите всякакви или никакви. Още: -
Функцията
format()
е инструмент за изграждане на сложни динамични SQL низове по безопасен и чист начин.