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所示...

來源: www.habr.com

添加評論