SQL ist nicht C++ und nicht JavaScript. Daher erfolgt die Auswertung logischer Ausdrücke anders, und das hier ist ganz und gar nicht dasselbe:
WHERE fncondX() AND fncondY()
= fncondX() && fncondY()
Bei der Optimierung des Ausführungsplans einer Anfrage kann PostgreSQL , einige von ihnen für bestimmte Datensätze nicht auswerten, sie dem verwendeten Index zuordnen… Kurz gesagt, am einfachsten ist es, zu sagen, dass Sie im Voraus nicht steuern können in welcher Reihenfolge die Bedingungen (und ob sie überhaupt) ausgewertet werden gleichberechtigt sind.
Wenn Sie also dennoch die Priorität steuern möchten, müssen Sie strukturell diese Bedingungen ungleich machen durch bedingte und .

Daten und die Arbeit mit ihnen sind die Grundlage deshalb ist es uns sehr wichtig, dass die Operationen damit nicht nur korrekt, sondern auch effizient durchgeführt werden. Lassen Sie uns konkrete Beispiele betrachten, in denen Fehler bei der Auswertung von Ausdrücken auftreten können und wo ihre Effizienz verbessert werden sollte.
#0: RTFM
Startwert :
Wenn die Reihenfolge der Auswertung wichtig ist, kann sie mit der Konstruktion
CASEfixiert werden. Zum Beispiel, um eine Division durch null in der Bedingung zu vermeiden,WHEREist unsicher:SELECT ... WHERE x > 0 AND y/x > 1.5;Sichere Variante:
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;Eine so angewendete Konstruktion
CASEschützt den Ausdruck vor der Optimierung, daher sollte sie nur bei Bedarf verwendet werden.
#1: условие в триггере
BEGIN
IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END; Im Grunde sieht alles gut aus, aber… Niemand garantiert, dass die verschachtelte SELECT nicht ausgeführt wird, wenn die erste Bedingung falsch ist. Korrigieren wir das mit verschachtelten IF:
BEGIN
IF cond(NEW.fld) THEN
IF EXISTS(SELECT ...) THEN
...
END IF;
END IF;
RETURN NEW;
END; Schauen wir nun genauer hin — der gesamte Körper der Triggerfunktion ist in IFeingekapselt. Das bedeutet, dass uns nichts davon abhält, diese Bedingung mit Hilfe von :
BEGIN
IF EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
...
CREATE TRIGGER ...
WHEN cond(NEW.fld);Dieser Ansatz ermöglicht eine garantierte Einsparung von Serverressourcen bei Falschheit der Bedingung.
#2: OR/AND-цепочка
SELECT ... WHERE EXISTS(... A) OR EXISTS(... B) Im ungünstigsten Fall kann es passieren, dass beide EXISTS „wahr“ sind, aber beide werden ausgeführt..
Wenn wir jedoch genau wissen, dass einer von ihnen viel häufiger „wahr“ ist (oder „falsch“ — für AND-Ketten) — kann man nicht irgendwie «seine Priorität erhöhen», damit die zweite nicht unnötig ausgeführt wird?
Es stellt sich heraus, dass es möglich ist — der algorithmische Ansatz ist nah am Thema des Artikels. .
Lass uns einfach beide Bedingungen «unter CASE» stecken:
SELECT ...
WHERE
CASE
WHEN EXISTS(... A) THEN TRUE
WHEN EXISTS(... B) THEN TRUE
END In diesem Fall haben wir nicht definiert ELSE-Wert, also im Falle der Falschheit beider Bedingungen. CASE gibt zurück NULL, was als interpretiert wird FALSCH in WHERE-Bedingung.
Dieses Beispiel kann auch anders kombiniert werden — nach Geschmack:
SELECT ...
WHERE
CASE
WHEN NOT EXISTS(... A) THEN EXISTS(... B)
ELSE TRUE
END#3: как [не] надо писать условия
Wir haben zwei Tage damit verbracht, die Gründe für das "seltsame" Auslösen dieses Triggers zu analysieren — schauen wir uns an, warum.
Quelltext:
IF( NEW."Dokument_" is null or NEW."Dokument_" = (select '"Komplett"'::regclass::oid) or NEW."Dokument_" = (select to_regclass('"DokumentNachGehaltsabrechnung"')::oid)
AND ( OLD."DokumentUnsereOrganisation" >< NEW."DokumentUnsereOrganisation"
OR OLD."Gelöscht" >< NEW."Gelöscht"
OR OLD."Datum" >< NEW."Datum"
OR OLD."Zeit" >< NEW."Zeit"
OR OLD."ErstelltVon" >< NEW."ErstelltVon" ) ) THEN ...Problem Nr. 1: Ungleichheit berücksichtigt NULL nicht.
Stellen wir uns vor, dass alle OLD-Felder den Wert NULLhatten. Was würde passieren?
SELECT NULL >< 1 OR NULL >< 2;
-- NULL Und aus der Sicht der Bedingungsauswertung NULL ist gleichbedeutend FALSCH, wie oben erwähnt.
Lösung: Verwenden Sie den Operator ab ROW-Operator, indem Sie ganze Datensätze gleichzeitig vergleichen:
SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
-- TRUEProblem Nr. 2: Verschiedene Implementierungen derselben Funktionalität.
Vergleichen wir:
NEW."Dokument_" = (select '"Komplett"'::regclass::oid)
NEW."Dokument_" = (select to_regclass('"DokumentNachGehaltsabrechnung"')::oid) Warum sind hier unnötige verschachtelte SELECT? А функция to_regclass? А по-разному-то почему?..
Lassen Sie uns das beheben:
NEW."Dokument_" = '"Komplett"'::regclass::oid
NEW."Dokument_" = '"DokumentNachGehaltsabrechnung"'::regclass::oidProblem Nr. 3: Priorität boolescher Operationen.
Formatieren wir den Quelltext:
{... IS NULL} OR
{... Komplett} OR
{... DokumentNachGehaltsabrechnung} AND
( {... Ungleichheiten} ) Ups... Tatsächlich ist es so ausgegangen, dass im Falle der Wahrheit einer der beiden ersten Bedingungen, die gesamte Bedingung sich in WAHR, ohne die Ungleichheiten zu berücksichtigen. Und das ist ganz und gar nicht das, was wir wollten.
Lassen Sie uns das beheben:
(
{... IS NULL} OR
{... Komplett} OR
{... DokumentNachGehaltsabrechnung}
) AND
( {... Ungleichheiten} )Problem Nr. 4 (klein): Komplexe OR-Bedingung für ein Feld.
Genau genommen haben wir das Problem in Nr. 3 genau deshalb, weil es drei Bedingungen gab. Aber stattdessen könnte man mit einem auskommen, indem man den Mechanismus coalesce ... IN:
coalesce(NEW."Dokument_"::text, '') IN ('', '"Komplett"', '"DokumentZurLohnabrechnung"') So fangen wir NULL «ein», und komplexe ODER mit Klammern müssen wir uns nicht herumschlagen.
Insgesamt
Lass uns festhalten, was wir erreicht haben:
WENN (
coalesce(NEW."Dokument_"::text, '') IN ('', '"Komplett"', '"DokumentZurLohnabrechnung"') UND
(
OLD."DokumentUnsereOrganisation"
, OLD."Gelöscht"
, OLD."Datum"
, OLD."Zeit"
, OLD."ErstelltVon"
) IST VERSCHIEDEN VON (
NEW."DokumentUnsereOrganisation"
, NEW."Gelöscht"
, NEW."Datum"
, NEW."Zeit"
, NEW."ErstelltVon"
)
) DANN ... Wenn man bedenkt, dass diese Trigger-Funktion nur in UPDATE-Triggern aufgrund von OLD/NEW in der obersten Bedingung angewendet werden kann, dann kann man diese Bedingung ganz in ein WHEN-Bedingung auslagern, wie in #1 gezeigt...
Quelle: habr.com
