【问题标题】:PostgreSQL parametrized queryPostgreSQL 参数化查询
【发布时间】:2016-05-19 10:28:12
【问题描述】:

我经常需要提交几乎相同的查询,但参数不同(单个参数,但在SELECT … 子句中多次(不是WHERE(!)))。

为了说明,它在duc.opt_level >= 5 /*v_min_level*/ 出现在下面的每个地方:

SELECT duc.id_du, duc.du_type_cd, duc.du_name, duc.du_addr, duc.id_du_def, duc.def_repeat_flg, defc.allows_txt_flg
  ,r.lvl + CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= 5 /*v_min_level*/ AND duc.max_occurs=1 THEN 0 ELSE 1 END ELSE 0 END lvl
  ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= 5 /*v_min_level*/ AND duc.max_occurs=1 THEN r.id_du ELSE duc.id_du END ELSE r.id_du END entid
  ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= 5 /*v_min_level*/ AND duc.max_occurs=1 THEN r.entraw ELSE duc.du_addr END ELSE r.entraw END entraw
  ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= 5 /*v_min_level*/ AND duc.max_occurs=1 THEN r.entnm ELSE duc.du_name END ELSE r.entnm END entnm
  ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= 5 /*v_min_level*/ AND duc.max_occurs=1 THEN r.pentid ELSE r.entid END ELSE r.pentid END pentid
  ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= 5 /*v_min_level*/ AND duc.max_occurs=1 THEN r.pentraw ELSE r.entraw END ELSE r.pentraw END pentraw
  ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= 5 /*v_min_level*/ AND duc.max_occurs=1 THEN r.pentnm ELSE r.entnm END ELSE r.pentnm END pentnm
  ,defc.def_type, defc.val_type
  ,defc.is_nillable
  ,defc.optional
FROM r
JOIN data_unit duc
  ON duc.id_parent_du = r.id_du
JOIN du_def defc
  ON defc.id_du_def = duc.id_du_def

Postgres 版本。 9.5.1

有什么优雅的方法可以参数化这个查询,这样我就可以只通过重新定义这个参数来调用它?

我想到了类似于 匿名 PL/pgSQL 块的东西,但我不知道具体是怎么回事

【问题讨论】:

  • 似乎可以避免嵌套 CASE。
  • 例如存储函数或PREPARE/EXECUTE statements
  • @joop 怎么样?你的意思是把CASE … WHEN改写成CASE WHEN …
  • @Abelisto with PREPARE/EXECUTE 恐怕它需要我放很多 ? 然后用多个但相同的参数调用:execute pblock(5,5,5,5,5,5,…)
  • 不,只是在查询中多次使用$1,fe:select $1, $1 || $1 ...

标签: sql postgresql parameters plpgsql reusability


【解决方案1】:

您可以交叉连接到“值”表:

SELECT duc.id_du, duc.du_type_cd, duc.du_name, duc.du_addr, duc.id_du_def, duc.def_repeat_flg, defc.allows_txt_flg
      ,r.lvl +  
         CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= p.min_level AND duc.max_occurs=1 THEN 0 ELSE 1 END ELSE 0 END lvl
      ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= p.min_level AND duc.max_occurs=1 THEN r.id_du ELSE duc.id_du END ELSE r.id_du END entid
      ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= p.min_level AND duc.max_occurs=1 THEN r.entraw ELSE duc.du_addr END ELSE r.entraw END entraw
      ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= p.min_level AND duc.max_occurs=1 THEN r.entnm ELSE duc.du_name END ELSE r.entnm END entnm
      ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= p.min_level AND duc.max_occurs=1 THEN r.pentid ELSE r.entid END ELSE r.pentid END pentid
      ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= p.min_level AND duc.max_occurs=1 THEN r.pentraw ELSE r.entraw END ELSE r.pentraw END pentraw
      ,CASE duc.du_type_cd WHEN 'e' THEN CASE WHEN duc.opt_level >= p.min_level AND duc.max_occurs=1 THEN r.pentnm ELSE r.entnm END ELSE r.pentnm END pentnm
      ,defc.def_type, defc.val_type
      ,defc.is_nillable
      ,defc.optional
FROM ...
  cross join (values (5)) as p(min_level)

【讨论】:

  • 不影响性能吗? (我知道它是单行虚拟内联表,但仍然......)
  • 如果这对性能有任何影响,我会非常感到惊讶。但如果你想确定,检查explain analyze的输出
【解决方案2】:

假设所有列不能为 NULL(问题中缺少信息)。

CASE 表达式解开为:

 , CASE WHEN duc.du_type_cd = 'e' AND (duc.opt_level < 5 OR duc.max_occurs <> 1) THEN r.lvl + 1   ELSE r.lvl     END AS lvl
 , CASE WHEN duc.du_type_cd = 'e' AND (duc.opt_level < 5 OR duc.max_occurs <> 1) THEN duc.id_du   ELSE r.id_du   END AS entid
 , CASE WHEN duc.du_type_cd = 'e' AND (duc.opt_level < 5 OR duc.max_occurs <> 1) THEN duc.du_addr ELSE r.entraw  END AS entraw
  -- etc.

相同的重复条件可以集中在单个LEFT JOINFROM 列表中r (r1) 的另一个实例中。如果不满足条件,则默认为 COALESCE 的替代值:

SELECT duc.id_du, duc.du_type_cd, duc.du_name, duc.du_addr, duc.id_du_def, duc.def_repeat_flg
     , defc.allows_txt_flg, defc.def_type, defc.val_type, defc.is_nillable, defc.optional
     , COALESCE(r1.lvl    , r.lvl + 1)   AS lvl
     , COALESCE(r1.id_du  , duc.id_du)   AS entid
     , COALESCE(r1.entraw , duc.du_addr) AS entraw
     , COALESCE(r1.entnm  , duc.du_name) AS entnm
     , COALESCE(r1.pentid , r.entid)     AS pentid
     , COALESCE(r1.pentraw, r.entraw)    AS pentraw
     , COALESCE(r1.pentnm , r.entnm)     AS pentnm
FROM   duc
JOIN   defc ON ???  -- missing information
JOIN   r    ON ???  -- missing information
LEFT   JOIN r1 ON duc.du_type_cd <> 'e'
               OR (duc.opt_level >= 5 /*v_min_level*/ AND duc.max_occurs = 1);

现在您只需提供一次v_min_level。而且查询要短得多。也可能快一点。

并且永远不要省略关键字AS 作为列别名。 The manual:

省略 AS 关键字
...
FROM 项中,标准和PostgreSQL 都允许省略AS 在作为非保留关键字的别名之前。但这是不切实际的 用于输出列名,因为语法不明确。

【讨论】:

  • 有趣的方法。我想我正在考虑在 PostgreSQL 特定语法/功能的上下文中处理它(如前面提到的PREPARE/EXECUTE),而不是重写特定的查询。我没有看到牺牲可读性和性能(额外的左外连接)来换取单个替换占位符的意义。在这个特定的(分层)查询(更新)中,如果可能的话,我认为这是一种矫枉过正的做法。回覆。 AS 别名 - 同意,这是一种很好的做法,但为了保持一致,所有 SQL 查询都应该始终引用它们的表和列名以保证 100% 安全,不是吗;)?
  • @msciwoj: About quoted identifiers. 至于性能:哪种方法更快还有待观察。连接条件每行计算一次,而CASE 表达式每列计算-取决于你有多少。如果有支持表达式的索引,则连接可能会快得多。只需使用explain analyze 进行测试。当然,您可以使用准备好的语句或带参数的函数。 SO上有很多相关的答案。
猜你喜欢
  • 1970-01-01
  • 2018-04-20
  • 2021-03-26
  • 1970-01-01
  • 2016-05-11
  • 2012-10-16
  • 1970-01-01
  • 1970-01-01
  • 2014-06-13
相关资源
最近更新 更多