PostgreSQL Antipatterns: «Det ska bara finnas en!»

I SQL beskriver du "vad" du vill ha, inte "hur" det ska utföras. DÀrför ligger problemet med att utveckla SQL-frÄgor i stilen "som hörs, sÄ skrivs" pÄ sin hedersplats, jÀmte egenskaperna för berÀkning av villkor i SQL.

I dag kommer vi att titta pÄ nÄgra mycket enkla exempel pÄ vad detta kan leda till i sammanhanget av att anvÀnda GROUP/DISTINCT och LIMIT tillsammans med dem.

Om du har skrivit i frĂ„gan «först anslut dessa tabeller, och sedan ta bort alla dubbletter, det ska bara finnas en instans per nyckel» — sĂ„ kommer det att fungera, Ă€ven om anslutningen inte alls var nödvĂ€ndig.

Och ibland har man tur och det "bara fungerar", ibland pÄverkar det prestandan negativt, och ibland ger det helt ovÀntade effekter ur utvecklarens synvinkel.

PostgreSQL Antipatterns: «Det ska bara finnas en!»
NÄvÀl, kanske inte sÄ spektakulÀra, men...

«Söta paret»: JOIN + DISTINCT

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

Det Ă€r tydligt vad man ville vĂ€lj sĂ„dana poster X för vilka det finns kopplade poster i Y med det aktuella villkoret.FrĂ„gan skrevs genom JOIN — och man fick nĂ„gra pk-vĂ€rden flera gĂ„nger (exakt sĂ„ mĂ„nga som det fanns passande poster i Y). Hur tar man bort dem? SjĂ€lvklart DISTINCT!

Det Àr sÀrskilt "uppmuntrande" nÀr varje X-post hittar flera hundra kopplade Y-poster, och sedan tar man heroiskt bort dubbletterna...

PostgreSQL Antipatterns: «Det ska bara finnas en!»

Hur rĂ€ttar man till det? Först och frĂ€mst mĂ„ste man inse att uppgiften kan modifieras till «vĂ€lj sĂ„dana poster X för vilka det finns ÅTMINSTONE ETT kopplad med det aktuella villkoret i Y» — för frĂ„n sjĂ€lva Y-posten behöver vi inget.

NĂ€stlad EXISTS

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

Vissa versioner av PostgreSQL förstÄr att det i EXISTS Àr tillrÀckligt att hitta den första posten, Àldre versioner gör inte det. DÀrför föredrar jag alltid att ange LIMIT 1 inuti 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;

Denna variant tillÄter ocksÄ att samtidigt returnera vissa data frÄn den funna kopplade Y-posten. En liknande variant har diskuterats i artikeln «PostgreSQL Antipatterns: sÀllan nÄr en post mitten av JOIN».

«Varför betala mer»: DISTINCT [ON] + LIMIT 1

En ytterligare fördel med denna typ av frÄgestÀllningar Àr möjligheten att enkelt begrÀnsa antalet poster, om endast en eller nÄgra av dem behövs, som i följande fall:

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

Nu lÀser vi frÄgan och försöker förstÄ vad databashanteringssystemet föreslÄr att göra:

  • vi kopplar samman tabellerna
  • vi unikgör efter X.pk
  • frĂ„n de Ă„terstĂ„ende posterna vĂ€ljer vi nĂ„gon en

SĂ„ vad har vi fĂ„tt? «NĂ„gon enskild post» frĂ„n de unikifierade – och om vi tar denna enskilda frĂ„n de icke-unikifierade, skulle resultatet Ă€ndras pĂ„ nĂ„got sĂ€tt?.. «Och om det inte spelar nĂ„gon roll, varför betala mer?»

SELECT
  *
FROM
  (
    SELECT
      *
    FROM
      X
    -- hÀr kan lÀmpliga villkor lÀggas till
    LIMIT 1 -- +1 Limit
  ) X
JOIN
  Y
    ON Y.fk = X.pk
LIMIT 1;

Och exakt samma tema med GROUP BY + LIMIT 1.

«Jag vill bara frÄga»: implicit GROUP + LIMIT

SÄdana saker förekommer vid olika kontroller av tomhet tabellen eller CTE under körningen av frÄgan:

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

Aggregatfunktioner (count/min/max/sum/...) kÀrnverkar över hela datasetet, Àven utan tydlig specifikation GROUP BY. Men de fungerar inte sÄ bra med LIMIT de.

Utvecklaren kan tÀnka «Om det finns poster dÀr, behöver jag inte mer Àn LIMIT». Men sÄ bör man inte tÀnka! Eftersom det för databasen Àr:

  • berĂ€kna vad som efterfrĂ„gas ge tillbaka sĂ„ mĂ„nga rader som begĂ€rs
  • Beroende pĂ„ mĂ„lvillkoren kan det vara lĂ€mpligt att göra en av följande byten:

(count + LIMIT 1) = 0

  • NOT EXISTS(LIMIT 1) till (count + LIMIT 1) > 0
  • EXISTS(LIMIT 1) till count >= N
  • (SELECT count(*) FROM (... LIMIT N)) till «Hur mycket ska jag ta i gram»: DISTINCT + LIMIT

SELECT DISTINCT pk FROM X LIMIT $1

Den naiva utvecklaren kan uppriktigt tro att frÄgutvÀrderingen kommer att stoppas,

sÄ fort vi hittar $1 första bÀsta olika vÀrden I framtiden kan det sÄ smÄningom fungera tack vare en ny nod.

Index Skip Scan , vars implementering just nu utarbetas, men Ă€n sĂ„ lĂ€nge – nej.För nu kommer först

alla poster att hĂ€mtas , unikgörs, och först frĂ„n dem kommer sĂ„ mĂ„nga som begĂ€rs att returneras. SĂ€rskilt sorgligt kan det bli om vi ville ha nĂ„got som, men det finns hundratusentals poster i tabellen
 $1 = 4För att inte grĂ„ta i onödan, lĂ„t oss anvĂ€nda en rekursiv frĂ„ga

«DISTINCT för fattiga» frÄn PostgreSQL Wiki PostgreSQL Antipatterns: berÀkning av villkor i SQL:

PostgreSQL Antipatterns: «Det ska bara finnas en!»

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster