Mga Antipattern ng PostgreSQL: Pagsusuri ng Kondisyon sa SQL

Ang SQL ay hindi C++, at hindi rin ito JavaScript. Samakatuwid, ang pagsusuri ng mga lohikal na expression ay naiiba, at hindi ito ang parehong bagay:

WHERE fncondX() AND fncondY()

= fncondX() && fncondY()

Habang ino-optimize ang execution plan ng isang PostgreSQL query maaaring arbitraryong "muling ayusin" ang mga katumbas na kondisyon, huwag kalkulahin ang alinman sa mga ito para sa mga indibidwal na rekord, sumangguni sa kondisyon ng inilapat na index ... Sa madaling salita, ang pinakamadaling paraan ay ipagpalagay na ikaw hindi kayang pamahalaan ang pagkakasunud-sunod kung saan sila (at kung sila ay kakalkulahin sa lahat) pantay mga kondisyon.

Samakatuwid, kung gusto mo pa ring pamahalaan ang priyoridad, kailangan mong structurally gawing hindi pantay ang mga kundisyong ito na may kondisyon ekspresyon ΠΈ mga operator.

Mga Antipattern ng PostgreSQL: Pagsusuri ng Kondisyon sa SQL
Data at pakikipagtulungan sa kanila ang batayan ng aming VLSI complex, kaya napakahalaga para sa amin na ang mga operasyon sa mga ito ay isinasagawa hindi lamang nang tama, ngunit mahusay din. Tingnan natin ang mga konkretong halimbawa kung saan maaaring gumawa ng mga pagkakamali sa pagsusuri ng expression, at kung saan ito ay nagkakahalaga ng pagpapabuti ng kanilang kahusayan.

#0: RTFM

Nagsisimula halimbawa mula sa dokumentasyon:

Kapag ang pagkakasunud-sunod ng pagsusuri ay mahalaga, maaari itong ayusin sa pamamagitan ng konstruksyon CASE. Halimbawa, sa ganitong paraan upang maiwasan ang paghahati ng zero sa isang pangungusap WHERE hindi mapagkakatiwalaan:

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

Ligtas na opsyon:

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

Ang ginamit na konstruksiyon CASE pinoprotektahan ang expression mula sa pag-optimize, kaya dapat lang itong gamitin kung kinakailangan.

#1: kundisyon ng trigger

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

Mukhang maganda ang lahat, ngunit... Walang nangangako na ang namuhunan SELECT ay hindi isasagawa kung mali ang unang kundisyon. Ayusin ito sa nakapugad IF:

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

Ngayon tingnan nating mabuti - ang buong katawan ng pag-andar ng pag-trigger ay naging "nakabalot" sa IF. At nangangahulugan ito na walang pumipigil sa amin na alisin ang kundisyong ito mula sa pamamaraang ginagamit WHEN-kondisyon:

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

Binibigyang-daan ka ng diskarteng ito na i-save ang mga mapagkukunan ng server na may garantiya kung mali ang kundisyon.

#2: O/AT chain

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

Kung hindi, maaari itong makuha na pareho EXISTS ay magiging totoo, ngunit kapwa mapapatupad.

Ngunit kung alam nating sigurado na ang isa sa kanila ay "totoo" nang mas madalas (o "mali" - para sa AND-chains) - posible bang kahit papaano ay "dagdagan ang priyoridad nito" upang ang pangalawa ay hindi maisakatuparan muli?

Ito ay lumalabas na posible - ang algorithm na diskarte ay malapit sa paksa ng artikulo Mga PostgreSQL Antipattern: Ang bihirang tala ay aabot sa gitna ng isang SUMALI.

"I-shove under CASE" lang natin ang parehong kundisyong ito:

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

Sa kasong ito, hindi namin tinukoy ELSE-value, iyon ay, kung ang parehong mga kundisyon ay mali CASE babalik NULL, na binibigyang kahulugan bilang FALSE Π² WHERE- kundisyon.

Ang halimbawang ito ay maaaring pagsamahin sa ibang paraan - sa panlasa at kulay:

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

#3: paano [hindi] magsulat ng mga kundisyon

Gumugol kami ng dalawang araw sa pagsusuri sa mga dahilan para sa "kakaibang" pag-trigger ng trigger na ito - tingnan natin kung bakit.

Pinagmulan:

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: Ang hindi pagkakapantay-pantay ay hindi isinasaalang-alang ang NULL

Ipagpalagay natin na ang lahat OLD-ang mga patlang ay mahalaga NULL. Ano ang mangyayari?

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

At mula sa punto ng view ng pagtatrabaho sa mga kondisyon NULL katumbas FALSE, gaya ng nabanggit sa itaas.

desisyon: gumamit ng operator IS DISTINCT FROM mula sa ROW-operator, naghahambing ng buong mga tala nang sabay-sabay:

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

Problema numero 2: iba't ibang pagpapatupad ng parehong pag-andar

Ihambing natin ang:

NEW."Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚_" = (select '"ΠšΠΎΠΌΠΏΠ»Π΅ΠΊΡ‚"'::regclass::oid)
NEW."Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚_" = (select to_regclass('"Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠŸΠΎΠ—Π°Ρ€ΠΏΠ»Π°Ρ‚Π΅"')::oid)

Bakit may extra investments SELECT? Isang function to_regclass? Bakit iba...

Ayusin natin:

NEW."Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚_" = '"ΠšΠΎΠΌΠΏΠ»Π΅ΠΊΡ‚"'::regclass::oid
NEW."Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚_" = '"Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠŸΠΎΠ—Π°Ρ€ΠΏΠ»Π°Ρ‚Π΅"'::regclass::oid

Problema #3: bool precedence

I-format natin ang pinagmulan:

{... IS NULL} OR
{... ΠšΠΎΠΌΠΏΠ»Π΅ΠΊΡ‚} OR
{... Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠŸΠΎΠ—Π°Ρ€ΠΏΠ»Π°Ρ‚Π΅} AND
( {... нСравСнства} )

Oops ... Sa katunayan, lumabas na sa kaso ng katotohanan ng alinman sa unang dalawang kundisyon, ang buong kundisyon ay nagiging TRUE, binabalewala ang mga hindi pagkakapantay-pantay. At hindi ito ang gusto namin.

Ayusin natin:

(
  {... IS NULL} OR
  {... ΠšΠΎΠΌΠΏΠ»Π΅ΠΊΡ‚} OR
  {... Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠŸΠΎΠ—Π°Ρ€ΠΏΠ»Π°Ρ‚Π΅}
) AND
( {... нСравСнства} )

Problema #4 (maliit): kumplikado O kundisyon para sa isang field

Actually, nagkaproblema kami sa No. 3 precisely because there were three conditions. Ngunit sa halip na sa kanila, maaari kang makakuha ng isa, gamit ang mekanismo coalesce ... IN:

coalesce(NEW."Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚_"::text, '') IN ('', '"ΠšΠΎΠΌΠΏΠ»Π΅ΠΊΡ‚"', '"Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠŸΠΎΠ—Π°Ρ€ΠΏΠ»Π°Ρ‚Π΅"')

Ganun din tayo NULL "huli", at kumplikado OR Hindi mo kailangang mag-abala sa mga panaklong.

Sa kabuuan

Ayusin natin ang nakuha natin:

IF (
  coalesce(NEW."Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚_"::text, '') IN ('', '"ΠšΠΎΠΌΠΏΠ»Π΅ΠΊΡ‚"', '"Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠŸΠΎΠ—Π°Ρ€ΠΏΠ»Π°Ρ‚Π΅"') AND
  (
    OLD."Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΠ°ΡˆΠ°ΠžΡ€Π³Π°Π½ΠΈΠ·Π°Ρ†ΠΈΡ"
  , OLD."Π£Π΄Π°Π»Π΅Π½"
  , OLD."Π”Π°Ρ‚Π°"
  , OLD."ВрСмя"
  , OLD."Π›ΠΈΡ†ΠΎΠ‘ΠΎΠ·Π΄Π°Π»"
  ) IS DISTINCT FROM (
    NEW."Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΠ°ΡˆΠ°ΠžΡ€Π³Π°Π½ΠΈΠ·Π°Ρ†ΠΈΡ"
  , NEW."Π£Π΄Π°Π»Π΅Π½"
  , NEW."Π”Π°Ρ‚Π°"
  , NEW."ВрСмя"
  , NEW."Π›ΠΈΡ†ΠΎΠ‘ΠΎΠ·Π΄Π°Π»"
  )
) THEN ...

At dahil magagamit lang ang trigger function na ito sa UPDATEtrigger dahil sa presensya OLD/NEW sa mataas na antas na kondisyon, kung gayon ang kundisyong ito ay karaniwang maaaring alisin WHEN-kondisyon tulad ng ipinapakita sa #1...

Pinagmulan: www.habr.com

Magdagdag ng komento