SQL nu este C++ și nu JavaScript. Prin urmare, calculul expresiilor logice are loc diferit și nu este deloc același lucru:
WHERE fncondX() AND fncondY()
= fncondX() && fncondY()
În procesul de optimizare a planului de execuție a interogărilor PostgreSQL
Prin urmare, dacă tot doriți să gestionați prioritatea, trebuie să o structurați face aceste condiții inegale folosind condiționale
Datele și lucrul cu ele sunt baza
#0: RTFM
Pornire
Când ordinea evaluării este importantă, aceasta poate fi surprinsă folosind constructul
CASE
. De exemplu, aceasta este o modalitate de a evita împărțirea la zero într-o propozițieWHERE
nesigur:SELECT ... WHERE x > 0 AND y/x > 1.5;
Opțiune sigură:
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;
Designul folosit în acest fel
CASE
protejează expresia de optimizare, deci ar trebui să fie folosită numai atunci când este necesar.
#1: condiție de declanșare
BEGIN
IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
Totul pare să arate bine, dar... Nimeni nu promite că investiția SELECT
nu va fi executată dacă prima condiție este falsă. Să o reparăm cu cuibărit IF
:
BEGIN
IF cond(NEW.fld) THEN
IF EXISTS(SELECT ...) THEN
...
END IF;
END IF;
RETURN NEW;
END;
Acum să ne uităm cu atenție - întregul corp al funcției de declanșare este „înfășurat”. IF
. Aceasta înseamnă că nimic nu ne împiedică să eliminăm această afecțiune din procedura de utilizare WHEN
-conditii
BEGIN
IF EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
...
CREATE TRIGGER ...
WHEN cond(NEW.fld);
Această abordare este garantată pentru a economisi resursele serverului atunci când condiția este falsă.
#2: lanț SAU/ȘI
SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)
În caz contrar, poți ajunge cu ambele EXISTS
va fi „adevărat”, dar ambele vor fi îndeplinite.
Dar dacă știm cu siguranță că unul dintre ele este „adevărat” mult mai des (sau „fals” - pentru AND
-lanțuri) - este posibil să-i „măriți prioritatea” cumva pentru ca al doilea să nu fie executat din nou?
Se pare că este posibil - abordarea algoritmică este aproape de subiectul articolului
Să „împingem” ambele condiții în CASE:
SELECT ...
WHERE
CASE
WHEN EXISTS(... A) THEN TRUE
WHEN EXISTS(... B) THEN TRUE
END
În acest caz nu am definit ELSE
-valoare, adică dacă ambele condiții sunt false CASE
va reveni NULL
, care este interpretat ca FALSE
в WHERE
-conditii.
Acest exemplu poate fi combinat în alte moduri - în funcție de gust și culoare:
SELECT ...
WHERE
CASE
WHEN NOT EXISTS(... A) THEN EXISTS(... B)
ELSE TRUE
END
#3: cum [nu] să scrieți condiții
Am petrecut două zile analizând motivele funcționării „ciudate” a acestui declanșator - să vedem de ce.
Sursă:
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 ...
Problema #1: inegalitatea nu respectă NULL
Să ne imaginăm că totul OLD
-câmpurile aveau sens NULL
. Ce se va intampla?
SELECT NULL <> 1 OR NULL <> 2;
-- NULL
Și din punctul de vedere al elaborării condițiilor NULL
echivalent FALSE
, așa cum sa menționat mai sus.
decizie: utilizați operator IS DISTINCT FROM
ROW
-operator, comparând înregistrări întregi simultan:
SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
-- TRUE
Problema #2: implementări diferite ale aceleiași funcționalități
Să comparăm:
NEW."Документ_" = (select '"Комплект"'::regclass::oid)
NEW."Документ_" = (select to_regclass('"ДокументПоЗарплате"')::oid)
De ce există investiții suplimentare aici? SELECT
? O functie to_regclass
? De ce este diferit?...
Să reparăm:
NEW."Документ_" = '"Комплект"'::regclass::oid
NEW."Документ_" = '"ДокументПоЗарплате"'::regclass::oid
Problema #3: prioritatea operațiunilor bool
Să formatăm sursa:
{... IS NULL} OR
{... Комплект} OR
{... ДокументПоЗарплате} AND
( {... неравенства} )
Hopa... De fapt, s-a dovedit că, dacă oricare dintre primele două condiții este adevărată, întreaga condiție se transformă în TRUE
, fără a ține cont de inegalități. Și nu este deloc ceea ce ne-am dorit.
Să reparăm:
(
{... IS NULL} OR
{... Комплект} OR
{... ДокументПоЗарплате}
) AND
( {... неравенства} )
Problema #4 (mică): condiție SAU complexă pentru un câmp
De fapt, am avut probleme la nr. 3 tocmai pentru că erau trei condiții. Dar în loc de ele te poți descurca cu unul, folosind mecanismul coalesce ... IN
:
coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"')
Deci noi NULL
„vom prinde”, și dificil OR
Nu este nevoie să îngrădiți cu paranteze.
În total
Să înregistrăm ce avem:
IF (
coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"') AND
(
OLD."ДокументНашаОрганизация"
, OLD."Удален"
, OLD."Дата"
, OLD."Время"
, OLD."ЛицоСоздал"
) IS DISTINCT FROM (
NEW."ДокументНашаОрганизация"
, NEW."Удален"
, NEW."Дата"
, NEW."Время"
, NEW."ЛицоСоздал"
)
) THEN ...
Și dacă considerați că această funcție de declanșare poate fi folosită doar în UPDATE
-declanșare din cauza disponibilității OLD/NEW
în starea de nivel superior, atunci această condiție poate fi plasată în general WHEN
- stare, așa cum se arată în #1...
Sursa: www.habr.com