SQL není C++ ani JavaScript. Proto je vyhodnocování logických výrazů odlišné a není to vůbec totéž:
WHERE fncondX() AND fncondY()
= fncondX() && fncondY()
Při optimalizaci plánu provádění dotazu PostgreSQL
Proto, pokud stále chcete řídit prioritu, musíte strukturálně učinit tyto podmínky nerovnými s podmíněným
Data a práce s nimi jsou základ
#0: RTFM
Začíná
Když je pořadí hodnocení důležité, lze jej opravit pomocí konstruktu
CASE
. Například tímto způsobem se vyhnete dělení nulou ve větěWHERE
nespolehlivý:SELECT ... WHERE x > 0 AND y/x > 1.5;
Bezpečná varianta:
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;
Použitá konstrukce
CASE
chrání výraz před optimalizací, takže by se měl používat pouze v případě potřeby.
#1: spouštěcí podmínka
BEGIN
IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
Všechno vypadá dobře, ale... Nikdo neslibuje, že investované SELECT
nebude provedena, pokud je první podmínka nepravdivá. Opravte to pomocí vnořený IF
:
BEGIN
IF cond(NEW.fld) THEN
IF EXISTS(SELECT ...) THEN
...
END IF;
END IF;
RETURN NEW;
END;
Nyní se podívejme pozorně – celé tělo funkce spouště se ukázalo jako „zabalené“. IF
. A to znamená, že nám nic nebrání tento stav z procedury odstranit WHEN
-podmínky
BEGIN
IF EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
...
CREATE TRIGGER ...
WHEN cond(NEW.fld);
Tento přístup vám umožňuje ušetřit prostředky serveru se zárukou, pokud je podmínka nepravdivá.
#2: Řetěz OR/AND
SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)
Jinak lze získat, že obojí EXISTS
bude to pravda, ale obojí bude vykonáno.
Ale pokud víme jistě, že jeden z nich je „pravdivý“ mnohem častěji (nebo „nepravdivý“ - pro AND
-řetězce) - je možné nějak "zvýšit jeho prioritu", aby se druhý neprováděl znovu?
Ukazuje se, že je to možné - algoritmický přístup je blízký tématu článku
Pojďme jen "strčit pod CASE" obě tyto podmínky:
SELECT ...
WHERE
CASE
WHEN EXISTS(... A) THEN TRUE
WHEN EXISTS(... B) THEN TRUE
END
V tomto případě jsme nedefinovali ELSE
-hodnota, to znamená, pokud jsou obě podmínky nepravdivé CASE
vrátí se NULL
, který se vykládá jako FALSE
в WHERE
- podmínky.
Tento příklad lze kombinovat jiným způsobem - podle chuti a barvy:
SELECT ...
WHERE
CASE
WHEN NOT EXISTS(... A) THEN EXISTS(... B)
ELSE TRUE
END
#3: jak [ne] psát podmínky
Strávili jsme dva dny analyzováním důvodů „podivného“ spuštění tohoto spouštěče – podívejme se proč.
Zdroj:
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ém č. 1: Nerovnost nebere v úvahu NULL
Předpokládejme, že všechno OLD
- na polích záleželo NULL
. Co se bude dít?
SELECT NULL <> 1 OR NULL <> 2;
-- NULL
A to z pohledu vypracování podmínek NULL
ekvivalent FALSE
, jak je zmíněno výše.
rozhodnutí: použijte operátor IS DISTINCT FROM
ROW
-operátor, porovnávající celé záznamy najednou:
SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
-- TRUE
Problém číslo 2: odlišná implementace stejné funkcionality
Porovnat:
NEW."Документ_" = (select '"Комплект"'::regclass::oid)
NEW."Документ_" = (select to_regclass('"ДокументПоЗарплате"')::oid)
Proč jsou tam další investice SELECT
? Funkce to_regclass
? Proč je to jinak...
Pojďme opravit:
NEW."Документ_" = '"Комплект"'::regclass::oid
NEW."Документ_" = '"ДокументПоЗарплате"'::regclass::oid
Problém č. 3: boolovská priorita
Zformátujeme zdroj:
{... IS NULL} OR
{... Комплект} OR
{... ДокументПоЗарплате} AND
( {... неравенства} )
Jejda... Ve skutečnosti se ukázalo, že v případě pravdivosti kterékoli z prvních dvou podmínek se celá podmínka změní na TRUE
, bez ohledu na nerovnosti. A to vůbec není to, co jsme chtěli.
Pojďme opravit:
(
{... IS NULL} OR
{... Комплект} OR
{... ДокументПоЗарплате}
) AND
( {... неравенства} )
Problém č. 4 (malý): složitá podmínka NEBO pro jedno pole
Ve skutečnosti jsme měli problémy v č. 3 právě proto, že byly tři podmínky. Ale místo nich si vystačíte s jedním pomocí mechanismu coalesce ... IN
:
coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"')
My také NULL
„chytit“ a složitě OR
Nemusíte si lámat hlavu se závorkami.
Celkem
Opravme, co máme:
IF (
coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"') AND
(
OLD."ДокументНашаОрганизация"
, OLD."Удален"
, OLD."Дата"
, OLD."Время"
, OLD."ЛицоСоздал"
) IS DISTINCT FROM (
NEW."ДокументНашаОрганизация"
, NEW."Удален"
, NEW."Дата"
, NEW."Время"
, NEW."ЛицоСоздал"
)
) THEN ...
A vzhledem k tomu, že tuto spouštěcí funkci lze použít pouze v UPDATE
spoušť kvůli přítomnosti OLD/NEW
ve stavu vyšší úrovně, pak lze tento stav obecně odstranit WHEN
- stav viz #1...
Zdroj: www.habr.com