PostgreSQL 反模式:SQL 中的条件评估

SQL 不是 C++,也不是 JavaScript。 因此,逻辑表达式的求值是不同的,这根本不是一回事:

WHERE fncondX() AND fncondY()

= fncondX() && fncondY()

优化 PostgreSQL 查询的执行计划时 可以任意“重新排列”等效条件,不要对单个记录计算任何一个,参考应用索引的条件...总之,最简单的方法就是假设你 无法管理 它们的顺序(以及它们是否会被计算) 公平 条件。

因此,如果你仍然想管理优先级,你需要在结构上 使这些条件不平等 有条件的 表情 и 运营商.

PostgreSQL 反模式:SQL 中的条件评估
数据和使用数据是基础 我们的 VLSI 综合体,因此对我们来说非常重要的是,对它们的操作不仅正确而且高效。 让我们看一下表达式求值中可能出现错误的具体示例,以及值得提高其效率的地方。

#0:RTFM

开始 文档中的示例:

当评估顺序很重要时,可以使用构造来固定它 CASE。 例如,这种方法可以避免在句子中被零除 WHERE 不可靠:

SELECT ... WHERE x > 0 AND y/x > 1.5;

安全选项:

SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;

使用的构造 CASE 保护表达式免遭优化,因此仅应在必要时使用它。

#1:触发条件

BEGIN
  IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
    ...
  END IF;
  RETURN NEW;
END;

一切看起来都不错,但是……没有人保证所投资的 SELECT 如果第一个条件为 false,则不会执行。 修复它 嵌套的 IF:

BEGIN
  IF cond(NEW.fld) THEN
    IF EXISTS(SELECT ...) THEN
      ...
    END IF;
  END IF;
  RETURN NEW;
END;

现在让我们仔细看看——触发函数的整个主体竟然被“包裹”在 IF。 这意味着没有什么可以阻止我们使用以下方法从程序中删除此条件 WHEN-状况:

BEGIN
  IF EXISTS(SELECT ...) THEN
    ...
  END IF;
  RETURN NEW;
END;
...
CREATE TRIGGER ...
  WHEN cond(NEW.fld);

如果条件为假,此方法可以保证您节省服务器资源。

#2:或/与链

SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)

否则,可得两者 EXISTS 将会是真的,但是 两者都将被执行.

但是,如果我们确定其中一个是“正确”的,则更常见(或“错误”的——对于 AND-chains) - 是否有可能以某种方式“提高其优先级”,以便第二个不会再次执行?

事实证明这是可能的——算法方法接近文章的主题 PostgreSQL 反模式:罕见条目到达 JOIN 的中间.

让我们将这两个条件“推到 CASE 之下”:

SELECT ...
WHERE
  CASE
    WHEN EXISTS(... A) THEN TRUE
    WHEN EXISTS(... B) THEN TRUE
  END

在这种情况下,我们没有定义 ELSE-value,即如果两个条件都为 false CASE 会回来 NULL,这被解释为 FALSE в WHERE- 状况。

这个例子可以用另一种方式组合——味道和颜色:

SELECT ...
WHERE
  CASE
    WHEN NOT EXISTS(... A) THEN EXISTS(... B)
    ELSE TRUE
  END

#3:如何[不]写条件

我们花了两天的时间来分析这个触发器“奇怪”触发的原因——让我们看看为什么。

来源:

IF( NEW."Документ_" is null or NEW."Документ_" = (select '"Комплект"'::regclass::oid) or NEW."Документ_" = (select to_regclass('"ДокументПоЗарплате"')::oid)
     AND (   OLD."ДокументНашаОрганизация" <> NEW."ДокументНашаОрганизация"
          OR OLD."Удален" <> NEW."Удален"
          OR OLD."Дата" <> NEW."Дата"
          OR OLD."Время" <> NEW."Время"
          OR OLD."ЛицоСоздал" <> NEW."ЛицоСоздал" ) ) THEN ...

问题#1:不等式不能解释 NULL

让我们假设一切 OLD-领域很重要 NULL。 会发生什么?

SELECT NULL <> 1 OR NULL <> 2;
-- NULL

并且从制定条件的角度来看 NULL 相等的 FALSE, 正如刚才提到的。

:使用运算符 IS DISTINCT FROMROW- 运算符,一次比较整个记录:

SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
-- TRUE

问题 2:相同功能的不同实现

让我们来比较:

NEW."Документ_" = (select '"Комплект"'::regclass::oid)
NEW."Документ_" = (select to_regclass('"ДокументПоЗарплате"')::oid)

为什么要有额外的投资 SELECT? 一个功能 to_regclass? 为什么不一样...

让我们修复:

NEW."Документ_" = '"Комплект"'::regclass::oid
NEW."Документ_" = '"ДокументПоЗарплате"'::regclass::oid

问题#3:布尔优先级

让我们格式化源:

{... IS NULL} OR
{... Комплект} OR
{... ДокументПоЗарплате} AND
( {... неравенства} )

哎呀......事实上,事实证明,在前两个条件中任何一个为真的情况下,整个条件就变成了 TRUE,不考虑不平等。 这根本不是我们想要的。

让我们修复:

(
  {... IS NULL} OR
  {... Комплект} OR
  {... ДокументПоЗарплате}
) AND
( {... неравенства} )

问题 #4(小):一个字段的复杂 OR 条件

事实上,我们在第三项中遇到问题正是因为存在三个条件。 但你可以使用以下机制来代替它们 coalesce ... IN:

coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"')

我们也是 NULL “抓住”,而且复杂 OR 您不必为括号大惊小怪。

在总

让我们修复我们得到的:

IF (
  coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"') AND
  (
    OLD."ДокументНашаОрганизация"
  , OLD."Удален"
  , OLD."Дата"
  , OLD."Время"
  , OLD."ЛицоСоздал"
  ) IS DISTINCT FROM (
    NEW."ДокументНашаОрганизация"
  , NEW."Удален"
  , NEW."Дата"
  , NEW."Время"
  , NEW."ЛицоСоздал"
  )
) THEN ...

并且鉴于此触发功能只能用于 UPDATE由于存在而触发 OLD/NEW 在上层条件中,那么这个条件一般可以在 WHEN-条件如#1所示...

来源: habr.com

添加评论