更好的查询
对于初学者,你可以修正语法,简化和澄清很多:
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
SQL Fiddle
您需要对 PL/pgSQL 有深入的了解。否则,要解释的东西太多了。您可以在plpgsql 下的 SO 上找到相关答案,了解答案中的几乎所有细节。
-
所有参数都被安全处理,没有 SQL 注入可能。更多:
-
特别注意,WHERE 子句是如何在查询字符串中使用位置参数$1 有条件地添加的(当传递_where_name 时)。该值通过USING clause作为值传递给EXECUTE。没有类型转换,没有转义,没有 SQL 注入的机会。例子:
-
对函数参数使用DEFAULT 值,因此您可以随意提供任何参数或不提供任何参数。更多:
函数 format() 有助于以安全和干净的方式构建复杂的动态 SQL 字符串。