Antipattern të PostgreSQL: «Duhet të mbetet vetëm një!»

Në SQL përshkruani «çfarë» dëshironi të merrni, jo «si» duhet të ekzekutohet. Pra, problemi i zhvillimit të pyetjeve SQL në stilin «si shprehet, ashtu dhe shkruhet» zë vendin e tij të nderuar, përkrah veçorive të llogaritjes së kushteve në SQL.

Sot do të shohim shembuj shumë të thjeshtë për atë çfarë mund të sjellë në kontekstin e përdorimit të GROUP/DISTINCT dhe LIMIT ndërsa bashkë me ta.

Ja, nĂ«se keni shkruar nĂ« pyetje «sĂ« pari bashkoni kĂ«to tabela, pastaj hiqni tĂ« gjitha kopjet, duhet tĂ« mbetet vetĂ«m njĂ« ekzemplar pĂ«r çdo çelĂ«s» — ashtu do tĂ« funksionojĂ«, edhe nĂ«se bashkimi nuk ka qenĂ« i nevojshĂ«m fare.

Dhe ndonjĂ«herĂ« ndodh qĂ« thjesht «punon», ndonjĂ«herĂ« — ndikon negativisht nĂ« performancĂ«, e ndonjĂ«herĂ« sjell efekte krejtĂ«sisht tĂ« papritura nga pikĂ«pamja e zhvilluesit.

Antipattern të PostgreSQL: «Duhet të mbetet vetëm një!»
Epo, ndoshta jo aq teatrale, por


«Çifti i Ă«mbĂ«l»: JOIN + DISTINCT

SELECT DISTINCT
  X.*
FROM
  X
JOIN
  Y
    ON Y.fk = X.pk
WHERE
  Y.bool_condition;

Sa e qartĂ« Ă«shtĂ« se donit tĂ« zgjidhni kĂ«to regjistrime X, pĂ«r tĂ« cilat nĂ« Y ka lidhje me kushtin qĂ« po ekzekutohet. E shkruajtĂ«t pyetjen pĂ«rmes JOIN — morĂ«m disa vlera pk disa herĂ« (saktĂ«sisht sa shumĂ« regjistra tĂ« pĂ«rshtatshĂ«m nĂ« Y rezultuan). Si ta eliminojmĂ«? Sigurisht DISTINCT!

Sidomos ‘gĂ«zon’ kur pĂ«r çdo regjistĂ«r X gjenden disa qindra regjistra tĂ« lidhur Y, dhe mĂ« pas heroikisht hiqen dublikatet...

Antipattern të PostgreSQL: «Duhet të mbetet vetëm një!»

Si ta rregullojmĂ«? SĂ« pari, tĂ« kuptojmĂ« se detyra mund tĂ« modifikohet nĂ« ‘tĂ« pĂ«rzgjidhen regjistrat X, pĂ«r tĂ« cilat nĂ« Y ka TË PAKTËN NJË regjistĂ«r tĂ« lidhur me kushtin e ekzekutimit’ — sepse nga regjistri Y vetĂ« nuk na nevojitet asgjĂ«.

Nënshkrimi EXISTS

SELECT
  *
FROM
  X
WHERE
  EXISTS(
    SELECT
      NULL
    FROM
      Y
    WHERE
      fk = X.pk AND
      bool_condition
    LIMIT 1
  );

Disa versione tĂ« PostgreSQL kuptojnĂ« se nĂ« EXISTS mjafton tĂ« gjendet regjistri i parĂ« qĂ« kapet, versionet mĂ« tĂ« vjetra — jo. Prandaj preferoj gjithmonĂ« tĂ« tregoj LIMIT 1 brenda EXISTS.

LATERAL JOIN

SELECT
  X.*
FROM
  X
, LATERAL (
    SELECT
      Y.*
    FROM
      Y
    WHERE
      fk = X.pk AND
      bool_condition
    LIMIT 1
  ) Y
WHERE
  Y IS DISTINCT FROM NULL;

Ky variant gjithashtu lejon, nĂ«se Ă«shtĂ« e nevojshme, tĂ« kthehen menjĂ«herĂ« disa tĂ« dhĂ«na nga regjistri i lidhur Y qĂ« u gjet. NjĂ« variant i ngjashĂ«m Ă«shtĂ« shqyrtuar nĂ« artikullin ‘PostgreSQL Antipatterns: njĂ« regjistĂ«r i rrallĂ« arrin nĂ« mes tĂ« JOIN’.

‘Pse tĂ« paguani mĂ« shumë’: DISTINCT [ON] + LIMIT 1

Një avantazh shtesë i këtij lloji të transformimeve të pyetjeve është mundësia për të kufizuar lehtësisht ciklin e regjistrimeve, nëse nevojitet vetëm një/disa prej tyre, si në rastin në vijim:

SELECT DISTINCT ON(X.pk)
  *
FROM
  X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;

Tani lexohet pyetja dhe përpiqemi të kuptojmë se çfarë propozohet të bëjë DBMS:

  • kemi lidhur tabelat
  • e bĂ«jmĂ« unike sipas X.pk
  • nga regjistrimet e mbetura zgjedhim ndonjĂ« njĂ«

Pra, çfarë kemi marrë? «Një regjistrim ndonjë» nga të unifikuar - po sikur ta marrim këtë një nga të paunifikuarit, rezultatet do të ndryshonin ndonjëherë?.. «Po nëse nuk ka ndonjë ndryshim, pse të paguajmë më shumë?»

SELECT
  *
FROM
  (
    SELECT
      *
    FROM
      X
    -- këtu mund të futni kushte të përshtatshme
    LIMIT 1 -- +1 Limit
  ) X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;

Dhe i njëjti qëndrim me GROUP BY + LIMIT 1.

«Më mjafton të pyes»: GROUP i implicit + LIMIT

Këto gjëra hasen në raste të ndryshme kontrollesh për mosrënien bosh tabelash ose CTE gjatë ekzekutimit të pyetjes:

...
CASE
  WHEN (
    SELECT
      count(*)
    FROM
      X
    LIMIT 1
  ) = 0 THEN ...

Funksionet agregate (count/min/max/sum/...) ekzekutohen me sukses në të gjithë grupin, madje edhe pa specifikimin e qartë GROUP BY. Por ata me LIMIT nuk shkojnë shumë mirë.

Programuesi mund të mendojë «Nëse ka regjistrime atje, atëherë nuk më duhet më shumë se LIMIT». Por mos e bën këtë! Sepse për bazën kjo është:

  • llogarita se çfarĂ« dĂ«shirojnĂ« pĂ«r tĂ« gjitha regjistrimet
  • kthe sa rreshta kĂ«rkohet

Varësisht nga kushtet e synuara, këtu është i përshtatshëm një nga zëvendësimet:

  • (count + LIMIT 1) = 0 nĂ« NOT EXISTS(LIMIT 1)
  • (count + LIMIT 1) > 0 nĂ« EXISTS(LIMIT 1)
  • count >= N nĂ« (SELECT count(*) FROM (... LIMIT N))

«Sa të peshojë në gramë»: DISTINCT + LIMIT

SELECT DISTINCT
  pk
FROM
  X
LIMIT $1

Një zhvillues naiv mund të mendojë sinqerisht se ekzekutimi i pyetjes do të ndalet sapohesht të gjejmë $1 të parë të ndryshme vlera.

Ndoshta nĂ« tĂ« ardhmen kjo do tĂ« funksionojĂ« falĂ« njĂ« node tĂ« re Index Skip Scan, realizimi i sĂ« cilĂ«s aktualisht po punohet, por pĂ«r momentin — jo.

Për momentin, së pari do të nxirren të gjitha regjistrimet, unikizohen, dhe vetëm nga ato do të kthehet sa kërkohet. Sidomos është e trishtueshme nëse ne dëshirojmë diçka si $1 = 4, ndërsa regjistrimet në tabelë janë qindra mijë...

Për të mos u ndjerë keq kot, le të përdorim një kërkesë rekurzive «DISTINCT për të varfrit» nga PostgreSQL Wiki:

Antipattern të PostgreSQL: «Duhet të mbetet vetëm një!»

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster