sql - 以编程方式构建 SQL 查询的可靠方法

标签 sql postgresql orm plpgsql dynamic-sql

我不得不求助于 ORM 不足的原始 SQL(使用 Django 1.7)。问题是大多数查询最终有 80-90% 的相似度。我想不出一种健壮和安全的方法来构建查询而不违反可重用性。

字符串连接是唯一的出路吗,即使用 if-else 条件构建无参数查询字符串,然后使用准备好的语句安全地包含参数(以避免 SQL 注入(inject))。我想遵循一种简单的方法为我的项目模板化 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

我怎样才能:

a) 在people
上添加可选的WHERE约束 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;

基于我更新的表格布局。请参阅下面的 fiddle 。

您不需要额外的子查询。窗口函数在聚合函数之后执行,因此您可以像演示那样嵌套它。

在谈论“排名”时,您可能想使用 rank(),而不是 row_number()

假设 people.people_id 是 PK,您可以简化 GROUP BY 子句。

确保对所有可能不明确的列名进行表限定。

PL/pgSQL 函数

我会编写一个 PL/pgSQL 函数,它为您的可变部分获取参数。实现 a - c 您的观点。 d 不清楚,留给你补充。

CREATE TABLE person (
  person_id serial PRIMARY KEY
, name text NOT NULL
, team text
);

CREATE TABLE score (
  score_id serial PRIMARY KEY
, person_id int NOT NULL REFERENCES person
, score int NOT NULL
);

-- dummy values
WITH ins AS (
   INSERT INTO person(name, team)
   SELECT 'Jon Doe ' || p, t
   FROM   generate_series(1,20) p                   -- 20 guys x
        , unnest ('{team1,team2,team3}'::text[]) t  -- 3 teams
   RETURNING person_id
   )
INSERT INTO score(person_id, score)
SELECT i.person_id, (random() * 100)::int
FROM   ins i, generate_series(1,5) g;               -- 5 scores each

功能:

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 numeric, rnk bigint)
  LANGUAGE plpgsql AS
$func$
DECLARE
   _agg_op  CONSTANT text[] := '{count, sum, avg}';  -- allowed agg 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)::numeric AS score
           , rank() OVER (PARTITION BY p.team ORDER BY %1$s(s.score) DESC) 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                                                                 -- %1$s
    , CASE WHEN _left_join THEN 'LEFT JOIN' ELSE 'JOIN' END                -- %2$s
    , CASE WHEN _where_name <> '' THEN 'WHERE p.name LIKE $1' ELSE '' END  -- %3$s
   );

   -- debug   -- inspect query first
   -- RAISE NOTICE '%', _sql;

   -- execute -- unquote when tested ok
   RETURN QUERY EXECUTE _sql
   USING  _where_name;   -- $1
END
$func$;

调用:

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

fiddle
<子>旧sqlfiddle

关于sql - 以编程方式构建 SQL 查询的可靠方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25186353/

相关文章:

mysql - 如果已给出所有成分,则显示食谱名称

c# - NHibernate 在 PostgreSQL 9.0 中不断丢失我的 DateTime 的时区

postgresql - 关键组屋-投诉 "Data line too long.likely due to invalid csv data"

javascript - 使用 Node.js 和 Postgresql 在没有回调的函数中调用数据库

c# - 如何在 NHibernate 中强制加载代理对象?

javascript - Adonis Lucid ORM 不返回数据

SQL 计算日期范围内的连续天数

sql - 如何构建数据……顺序的还是分层的?

java - Hibernate 一对多 HashMap 不更新 child

SQL 存储率随时间变化和 SELECT 率在特定时刻有效