cli*_*ick 5 sql postgresql orm dynamic-sql plpgsql
我不得不求助于ORM不足的原始SQL(使用Django 1.7).问题是大多数查询最终都有80-90%的相似性.在不违反可重用性的情况下,我无法找到构建查询的强大而安全的方法.
字符串连接是唯一的出路,即使用if-else条件构建无参数查询字符串,然后使用预准备语句安全地包含参数(以避免SQL注入).我想按照一种简单的方法来为我的项目模板化SQL,而不是重新发明一个迷你ORM.
例如,考虑以下查询:
SELECT id, name, team, rank_score
FROM
( SELECT id, name, team
ROW_NUMBER() OVER (PARTITION BY team
ORDER BY count_score DESC) AS rank_score
FROM
(SELECT id, name, team
COUNT(score) AS count_score
FROM people
INNER JOIN scores on (scores.people_id = people.id)
GROUP BY id, name, team
) AS count_table
) AS rank_table
WHERE rank_score < 3
Run Code Online (Sandbox Code Playgroud)
我怎么能够:
a)添加可选WHERE约束people 或
b)更改INNER JOIN为LEFT OUTER 或
c)更改COUNT为SUM 或
d)完全跳过该OVER / PARTITION条款?
对于初学者,您可以修复语法,简化和澄清一下:
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;
Run Code Online (Sandbox Code Playgroud)
基于我更新的表格布局.见下面的小提琴.
您不需要其他子查询.窗口函数在聚合函数之后执行,因此您可以像演示一样嵌套它.
在谈论"排名"时,你可能想要使用rank(),而不是row_number().
假设people.people_id是PK,你可以简化GROUP BY.
确保对所有可能不明确的列名进行表限定
然后我会写一个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;
Run Code Online (Sandbox Code Playgroud)
呼叫:
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
Run Code Online (Sandbox Code Playgroud)
您需要对PL/pgSQL有一个坚定的理解.此外,还有太多不足以解释的问题.您可以在plpgsql下找到相关答案,几乎答案中的每个细节.
安全地处理所有参数,不能进行SQL注入.更多:
特别注意,如何使用查询sting中的位置参数WHERE有条件地添加子句(何时_where_name传递)$1.值传递到EXECUTE 作为价值与USING条款.没有类型转换,没有转义,没有SQL注入的机会.例子:
使用DEFAULT函数参数的值,因此您可以自由提供任何或不提供.更多:
该函数format()有助于以安全和干净的方式构建复杂的动态SQL字符串.
| 归档时间: |
|
| 查看次数: |
1162 次 |
| 最近记录: |