PostgreSQL Antipatterns: llogaritja e kushteve në SQL

SQL — nuk është C++, as JavaScript. Prandaj, llogaritja e shprehjeve logjike ndodh ndryshe, dhe kjo është krejt ndryshe:

KUHERE fncondX() DHE fncondY()

= fncondX() && fncondY()

Gjatë procesit të optimizimit të planit të ekzekutimit të pyetjeve PostgreSQL mund të "rregullojë" në mënyrë të rastësishme kushtet ekuivalente, të mos llogarisë disa prej tyre për regjistra të veçantë, t'i atribuojë kushtit të indeksit të përdorur... Në thelb, është më e lehtë të mendohet se nuk mund të menaxhosh rendin në të cilin do të llogariten (dhe nëse do të llogariten fare) kushtet në mënyrë të barabartë. Prandaj, nëse dëshiron që të menaxhosh prioritetin, duhet që strukturnisht

të bësh këto kushte të pabarabarta nëpërmjet shprehjeve me anë të kushteve Të dhënat dhe puna me to — janë thelbësore dhe operatorësh.

PostgreSQL Antipatterns: llogaritja e kushteve në SQL
Të dhënat dhe puna me to janë themeli e kompleksit tonë SBIS, prandaj është shumë e rëndësishme për ne që operacionet mbi to të realizohen jo vetëm korrekt, por edhe efikas. Le të shohim disa shembuj konkretë, ku mund të ndodhin gabime në llogaritjen e shprehjeve, dhe ku duhet të përmirësojmë efikasitetin e tyre.

#0: RTFM

Fillimi shembulli nga dokumenti:

Kur rendi i llogaritjes është i rëndësishëm, mund të fiksohet përmes konstrukcionit CASE.P.sh, ky mënyrë për të shmangur ndarjen me zero në deklaratë WHERE nuk është i besueshëm:

Zgjidhja e sigurt:

Varianti i sigurt:

SELECT ... KUHERE CASE KUR x > 0 ATËHERË y/x > 1.5 ELSË false MBARO;

Konstrukcioni i aplikuar kështu CASE. mbron shprehjen nga optimizimi, prandaj duhet përdorur vetëm kur është e nevojshme.

#1: условие в триггере

BEGIN
  IF cond(NEW.fld) DHE EKZISTON(SELECT ...) ATËHERË
    ...
  MBARO IF;
  KTHE NEW;
END;

Të gjitha duken mirë, por... Askush nuk e premton që e përmbajtur SELECT nuk do të ekzekutohet në rastin e pavërtetësisë së kushtit të parë. Ta përmirësojmë me nën IF:

BEGIN
  IF cond(NEW.fld) ATËHERË
    IF EKZISTON(SELECT ...) ATËHERË
      ...
    MBARO IF;
  MBARO IF;
  KTHE NEW;
END;

Tani le të shohim me kujdes — e gjithë trupi i funksionit të triguarit doli që ishte "ndarë" në IF. Dhe kjo do të thotë që asgjë nuk na pengon ta nxjerrim këtë kusht nga procedura përmes kushteveBEGIN IF EKZISTON(SELECT ...) ATËHERË ... MBARO IF; KTHE NEW; END; ... KRIJO TRIGGER ... KUR cond(NEW.fld);:

Ky qasje mundëson garantimin e kursimeve të burimeve të serverit në rastin e pavërtetësisë së kushtit.

SELECT ... KUHERE EKZISTON(... A) OSE EKZISTON(... B)

#2: OR/AND-цепочка

Në rast të pakëndshëm, mund të ndodhë që të dy

do të jenë "të vërteta", por EXISTS të dy do të ekzekutohen. Por nëse ne e dimë saktësisht që njëri prej tyre është "i vërtetë" shumë më shpesh (ose "i gabuar" — për.

-lidhje) — a nuk mund ta "rrisim prioritetin e tij", në mënyrë që tjetri të mos ekzekutohet pa nevojë? DHEDuket se është e mundur — qasja algoritmike është e afërt me temën e artikullit

PostgreSQL Antipatterns: një shkrim i rrallë arrin në mes të JOIN Le të vendosim "në CASE" të dy këto kushte:.

SELECT ... KUHERE CASE KUR EKZISTON(... A) ATËHERË TRUE KUR EKZISTON(... B) ATËHERË TRUE MBARO

Në këtë rast ne nuk e kemi përcaktuar

VLERËS ELSE -pra, në rast të pavërtetësisë së të dy kushteve-vlera, domethënë në rastin e rrejshmërisë së të dy kushteve CASE. do të kthejë NULL, që përkthehet si FALSEWHERE-kusht.

Ky shembuj mund të kombinohen ndryshe — sipas shijes:

SELECT ...
KUHERE
  CASE
    KUR NUK EKZISTON(... A) ATËHERË EKZISTON(... B)
    ELSË TRUE
  MBARO

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

Për analizimin e arsyeve të "çuditshme" të ekzekutimit të këtij triguari ne kemi shpenzuar dy ditë — le të shohim pse.

Origjinali:

IF( NEW."Dokument_" është null ose NEW."Dokument_" = (zgjedh '"Kompakt"'::regclass::oid) ose NEW."Dokument_" = (zgjedh to_regclass('"DokumentPërPagë"')::oid)
     DHE (   OLD."DokumentOrganizataJonë"  NEW."DokumentOrganizataJonë"
          OSE OLD."Fshirë"  NEW."Fshirë"
          OSE OLD."Data"  NEW."Data"
          OSE OLD."Koha"  NEW."Koha"
          OSE OLD."Krijuesi"  NEW."Krijuesi" ) ) ATËHERË ...

Problemi №1: pabarazia nuk merr parasysh NULL

Le të supozojmë se të gjitha OLD-fushat kishin vlerën NULL. Çfarë do të ndodhte?

SELECT NULL  1 OSE NULL  2;
-- NULL

Dhe nga pikëpamja e përmbushjes së kushteve NULL shërben si FALSE, siç u përmend më parë.

Zgjidhja: përdorni operatorin IS DISTINCT FROM nga RRESHT-operatorit, duke krahasuar menjëherë të gjitha regjistrat:

SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
-- E VËRTETË

Problemi №2: realizim i ndryshëm i funksionalitetit të njëjtë

Le të krahasojmë:

NEW."Dokument_" = (zgjedh '"Kompakt"'::regclass::oid)
NEW."Dokument_" = (zgjedh to_regclass('"DokumentPërPagë"')::oid)

Pse këtu janë të panevojshme për të vendosur SELECT? А функция to_regclass? А по-разному-то почему?..

Le të korrigjojmë:

NEW."Dokument_" = '"Kompakt"'::regclass::oid
NEW."Dokument_" = '"DokumentPërPagë"'::regclass::oid

Problemi №3: prioriteti i operacioneve bool

Le të formatizojmë origjinalin:

{... IS NULL} OSE
{... Kompakt} OSE
{... DokumentPërPagë} DHE
( {... pabarazitë} )

Ups... Në fakt, ndodhi se në rastin e vërtetësisë së çfarëdo nga dy kushteve të para, e gjithë kushti kthehet në TRUE, pa marrë parasysh pabarazitë. Dhe kjo është krejt ndryshe nga ajo që donim.

Le të korrigjojmë:

(
  {... IS NULL} OSE
  {... Kompakt} OSE
  {... DokumentPërPagë}
) DHE
( {... pabarazitë} )

Problemi №4 (i vogël): kushti i ndërlikuar OR për një fushë

Në të vërtetë, problemet në №3 u shfaqën pikërisht sepse kishte tre kushte. Por në vend të tyre mund të përdoret një, përmes mekanizmit coalesce ... IN:

coalesce(NEW."Dokument_"::text, '') IN ('', '"Kompakt"', '"DokumentPërPagë"')

Kështu ne NULL "kapim", dhe nuk do të kemi nevojë të ndërtojmë ndërlikime me OSE në fillim.

Në përfundim

Le të shohim çfarë kemi arritur:

IF (
  coalesce(NEW."Dokument_"::text, '') IN ('', '"Komplekt"', '"DokumentPoZarpatë"') AND
  (
    OLD."DokumentNашаОrganizatsia"
  , OLD."Udalën"
  , OLD."Data"
  , OLD."Koha"
  , OLD."LicaKrijuar"
  ) IS DISTINCT FROM (
    NEW."DokumentNашаОrganizatsia"
  , NEW."Udalën"
  , NEW."Data"
  , NEW."Koha"
  , NEW."LicaKrijuar"
  )
) THEN ...

Dhe nëse merret parasysh se kjo funksion trigger-i mund të aplikohet vetëm në UPDATE-trigger për shkak të pranisë OLD/NEW në kushtin e nivelit të lartë, atëherë mund ta nxjerrim këtë kusht në kushteve-kusht, siç u tregua në #1…

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster