SQL n'est pas C++, ni JavaScript. Par conséquent, l'évaluation des expressions logiques est différente, et ce n'est pas du tout la même chose :
WHERE fncondX() AND fncondY()
= fncondX() && fncondY()
En optimisant le plan d'exécution d'une requête PostgreSQL
Par conséquent, si vous souhaitez toujours gérer la priorité, vous devez structurellement rendre ces conditions inégales au conditionnel
Les données et travailler avec elles sont la base
#0 : RTFM
Départ
Lorsque l'ordre d'évaluation est important, il peut être fixé avec le construit
CASE
. Par exemple, cette façon d'éviter la division par zéro dans une phraseWHERE
non fiable:SELECT ... WHERE x > 0 AND y/x > 1.5;
Options sûres :
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;
La construction utilisée
CASE
protège l'expression de l'optimisation, elle ne doit donc être utilisée que lorsque cela est nécessaire.
#1 : condition de déclenchement
BEGIN
IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
Tout semble bien paraître, mais... Personne ne promet que les investis SELECT
ne sera pas exécuté si la première condition est fausse. Fixez-le avec imbriqué IF
:
BEGIN
IF cond(NEW.fld) THEN
IF EXISTS(SELECT ...) THEN
...
END IF;
END IF;
RETURN NEW;
END;
Maintenant, regardons attentivement - tout le corps de la fonction de déclenchement s'est avéré être "enveloppé" dans IF
. Et cela signifie que rien ne nous empêche de supprimer cette condition de la procédure en utilisant WHEN
-conditions
BEGIN
IF EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
...
CREATE TRIGGER ...
WHEN cond(NEW.fld);
Cette approche vous permet d'économiser les ressources du serveur avec une garantie si la condition est fausse.
#2 : Chaîne OU/ET
SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)
Sinon, on peut obtenir que les deux EXISTS
sera vrai, mais les deux seront exécutés.
Mais si nous savons avec certitude que l'un d'entre eux est "vrai" beaucoup plus souvent (ou "faux" - par AND
-chaînes) - est-il possible d'une manière ou d'une autre "d'augmenter sa priorité" afin que la seconde ne soit pas exécutée à nouveau ?
Il s'avère que c'est possible - l'approche algorithmique est proche du sujet de l'article
Disons simplement "poussons sous CASE" ces deux conditions :
SELECT ...
WHERE
CASE
WHEN EXISTS(... A) THEN TRUE
WHEN EXISTS(... B) THEN TRUE
END
Dans ce cas, nous n'avons pas défini ELSE
-value, c'est-à-dire si les deux conditions sont fausses CASE
reviendra NULL
, qui est interprété comme FALSE
в WHERE
- conditions.
Cet exemple peut être combiné d'une autre manière - au goût et à la couleur :
SELECT ...
WHERE
CASE
WHEN NOT EXISTS(... A) THEN EXISTS(... B)
ELSE TRUE
END
#3 : comment [ne pas] écrire les conditions
Nous avons passé deux jours à analyser les raisons du déclenchement "étrange" de ce déclencheur - voyons pourquoi.
Source:
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 ...
Problème n° 1 : l'inégalité ne tient pas compte de NULL
Supposons que tout OLD
-les champs comptaient NULL
. Que va-t-il se passer ?
SELECT NULL <> 1 OR NULL <> 2;
-- NULL
Et du point de vue de l'élaboration des conditions NULL
équivalent FALSE
, comme mentionné ci-dessus.
décision: utiliser l'opérateur IS DISTINCT FROM
ROW
-opérateur, comparant des enregistrements entiers à la fois :
SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
-- TRUE
Problème numéro 2 : implémentation différente de la même fonctionnalité
Comparons:
NEW."Документ_" = (select '"Комплект"'::regclass::oid)
NEW."Документ_" = (select to_regclass('"ДокументПоЗарплате"')::oid)
Pourquoi y a-t-il des investissements supplémentaires SELECT
? Une fonction to_regclass
? Pourquoi est-ce différent...
Réparons :
NEW."Документ_" = '"Комплект"'::regclass::oid
NEW."Документ_" = '"ДокументПоЗарплате"'::regclass::oid
Problème n ° 3: bool priorité
Formatons la source :
{... IS NULL} OR
{... Комплект} OR
{... ДокументПоЗарплате} AND
( {... неравенства} )
Oups ... En fait, il s'est avéré que dans le cas de la vérité de l'une des deux premières conditions, la condition entière se transforme en TRUE
, au mépris des inégalités. Et ce n'est pas du tout ce que nous voulions.
Réparons :
(
{... IS NULL} OR
{... Комплект} OR
{... ДокументПоЗарплате}
) AND
( {... неравенства} )
Problème #4 (petit) : condition OR complexe pour un champ
En fait, nous avons eu des problèmes dans le numéro 3 précisément parce qu'il y avait trois conditions. Mais au lieu d'eux, vous pouvez vous en tirer avec un, en utilisant le mécanisme coalesce ... IN
:
coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"')
Alors sommes-nous NULL
"attrape", et complexe OR
Vous n'avez pas à vous soucier des parenthèses.
En tout
Réparons ce que nous avons :
IF (
coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"') AND
(
OLD."ДокументНашаОрганизация"
, OLD."Удален"
, OLD."Дата"
, OLD."Время"
, OLD."ЛицоСоздал"
) IS DISTINCT FROM (
NEW."ДокументНашаОрганизация"
, NEW."Удален"
, NEW."Дата"
, NEW."Время"
, NEW."ЛицоСоздал"
)
) THEN ...
Et étant donné que cette fonction de déclenchement ne peut être utilisée que dans UPDATE
déclenchement dû à la présence OLD/NEW
dans la condition de niveau supérieur, alors cette condition peut généralement être retirée dans WHEN
-état comme indiqué dans #1...
Source: habr.com