ПостгреСКЛ антиобрасци: процена стања у СКЛ-у

СКЛ није Ц++, нити ЈаваСцрипт. Дакле, процена логичких израза је другачија, а ово уопште није иста ствар:

WHERE fncondX() AND fncondY()

= fncondX() && fncondY()

Док оптимизујете план извршења ПостгреСКЛ упита може произвољно „преуредити“ еквивалентне услове, не рачунајте ниједну за појединачне записе, позивајте се на стање примењеног индекса ... Укратко, најлакше је претпоставити да сте не могу управљати редоследом којим ће бити (и да ли ће се уопште рачунати) једнаки условиа.

Стога, ако и даље желите да управљате приоритетом, морате структурално чине ове услове неједнаким са условним изрази и оператора.

ПостгреСКЛ антиобрасци: процена стања у СКЛ-у
Подаци и рад са њима су основа нашег ВЛСИ комплекса, па нам је веома важно да се операције на њима обављају не само исправно, већ и ефикасно. Погледајмо конкретне примере где се могу направити грешке у евалуацији израза и где је вредно побољшати њихову ефикасност.

#0: РТФМ

Почиње пример из документације:

Када је редослед вредновања важан, он се може фиксирати конструктом CASE. На пример, на овај начин избегавате дељење нулом у реченици WHERE непоуздан:

SELECT ... WHERE x > 0 AND y/x > 1.5;

Сигурна опција:

SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;

Конструкција која се користи CASE штити израз од оптимизације, тако да га треба користити само када је то неопходно.

#1: стање окидача

BEGIN
  IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
    ...
  END IF;
  RETURN NEW;
END;

Све изгледа добро, али... Нико не обећава да је уложено SELECT неће се извршити ако је први услов нетачан. Поправи то са угнездио IF:

BEGIN
  IF cond(NEW.fld) THEN
    IF EXISTS(SELECT ...) THEN
      ...
    END IF;
  END IF;
  RETURN NEW;
END;

Сада погледајмо пажљиво - испоставило се да је цело тело функције окидача "умотано". IF. А то значи да нас ништа не спречава да ово стање уклонимо из процедуре коришћења WHEN-Услови:

BEGIN
  IF EXISTS(SELECT ...) THEN
    ...
  END IF;
  RETURN NEW;
END;
...
CREATE TRIGGER ...
  WHEN cond(NEW.fld);

Овај приступ вам омогућава да сачувате ресурсе сервера уз гаранцију ако је услов нетачан.

#2: ИЛИ/И ланац

SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)

У супротном, може се добити да обоје EXISTS биће истина, али оба ће бити погубљена.

Али ако са сигурношћу знамо да је једно од њих „тачно“ много чешће (или „нетачно“ – за AND-ланци) - да ли је могуће некако "повећати његов приоритет" да се други не изврши још једном?

Испоставља се да је могуће - алгоритамски приступ је близак теми чланка ПостгреСКЛ антиобрасци: Ретки унос достиже средину ЈОИН-а.

Хајде да само "гурнемо под ЦАСЕ" оба ова услова:

SELECT ...
WHERE
  CASE
    WHEN EXISTS(... A) THEN TRUE
    WHEN EXISTS(... B) THEN TRUE
  END

У овом случају нисмо дефинисали ELSE-вредност, односно ако су оба услова нетачна CASE ће се вратити NULL, што се тумачи као FALSE в WHERE- Услови.

Овај пример се може комбиновати на други начин - по укусу и боји:

SELECT ...
WHERE
  CASE
    WHEN NOT EXISTS(... A) THEN EXISTS(... B)
    ELSE TRUE
  END

#3: како [не] написати услове

Провели смо два дана анализирајући разлоге „чудног“ покретања овог окидача – да видимо зашто.

Извор:

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 ...

Проблем #1: Неједнакост не узима у обзир НУЛЛ

Претпоставимо да је све OLD-поља су била важна NULL. Шта ће се десити?

SELECT NULL <> 1 OR NULL <> 2;
-- NULL

И са становишта разраде услова NULL еквивалент FALSE, што је горе поменуто.

одлука: користи оператор IS DISTINCT FROM из ROW-оператер, који упоређује читаве записе одједном:

SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
-- TRUE

Проблем број 2: различита имплементација исте функционалности

Упоредите:

NEW."Документ_" = (select '"Комплект"'::regclass::oid)
NEW."Документ_" = (select to_regclass('"ДокументПоЗарплате"')::oid)

Зашто постоје додатне инвестиције SELECT? Функција to_regclass? Зашто је другачије...

Хајде да поправимо:

NEW."Документ_" = '"Комплект"'::regclass::oid
NEW."Документ_" = '"ДокументПоЗарплате"'::regclass::oid

Проблем #3: Боол приоритет

Хајде да форматирамо извор:

{... IS NULL} OR
{... Комплект} OR
{... ДокументПоЗарплате} AND
( {... неравенства} )

Упс... У ствари, показало се да се у случају истинитости било ког од прва два услова цео услов претвара у TRUE, занемарујући неједнакости. А ово уопште није оно што смо желели.

Хајде да поправимо:

(
  {... IS NULL} OR
  {... Комплект} OR
  {... ДокументПоЗарплате}
) AND
( {... неравенства} )

Проблем #4 (мали): сложени услов ИЛИ за једно поље

У ствари, имали смо проблема у броју 3 управо зато што су била три услова. Али уместо њих, можете проћи са једним, користећи механизам coalesce ... IN:

coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"')

Тако да смо NULL "ухватити", и сложено OR Не морате да се мучите са заградама.

Укупно

Хајде да поправимо оно што имамо:

IF (
  coalesce(NEW."Документ_"::text, '') IN ('', '"Комплект"', '"ДокументПоЗарплате"') AND
  (
    OLD."ДокументНашаОрганизация"
  , OLD."Удален"
  , OLD."Дата"
  , OLD."Время"
  , OLD."ЛицоСоздал"
  ) IS DISTINCT FROM (
    NEW."ДокументНашаОрганизация"
  , NEW."Удален"
  , NEW."Дата"
  , NEW."Время"
  , NEW."ЛицоСоздал"
  )
) THEN ...

А с обзиром да се ова функција окидача може користити само у UPDATEокидач због присуства OLD/NEW у стању вишег нивоа, онда се овај услов генерално може уклонити WHEN- стање као што је приказано у #1...

Извор: ввв.хабр.цом

Додај коментар