PostgreSQL antipatternlari: SQLda vaziyatni baholash

SQL C++ emas, JavaScript ham emas. Shuning uchun mantiqiy ifodalarni baholash boshqacha va bu umuman bir xil emas:

WHERE fncondX() AND fncondY()

= fncondX() && fncondY()

PostgreSQL so'rovini bajarish rejasini optimallashtirishda ekvivalent shartlarni o'zboshimchalik bilan "qayta tartibga solish" mumkin, alohida yozuvlar uchun ulardan hech birini hisoblamang, qo'llaniladigan indeks holatiga murojaat qiling ... Qisqasi, eng oson yo'li - siz boshqara olmaydi ular bo'lish tartibi (va ular umuman hisoblab chiqiladimi) teng sharoitlar.

Shuning uchun, agar siz hali ham ustuvorlikni boshqarishni istasangiz, tizimli ravishda qilishingiz kerak bu shartlarni tengsiz qiling shartli bilan ifodalar ΠΈ operatorlar.

PostgreSQL antipatternlari: SQLda vaziyatni baholash
Ma'lumotlar va ular bilan ishlash asosdir VLSI kompleksimiz, shuning uchun ular bo'yicha operatsiyalar nafaqat to'g'ri, balki samarali bajarilishi biz uchun juda muhimdir. Keling, iboralarni hisoblashda xatolarga yo'l qo'yilishi mumkin bo'lgan va ularning samaradorligini oshirishga arziydigan aniq misollarni ko'rib chiqaylik.

β„– 0: RTFM

Boshlanmoqda hujjatlardan misol:

Baholash tartibi muhim bo'lsa, uni konstruktsiya bilan tuzatish mumkin CASE. Masalan, jumlada nolga bo'linmaslikning bu usuli WHERE ishonchsiz:

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

Xavfsiz variant:

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

Amaldagi qurilish CASE ifodani optimallashtirishdan himoya qiladi, shuning uchun uni faqat kerak bo'lganda ishlatish kerak.

β„–1: ishga tushirish holati

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

Hamma narsa yaxshi ko'rinadi, lekin ... Hech kim investitsiya qilinganini va'da qilmaydi SELECT birinchi shart noto'g'ri bo'lsa, bajarilmaydi. bilan tuzating o'rnatilgan IF:

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

Endi diqqat bilan qaraylik - tetik funktsiyasining butun tanasi "o'ralgan" bo'lib chiqdi IF. Va bu shuni anglatadiki, bizni ushbu holatni foydalanish tartibidan olib tashlashga hech narsa to'sqinlik qilmaydi WHEN- shartlar:

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

Ushbu yondashuv, agar shart noto'g'ri bo'lsa, server resurslarini kafolat bilan saqlashga imkon beradi.

β„–2: YOKI/VA zanjiri

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

Aks holda, ikkalasini ham olish mumkin EXISTS to'g'ri bo'ladi, lekin ikkalasi ham bajariladi.

Ammo agar biz ulardan biri "to'g'ri" ekanligini aniq bilsak, tez-tez (yoki "noto'g'ri" - uchun AND-zanjirlar) - ikkinchisi yana bir bor bajarilmasligi uchun qandaydir tarzda "uning ustuvorligini oshirish" mumkinmi?

Ma'lum bo'lishicha, bu mumkin - algoritmik yondashuv maqola mavzusiga yaqin PostgreSQL antipatternlari: Nodir kirish JOINning o'rtasiga etadi.

Keling, ikkala shartni ham "CASE ostida turaylik":

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

Bu holatda biz aniqlamadik ELSE-qiymat, ya'ni ikkala shart ham noto'g'ri bo'lsa CASE qaytadi NULL, deb talqin etiladi FALSE Π² WHERE- sharoitlar.

Ushbu misolni boshqa yo'l bilan birlashtirish mumkin - ta'mga va rangga:

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

β„–3: shartlarni qanday yozmaslik kerak

Biz ikki kun davomida ushbu qo'zg'atuvchining "g'alati" ishga tushishining sabablarini tahlil qildik - keling, nima uchun ekanligini bilib olaylik.

Manba:

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

Muammo β„–1: Tengsizlik NULLni hisobga olmaydi

Faraz qilaylik, hamma narsa OLD- maydonlar muhim edi NULL. Nima bo'ladi?

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

Va shartlarni ishlab chiqish nuqtai nazaridan NULL ekvivalent FALSE, yuqorida aytib o'tilganidek.

qaror: operatordan foydalaning IS DISTINCT FROM ΠΎΡ‚ ROW-operator, bir vaqtning o'zida butun yozuvlarni taqqoslash:

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

2-muammo: bir xil funktsiyani turlicha amalga oshirish

Keling, solishtiring:

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

Nima uchun qo'shimcha investitsiyalar mavjud SELECT? Funktsiya to_regclass? Nega boshqacha...

Keling, tuzatamiz:

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

β„–3 muammo: bool ustunligi

Keling, manbani formatlaymiz:

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

Voy ... Aslida, birinchi ikkita shartning birortasi haqiqatga to'g'ri kelgan taqdirda, butun shartga aylanadi. TRUE, tengsizliklarni hisobga olmaganda. Va bu biz xohlagan narsa emas.

Keling, tuzatamiz:

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

Muammo β„–4 (kichik): bitta maydon uchun murakkab YOKI shart

Aslida, 3-raqamda muammolarga duch keldik, chunki uchta shart bor edi. Ammo ularning o'rniga siz mexanizmdan foydalanib, bittasi bilan olishingiz mumkin coalesce ... IN:

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

Biz ham shundaymiz NULL "ushlash" va murakkab OR Qavslar bilan ovora bo'lishingiz shart emas.

jami

Keling, nima borligini aniqlaymiz:

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

Va bu trigger funktsiyasidan faqat foydalanish mumkinligini hisobga olsak UPDATEmavjudligi sababli tetik OLD/NEW yuqori darajadagi holatda, keyin bu holat odatda olib tashlanishi mumkin WHEN-1-sonda ko'rsatilganidek, vaziyat ...

Manba: www.habr.com

a Izoh qo'shish