SQL HowTo: pagsulat sa usa ka while loop direkta sa pangutana, o "Elementary three-step"

Matag karon ug unya, ang tahas sa pagpangita alang sa mga may kalabutan nga datos gamit ang usa ka hugpong sa mga yawe mitungha. hangtod makuha namo ang gikinahanglang kinatibuk-ang gidaghanon sa mga rekord.

Ang labing "tinuod nga kinabuhi" nga panig-ingnan mao ang pagpakita 20 labing karaan nga mga problema, gilista sa listahan sa mga empleyado (pananglitan, sulod sa usa ka dibisyon). Alang sa lainlaing mga "dashboard" sa pagdumala nga adunay mubu nga mga summary sa mga lugar sa trabaho, usa ka parehas nga hilisgutan ang gikinahanglan kanunay.

SQL HowTo: pagsulat sa usa ka while loop direkta sa pangutana, o "Elementary three-step"

Niini nga artikulo atong tan-awon ang pagpatuman sa PostgreSQL sa usa ka "naive" nga solusyon sa ingon nga problema, usa ka "mas maalamon" ug komplikado kaayo nga algorithm. "loop" sa SQL nga adunay kondisyon sa paggawas gikan sa nakit-an nga datos, nga mahimong mapuslanon alang sa kinatibuk-ang kalamboan ug alang sa paggamit sa uban nga susama nga mga kaso.

Magkuha ta ug test data set gikan sa miaging artikulo. Aron mapugngan ang gipakita nga mga rekord gikan sa "paglukso" matag karon ug unya kung ang mga gisunud nga kantidad nagdungan, pagpalapad sa indeks sa hilisgutan pinaagi sa pagdugang sa panguna nga yawe. Sa parehas nga oras, hatagan dayon kini nga pagkatalagsaon ug garantiya kanamo nga ang pagkasunud sa pagsunud dili klaro:

CREATE INDEX ON task(owner_id, task_date, id);
-- Π° старый - ΡƒΠ΄Π°Π»ΠΈΠΌ
DROP INDEX task_owner_id_task_date_idx;

Ingon sa nadungog, mao usab ang nahisulat

Una, atong i-sketch ang pinakasimple nga bersyon sa hangyo, pagpasa sa mga ID sa mga performers array isip input parameter:

SELECT
  *
FROM
  task
WHERE
  owner_id = ANY('{1,2,4,8,16,32,64,128,256,512}'::integer[])
ORDER BY
  task_date, id
LIMIT 20;

SQL HowTo: pagsulat sa usa ka while loop direkta sa pangutana, o "Elementary three-step"
[tan-awa sa explain.tensor.ru]

Usa ka gamay nga kasubo - kami nag-order ra sa 20 nga mga rekord, apan gibalik kini sa Index Scan kanamo 960 ka linya, nga kinahanglan usab nga ihan-ay ... Atong sulayan ang pagbasa og gamay.

unnest + ARRAY

Ang unang konsiderasyon nga makatabang kanato kon gikinahanglan 20 ra ang gisunod mga rekord, unya basaha lang dili mosobra sa 20 ang nahan-ay sa samang han-ay alang sa matag usa yawe. maayo, angay nga indeks (owner_id, task_date, id) naa mi.

Gamiton nato ang parehas nga mekanismo sa pagkuha ug "pagsabwag sa mga kolum" integral nga talaan sa talaan, ingon sa miaging artikulo. Mahimo usab naton i-apply ang pagpilo sa usa ka array gamit ang function ARRAY():

WITH T AS (
  SELECT
    unnest(ARRAY(
      SELECT
        t
      FROM
        task t
      WHERE
        owner_id = unnest
      ORDER BY
        task_date, id
      LIMIT 20 -- ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΠ²Π°Π΅ΠΌ Ρ‚ΡƒΡ‚...
    )) r
  FROM
    unnest('{1,2,4,8,16,32,64,128,256,512}'::integer[])
)
SELECT
  (r).*
FROM
  T
ORDER BY
  (r).task_date, (r).id
LIMIT 20; -- ... ΠΈ Ρ‚ΡƒΡ‚ - Ρ‚ΠΎΠΆΠ΅

SQL HowTo: pagsulat sa usa ka while loop direkta sa pangutana, o "Elementary three-step"
[tan-awa sa explain.tensor.ru]

Oh, mas maayo na! 40% nga mas paspas ug 4.5 ka beses nga mas gamay nga datos Kinahanglan nakong basahon kini.

Pag-materialize sa mga talaan sa lamesa pinaagi sa CTETugoti ako sa pagdani sa imong pagtagad sa kamatuoran nga sa pipila ka mga kaso Ang pagsulay sa pagtrabaho dayon sa mga natad sa usa ka rekord pagkahuman sa pagpangita niini sa usa ka subquery, nga wala "pagputos" niini sa usa ka CTE, mahimong mosangpot sa "multiply" InitPlan proporsyonal sa gidaghanon niining parehas nga mga natad:

SELECT
  ((
    SELECT
      t
    FROM
      task t
    WHERE
      owner_id = 1
    ORDER BY
      task_date, id
    LIMIT 1
  ).*);

Result  (cost=4.77..4.78 rows=1 width=16) (actual time=0.063..0.063 rows=1 loops=1)
  Buffers: shared hit=16
  InitPlan 1 (returns $0)
    ->  Limit  (cost=0.42..1.19 rows=1 width=48) (actual time=0.031..0.032 rows=1 loops=1)
          Buffers: shared hit=4
          ->  Index Scan using task_owner_id_task_date_id_idx on task t  (cost=0.42..387.57 rows=500 width=48) (actual time=0.030..0.030 rows=1 loops=1)
                Index Cond: (owner_id = 1)
                Buffers: shared hit=4
  InitPlan 2 (returns $1)
    ->  Limit  (cost=0.42..1.19 rows=1 width=48) (actual time=0.008..0.009 rows=1 loops=1)
          Buffers: shared hit=4
          ->  Index Scan using task_owner_id_task_date_id_idx on task t_1  (cost=0.42..387.57 rows=500 width=48) (actual time=0.008..0.008 rows=1 loops=1)
                Index Cond: (owner_id = 1)
                Buffers: shared hit=4
  InitPlan 3 (returns $2)
    ->  Limit  (cost=0.42..1.19 rows=1 width=48) (actual time=0.008..0.008 rows=1 loops=1)
          Buffers: shared hit=4
          ->  Index Scan using task_owner_id_task_date_id_idx on task t_2  (cost=0.42..387.57 rows=500 width=48) (actual time=0.008..0.008 rows=1 loops=1)
                Index Cond: (owner_id = 1)
                Buffers: shared hit=4"
  InitPlan 4 (returns $3)
    ->  Limit  (cost=0.42..1.19 rows=1 width=48) (actual time=0.009..0.009 rows=1 loops=1)
          Buffers: shared hit=4
          ->  Index Scan using task_owner_id_task_date_id_idx on task t_3  (cost=0.42..387.57 rows=500 width=48) (actual time=0.009..0.009 rows=1 loops=1)
                Index Cond: (owner_id = 1)
                Buffers: shared hit=4

Ang sama nga rekord "gitan-aw" 4 ka beses ... Hangtud sa PostgreSQL 11, kini nga kinaiya mahitabo kanunay, ug ang solusyon mao ang "pagputos" niini sa usa ka CTE, nga usa ka hingpit nga limitasyon alang sa optimizer niini nga mga bersyon.

Recursive accumulator

Sa miaging bersyon, sa kinatibuk-an among gibasa 200 ka linya alang sa gikinahanglan nga 20. Dili 960, apan mas gamay - posible ba?

Atong sulayan nga gamiton ang kahibalo nga atong gikinahanglan total 20 mga rekord. Sa ato pa, usbon namo ang pagbasa sa datos hangtod nga maabot namo ang kantidad nga among gikinahanglan.

Lakang 1: Listahan sa Pagsugod

Dayag nga, ang among "target" nga lista sa 20 nga mga rekord kinahanglan magsugod sa "una" nga mga rekord alang sa usa sa among mga yawe sa owner_id. Busa, una natong makit-an ang ingon niini "una kaayo" alang sa matag usa sa mga yawe ug idugang kini sa listahan, paghan-ay niini sa han-ay nga gusto nato - (task_date, id).

SQL HowTo: pagsulat sa usa ka while loop direkta sa pangutana, o "Elementary three-step"

Lakang 2: Pangitaa ang "sunod" nga mga entri

Karon kung atong kuhaon ang unang entry gikan sa atong listahan ug magsugod "lakang" sa unahan sa indeks pagpreserbar sa yawe sa tag-iya_id, unya ang tanan nga nakit-an nga mga rekord mao gyud ang sunod sa resulta nga pagpili. Siyempre, lamang hantod nalabang mi sa butt key ikaduhang entry sa listahan.

Kung kini nahimo nga "gitabok" namon ang ikaduha nga rekord, nan ang kataposang entry nga gibasa kinahanglang idugang sa listahan imbes sa una (nga adunay parehas nga tag-iya_id), pagkahuman among gilain pag-usab ang lista.

SQL HowTo: pagsulat sa usa ka while loop direkta sa pangutana, o "Elementary three-step"

Sa ato pa, kanunay namong makuha nga ang lista adunay dili labaw sa usa ka entry alang sa matag usa sa mga yawe (kung ang mga entries mahurot ug dili kami "motabok", nan ang unang entry gikan sa listahan mawala ra ug walay idugang. ), ug sila gihan-ay kanunay sa pataas nga han-ay sa yawe sa aplikasyon (task_date, id).

SQL HowTo: pagsulat sa usa ka while loop direkta sa pangutana, o "Elementary three-step"

Lakang 3: filter ug "palapad" nga mga rekord

Sa pipila sa mga laray sa among recursive nga pagpili, pipila ka mga rekord rv mga duplicated - una atong makita sama sa "pagtabok sa utlanan sa 2nd entry sa listahan", ug unya ilisan kini ingon nga ang 1st gikan sa listahan. Busa ang una nga panghitabo kinahanglan nga masala.

Ang gikahadlokan nga katapusang pangutana

WITH RECURSIVE T AS (
  -- #1 : заносим Π² список "ΠΏΠ΅Ρ€Π²Ρ‹Π΅" записи ΠΏΠΎ ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡƒ ΠΈΠ· ΠΊΠ»ΡŽΡ‡Π΅ΠΉ Π½Π°Π±ΠΎΡ€Π°
  WITH wrap AS ( -- "ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΈΠ·ΡƒΠ΅ΠΌ" record'Ρ‹, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΠ±Ρ€Π°Ρ‰Π΅Π½ΠΈΠ΅ ΠΊ полям Π½Π΅ Π²Ρ‹Π·Ρ‹Π²Π°Π»ΠΎ умноТСния InitPlan/SubPlan
    WITH T AS (
      SELECT
        (
          SELECT
            r
          FROM
            task r
          WHERE
            owner_id = unnest
          ORDER BY
            task_date, id
          LIMIT 1
        ) r
      FROM
        unnest('{1,2,4,8,16,32,64,128,256,512}'::integer[])
    )
    SELECT
      array_agg(r ORDER BY (r).task_date, (r).id) list -- сортируСм список Π² Π½ΡƒΠΆΠ½ΠΎΠΌ порядкС
    FROM
      T
  )
  SELECT
    list
  , list[1] rv
  , FALSE not_cross
  , 0 size
  FROM
    wrap
UNION ALL
  -- #2 : Π²Ρ‹Ρ‡ΠΈΡ‚Ρ‹Π²Π°Π΅ΠΌ записи 1-Π³ΠΎ ΠΏΠΎ порядку ΠΊΠ»ΡŽΡ‡Π°, ΠΏΠΎΠΊΠ° Π½Π΅ ΠΏΠ΅Ρ€Π΅ΡˆΠ°Π³Π½Π΅ΠΌ Ρ‡Π΅Ρ€Π΅Π· запись 2-Π³ΠΎ
  SELECT
    CASE
      -- Ссли Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ΠΎ для ΠΊΠ»ΡŽΡ‡Π° 1-ΠΉ записи
      WHEN X._r IS NOT DISTINCT FROM NULL THEN
        T.list[2:] -- ΡƒΠ±ΠΈΡ€Π°Π΅ΠΌ Π΅Π΅ ΠΈΠ· списка
      -- Ссли ΠΌΡ‹ НЕ пСрСсСкли ΠΏΡ€ΠΈΠΊΠ»Π°Π΄Π½ΠΎΠΉ ΠΊΠ»ΡŽΡ‡ 2-ΠΉ записи
      WHEN X.not_cross THEN
        T.list -- просто протягиваСм Ρ‚ΠΎΡ‚ ΠΆΠ΅ список Π±Π΅Π· ΠΌΠΎΠ΄ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΉ
      -- Ссли Π² спискС ΡƒΠΆΠ΅ Π½Π΅Ρ‚ 2-ΠΉ записи
      WHEN T.list[2] IS NULL THEN
        -- просто Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ пустой список
        '{}'
      -- пСрСсортировываСм ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ, убирая 1-ю запись ΠΈ добавляя послСднюю ΠΈΠ· Π½Π°ΠΉΠ΄Π΅Π½Π½Ρ‹Ρ…
      ELSE (
        SELECT
          coalesce(T.list[2] || array_agg(r ORDER BY (r).task_date, (r).id), '{}')
        FROM
          unnest(T.list[3:] || X._r) r
      )
    END
  , X._r
  , X.not_cross
  , T.size + X.not_cross::integer
  FROM
    T
  , LATERAL(
      WITH wrap AS ( -- "ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΈΠ·ΡƒΠ΅ΠΌ" record
        SELECT
          CASE
            -- Ссли всС-Ρ‚Π°ΠΊΠΈ "ΠΏΠ΅Ρ€Π΅ΡˆΠ°Π³Π½ΡƒΠ»ΠΈ" Ρ‡Π΅Ρ€Π΅Π· 2-ю запись
            WHEN NOT T.not_cross
              -- Ρ‚ΠΎ нуТная запись - пСрвая ΠΈΠ· спписка
              THEN T.list[1]
            ELSE ( -- Ссли Π½Π΅ пСрСсСкли, Ρ‚ΠΎ ΠΊΠ»ΡŽΡ‡ остался ΠΊΠ°ΠΊ Π² ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅ΠΉ записи - отталкиваСмся ΠΎΡ‚ Π½Π΅Π΅
              SELECT
                _r
              FROM
                task _r
              WHERE
                owner_id = (rv).owner_id AND
                (task_date, id) > ((rv).task_date, (rv).id)
              ORDER BY
                task_date, id
              LIMIT 1
            )
          END _r
      )
      SELECT
        _r
      , CASE
          -- Ссли 2-ΠΉ записи ΡƒΠΆΠ΅ Π½Π΅Ρ‚ Π² спискС, Π½ΠΎ ΠΌΡ‹ Ρ…ΠΎΡ‚ΡŒ Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ нашли
          WHEN list[2] IS NULL AND _r IS DISTINCT FROM NULL THEN
            TRUE
          ELSE -- Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ нашли ΠΈΠ»ΠΈ "ΠΏΠ΅Ρ€Π΅ΡˆΠ°Π³Π½ΡƒΠ»ΠΈ"
            coalesce(((_r).task_date, (_r).id) < ((list[2]).task_date, (list[2]).id), FALSE)
        END not_cross
      FROM
        wrap
    ) X
  WHERE
    T.size < 20 AND -- ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΠ²Π°Π΅ΠΌ Ρ‚ΡƒΡ‚ количСство
    T.list IS DISTINCT FROM '{}' -- ΠΈΠ»ΠΈ ΠΏΠΎΠΊΠ° список Π½Π΅ кончился
)
-- #3 : "Ρ€Π°Π·Π²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Π΅ΠΌ" записи - порядок Π³Π°Ρ€Π°Π½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ ΠΏΠΎ ΠΏΠΎΡΡ‚Ρ€ΠΎΠ΅Π½ΠΈΡŽ
SELECT
  (rv).*
FROM
  T
WHERE
  not_cross; -- Π±Π΅Ρ€Π΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ "Π½Π΅ΠΏΠ΅Ρ€Π΅ΡΠ΅ΠΊΠ°ΡŽΡ‰ΠΈΠ΅" записи

SQL HowTo: pagsulat sa usa ka while loop direkta sa pangutana, o "Elementary three-step"
[tan-awa sa explain.tensor.ru]

Sa ingon, kami gibaligya ang 50% sa datos nga gibasa alang sa 20% sa oras sa pagpatuman. Kana mao, kung adunay ka mga hinungdan sa pagtuo nga ang pagbasa mahimong magdugay (pananglitan, ang datos kanunay nga wala sa cache, ug kinahanglan nimo nga moadto sa disk alang niini), nan sa ingon niini nga paagi dili ka makasalig sa pagbasa. .

Sa bisan unsang kaso, ang oras sa pagpatay nahimo nga labi ka maayo kaysa sa "naive" nga una nga kapilian. Apan hain niining 3 ka mga opsyon ang imong gamiton.

Source: www.habr.com

Idugang sa usa ka comment