SQL n'est ni C ++, ni JavaScript. Par conséquent, le calcul des expressions logiques est différent, et ce n'est pas la même chose:WHERE fncondX() AND fncondY()
= fncondX() && fncondY()
Dans le processus d'optimisation du plan d'exécution des requêtes, PostgreSQL peut "réorganiser" arbitrairement les conditions équivalentes , n'en calculer aucune pour des enregistrements individuels, l'affecter à la condition de l'index utilisé ... En bref, il est plus facile de supposer que vous ne pouvez pas contrôler dans quel ordre Les conditions des pairs seront calculées (et si elles le sont) .Par conséquent, si vous souhaitez toujours contrôler la priorité, vous devez structurellement rendre ces conditions inégales à l' aide d' expressions et d' opérateurs conditionnels .Les données et leur utilisation sont la base de notre complexe VLSI , il est donc très important pour nous que les opérations sur celles-ci soient effectuées non seulement correctement, mais aussi efficacement. Examinons des exemples spécifiques où des erreurs de calcul d'expression peuvent être commises et où leur efficacité mérite d'être améliorée.# 0: RTFM
À partir exemple de la documentation :Lorsque l'ordre de calcul est important, il peut être corrigé à l'aide de la construction CASE
. Par exemple, une telle façon d'éviter de diviser par zéro dans une phrase n'est WHERE
pas fiable:
SELECT ... WHERE x > 0 AND y/x > 1.5;
Option sûre:
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;
La construction utilisée de cette manière CASE
protège l'expression de l'optimisation, elle ne doit donc être utilisée que si nécessaire.
# 1: condition dans le déclencheur
BEGIN
IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
Il semble que tout a l'air bien, mais ... Personne ne promet que la pièce jointe SELECT
ne sera pas exécutée si la première condition est fausse. Corriger en utilisant imbriquéIF
:BEGIN
IF cond(NEW.fld) THEN
IF EXISTS(SELECT ...) THEN
...
END IF;
END IF;
RETURN NEW;
END;
Examinons maintenant attentivement - tout le corps de la fonction de déclenchement s'est avéré être «enveloppé» IF
. Et cela signifie que rien ne nous empêche de supprimer cette condition de la procédure en utilisant la condition WHEN
-condition :BEGIN
IF EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
...
CREATE TRIGGER ...
WHEN cond(NEW.fld);
Cette approche permet une économie garantie des ressources du serveur dans de fausses conditions.# 2: Chaîne OU / ET
SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)
Sinon, vous pouvez obtenir que les deux EXISTS
seront "vrais", mais les deux seront remplis .Mais si nous savons avec certitude que l'une d'elles est «vraie» beaucoup plus souvent (ou «fausse» pour la AND
chaîne), est-il possible en quelque sorte d '«augmenter sa priorité» pour que la seconde ne soit pas répétée?Il s'avère que c'est possible - l'approche algorithmique est proche du sujet de l'article de PostgreSQL Antipatterns: un enregistrement rare atteindra le milieu du JOIN .Disons simplement «pop under 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éterminé la ELSE
valeur-, c'est-à-dire que si les deux conditions sont fausses , elle CASE
sera renvoyée NULL
, ce qui est interprété comme FALSE
dans la condition WHERE
-c.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éclencheur «étrange» de ce déclencheur - voyons pourquoi.La 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 prend pas en compte NULL
Imaginez que tous les OLD
champs soient importants NULL
. Que va-t-il se passer?SELECT NULL <> 1 OR NULL <> 2;
Et du point de vue de l'élaboration des conditions est NULL
équivalent FALSE
, comme mentionné ci-dessus.Solution : utilisez l'opérateur IS DISTINCT FROM
de l' ROW
opérateur, en comparant des enregistrements entiers à la fois:SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
Problème numéro 2: implémentation différente de la même fonctionnalité
Comparer:NEW."_" = (select '""'::regclass::oid)
NEW."_" = (select to_regclass('""')::oid)
Pourquoi y a-t-il des emboîtements supplémentaires SELECT
? Et la fonction to_regclass
? Et de différentes manières, pourquoi? ..Correction:NEW."_" = '""'::regclass::oid
NEW."_" = '""'::regclass::oid
Problème n ° 3: priorité des opérations booléennes
Formatez 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 devient TRUE
, sans tenir compte des inégalités. Et ce n'est pas du tout ce que nous voulions.Réparer:(
{... IS NULL} OR
{... } OR
{... }
) AND
( {... } )
Problème 4 (petit): condition OU 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 en faire un, en utilisant le mécanisme coalesce ... IN
:coalesce(NEW."_"::text, '') IN ('', '""', '""')
Donc, nous NULL
«attrapons», et complexes OR
avec des crochets n'ont pas à clôturer.Total
Nous réparons ce que nous avons obtenu:IF (
coalesce(NEW."_"::text, '') IN ('', '""', '""') AND
(
OLD.""
, OLD.""
, OLD.""
, OLD.""
, OLD.""
) IS DISTINCT FROM (
NEW.""
, NEW.""
, NEW.""
, NEW.""
, NEW.""
)
) THEN ...
Et si nous prenons en compte que cette fonction de déclenchement ne peut être utilisée que dans le UPDATE
déclencheur en raison de la présence d' OLD/NEW
un niveau supérieur dans la condition, cette condition peut généralement être placée dans la condition WHEN
-comme cela a été montré dans # 1 ...