SQL HowTo: пишСм while-Ρ†ΠΈΠΊΠ» прямо Π² запросС, ΠΈΠ»ΠΈ «ЭлСмСнтарная Ρ‚Ρ€Π΅Ρ…Ρ…ΠΎΠ΄ΠΎΠ²ΠΊΠ°Β»

ΠŸΠ΅Ρ€ΠΈΠΎΠ΄ΠΈΡ‡Π΅ΡΠΊΠΈ Π²ΠΎΠ·Π½ΠΈΠΊΠ°Π΅Ρ‚ Π·Π°Π΄Π°Ρ‡Π° поиска связанных Π΄Π°Π½Π½Ρ‹Ρ… ΠΏΠΎ Π½Π°Π±ΠΎΡ€Ρƒ ΠΊΠ»ΡŽΡ‡Π΅ΠΉ, ΠΏΠΎΠΊΠ° Π½Π΅ Π½Π°Π±Π΅Ρ€Π΅ΠΌ Π½ΡƒΠΆΠ½ΠΎΠ΅ суммарноС количСство записСй.

НаиболСС Β«ΠΆΠΈΠ·Π½Π΅Π½Π½Ρ‹ΠΉΒ» ΠΏΡ€ΠΈΠΌΠ΅Ρ€ β€” вывСсти 20 самых старых Π·Π°Π΄Π°Ρ‡, числящихся Π½Π° спискС сотрудников (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Π² Ρ€Π°ΠΌΠΊΠ°Ρ… ΠΎΠ΄Π½ΠΎΠ³ΠΎ подраздСлСния). Для Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… управлСнчСских Β«Π΄Π°ΡˆΠ±ΠΎΡ€Π΄ΠΎΠ²Β» с ΠΊΡ€Π°Ρ‚ΠΊΠΈΠΌΠΈ Π²Ρ‹ΠΆΠΈΠΌΠΊΠ°ΠΌΠΈ ΠΏΠΎ участкам Ρ€Π°Π±ΠΎΡ‚Ρ‹ похоТая Ρ‚Π΅ΠΌΠ° трСбуСтся достаточно часто.

SQL HowTo: пишСм while-Ρ†ΠΈΠΊΠ» прямо Π² запросС, ΠΈΠ»ΠΈ «Π­Π»Π΅ΠΌΠ΅Π½Ρ‚арная Ρ‚Ρ€Π΅Ρ…Ρ…ΠΎΠ΄ΠΎΠ²ΠΊΠ°»

Π’ ΡΡ‚Π°Ρ‚ΡŒΠ΅ рассмотрим Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ Π½Π° PostgreSQL Β«Π½Π°ΠΈΠ²Π½ΠΎΠ³ΠΎΒ» Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π° Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ Ρ‚Π°ΠΊΠΎΠΉ Π·Π°Π΄Π°Ρ‡ΠΈ, Β«ΠΏΠΎΡƒΠΌΠ½Π΅Π΅Β» ΠΈ совсСм слоТный Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌ Β«Ρ†ΠΈΠΊΠ»Π°Β» Π½Π° SQL с условиСм Π²Ρ‹Ρ…ΠΎΠ΄Π° ΠΎΡ‚ Π½Π°ΠΉΠ΄Π΅Π½Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ…, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ ΠΏΠΎΠ»Π΅Π·Π΅Π½ ΠΊΠ°ΠΊ для ΠΎΠ±Ρ‰Π΅Π³ΠΎ развития, Ρ‚Π°ΠΊ ΠΈ для примСнСния Π² Π΄Ρ€ΡƒΠ³ΠΈΡ… ΠΏΠΎΡ…ΠΎΠΆΠΈΡ… случаях.

Π’ΠΎΠ·ΡŒΠΌΠ΅ΠΌ тСстовый Π½Π°Π±ΠΎΡ€ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΠ· ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅ΠΉ ΡΡ‚Π°Ρ‚ΡŒΠΈ. Π§Ρ‚ΠΎΠ±Ρ‹ Π²Ρ‹Π²ΠΎΠ΄ΠΈΠΌΡ‹Π΅ записи Π½Π΅ «скакали» ΠΎΡ‚ Ρ€Π°Π·Π° ΠΊ Ρ€Π°Π·Ρƒ ΠΏΡ€ΠΈ совпадСнии сортируСмых Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ, Ρ€Π°ΡΡˆΠΈΡ€ΠΈΠΌ ΠΏΡ€Π΅Π΄ΠΌΠ΅Ρ‚Π½Ρ‹ΠΉ индСкс Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ΠΌ ΠΏΠ΅Ρ€Π²ΠΈΡ‡Π½ΠΎΠ³ΠΎ ΠΊΠ»ΡŽΡ‡Π°. Π—Π°ΠΎΠ΄Π½ΠΎ это сразу придаст Π΅ΠΌΡƒ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ, ΠΈ Π³Π°Ρ€Π°Π½Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚ Π½Π°ΠΌ ΠΎΠ΄Π½ΠΎΠ·Π½Π°Ρ‡Π½ΠΎΡΡ‚ΡŒ порядка сортировки:

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

Как ΡΠ»Ρ‹ΡˆΠΈΡ‚ΡΡ, Ρ‚Π°ΠΊ ΠΈ ΠΏΠΈΡˆΠ΅Ρ‚ΡΡ

Π‘Π½Π°Ρ‡Π°Π»Π° набросаСм самый простой Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ запроса, пСрСдавая ID исполнитСлСй массивом Π² качСствС Π²Ρ…ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°:

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: пишСм while-Ρ†ΠΈΠΊΠ» прямо Π² запросС, ΠΈΠ»ΠΈ «Π­Π»Π΅ΠΌΠ΅Π½Ρ‚арная Ρ‚Ρ€Π΅Ρ…Ρ…ΠΎΠ΄ΠΎΠ²ΠΊΠ°»
[ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π½Π° explain.tensor.ru]

НСмного грустно β€” ΠΌΡ‹ Π·Π°ΠΊΠ°Π·Ρ‹Π²Π°Π»ΠΈ всСго 20 записСй, Π° Index Scan Π²Π΅Ρ€Π½ΡƒΠ» Π½Π°ΠΌ 960 строк, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΏΠΎΡ‚ΠΎΠΌ Π΅Ρ‰Π΅ ΠΈ ΡΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΡ€ΠΈΡˆΠ»ΠΎΡΡŒβ€¦ А Π΄Π°Π²Π°ΠΉΡ‚Π΅ ΠΏΠΎΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ помСньшС.

unnest + ARRAY

ΠŸΠ΅Ρ€Π²ΠΎΠ΅ сообраТСниС, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Π½Π°ΠΌ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ β€” Ссли Π½Π°ΠΌ Π½Π°Π΄ΠΎ всСго 20 отсортированных записСй, Ρ‚ΠΎ достаточно Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ Π½Π΅ Π±ΠΎΠ»Π΅Π΅ 20 отсортированных Π² Ρ‚ΠΎΠΌ ΠΆΠ΅ порядкС ΠΏΠΎ ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡƒ ΠΊΠ»ΡŽΡ‡Ρƒ. Π‘Π»Π°Π³ΠΎ, подходящий индСкс (owner_id, task_date, id) Ρƒ нас Π΅ΡΡ‚ΡŒ.

Π’ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡΡ Ρ‚Π΅ΠΌ ΠΆΠ΅ ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌΠΎΠΌ извлСчСния ΠΈ Β«Ρ€Π°Π·Π²ΠΎΡ€ΠΎΡ‚Π° Π² столбцы» цСлостной записи Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹, Ρ‡Ρ‚ΠΎ ΠΈ Π² ΠΏΡ€ΠΎΡˆΠ»ΠΎΠΉ ΡΡ‚Π°Ρ‚ΡŒΠ΅. А Ρ‚Π°ΠΊΠΆΠ΅ ΠΏΡ€ΠΈΠΌΠ΅Π½ΠΈΠΌ свСртку Π² массив с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ 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: пишСм while-Ρ†ΠΈΠΊΠ» прямо Π² запросС, ΠΈΠ»ΠΈ «Π­Π»Π΅ΠΌΠ΅Π½Ρ‚арная Ρ‚Ρ€Π΅Ρ…Ρ…ΠΎΠ΄ΠΎΠ²ΠΊΠ°»
[ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π½Π° explain.tensor.ru]

О, ΡƒΠΆΠ΅ Π½Π°ΠΌΠ½ΠΎΠ³ΠΎ Π»ΡƒΡ‡ΡˆΠ΅! На 40% быстрСС, ΠΈ Π² 4.5 Ρ€Π°Π·Π° мСньшС Π΄Π°Π½Π½Ρ‹Ρ… ΠΏΡ€ΠΈΡˆΠ»ΠΎΡΡŒ Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ.

ΠœΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ записСй Ρ‚Π°Π±Π»ΠΈΡ† Ρ‡Π΅Ρ€Π΅Π· CTEΠžΠ±Ρ€Π°Ρ‰Ρƒ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, Ρ‡Ρ‚ΠΎ Π² Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… случаях ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° сразу Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с полями записи послС Π΅Π΅ поиска Π² подзапросС, Π±Π΅Π· «оборачивания» Π² CTE ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ΡŒ ΠΊ Β«ΡƒΠΌΠ½ΠΎΠΆΠ΅Π½ΠΈΡŽΒ» InitPlan ΠΏΡ€ΠΎΠΏΠΎΡ€Ρ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ количСству этих самых ΠΏΠΎΠ»Π΅ΠΉ:

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

Одна ΠΈ Ρ‚Π° ΠΆΠ΅ запись «поискалась» 4 раза… Π’ΠΏΠ»ΠΎΡ‚ΡŒ Π΄ΠΎ PostgreSQL 11 Ρ‚Π°ΠΊΠΎΠ΅ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ встрСчаСтся рСгулярно, ΠΈ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ΠΌ являСтся Β«ΠΎΠ±ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Π½ΠΈΠ΅Β» Π² CTE, Ρ‡Ρ‚ΠΎ являСтся бСзусловной Π³Ρ€Π°Π½ΠΈΡ†Π΅ΠΉ для ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ‚ΠΎΡ€Π° Π² этих вСрсиях.

РСкурсивный аккумулятор

Π’ ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅ΠΌ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π΅ суммарно ΠΌΡ‹ ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Π»ΠΈ 200 строк Ρ€Π°Π΄ΠΈ Π½ΡƒΠΆΠ½Ρ‹Ρ… 20. Π£ΠΆΠ΅ Π½Π΅ 960, Π½ΠΎ Π΅Ρ‰Π΅ мСньшС β€” ΠΌΠΎΠΆΠ½ΠΎ?

Π”Π°Π²Π°ΠΉΡ‚Π΅ ΠΏΠΎΠΏΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ Π·Π½Π°Π½ΠΈΠ΅ΠΌ, Ρ‡Ρ‚ΠΎ Π½Π°ΠΌ Π½Π°Π΄ΠΎ всСго 20 записСй. Π’ΠΎ Π΅ΡΡ‚ΡŒ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡ‚Π΅Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π²Ρ‹Ρ‡ΠΈΡ‚ΠΊΡƒ Π΄Π°Π½Π½Ρ‹Ρ… Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π΄ΠΎ достиТСния Π½ΡƒΠΆΠ½ΠΎΠ³ΠΎ Π½Π°ΠΌ количСства.

Π¨Π°Π³ 1: стартовый список

ΠžΡ‡Π΅Π²ΠΈΠ΄Π½ΠΎ, Ρ‡Ρ‚ΠΎ наш Β«Ρ†Π΅Π»Π΅Π²ΠΎΠΉΒ» список ΠΈΠ· 20 записСй Π΄ΠΎΠ»ΠΆΠ΅Π½ Π½Π°Ρ‡ΠΈΠ½Π°Ρ‚ΡŒΡΡ с Β«ΠΏΠ΅Ρ€Π²Ρ‹Ρ…Β» записСй ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΌΡƒ ΠΈΠ· Π½Π°ΡˆΠΈΡ… owner_id-ΠΊΠ»ΡŽΡ‡Π΅ΠΉ. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ сначала Π½Π°ΠΉΠ΄Π΅ΠΌ Ρ‚Π°ΠΊΠΈΠ΅ «самыС ΠΏΠ΅Ρ€Π²Ρ‹Π΅Β» ΠΏΠΎ ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡƒ ΠΈΠ· ΠΊΠ»ΡŽΡ‡Π΅ΠΉ ΠΈ занСсСм Π² список, отсортировав Π΅Π³ΠΎ Π² порядкС, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Ρ…ΠΎΡ‚ΠΈΠΌ β€” (task_date, id).

SQL HowTo: пишСм while-Ρ†ΠΈΠΊΠ» прямо Π² запросС, ΠΈΠ»ΠΈ «Π­Π»Π΅ΠΌΠ΅Π½Ρ‚арная Ρ‚Ρ€Π΅Ρ…Ρ…ΠΎΠ΄ΠΎΠ²ΠΊΠ°»

Π¨Π°Π³ 2: Π½Π°Ρ…ΠΎΠ΄ΠΈΠΌ Β«ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅Β» записи

Π’Π΅ΠΏΠ΅Ρ€ΡŒ, Ссли ΠΌΡ‹ возьмСм ΠΈΠ· нашСго списка ΠΏΠ΅Ρ€Π²ΡƒΡŽ запись ΠΈ Π½Π°Ρ‡Π½Π΅ΠΌ Β«ΡˆΠ°Π³Π°Ρ‚ΡŒΒ» дальшС ΠΏΠΎ индСксу с сохранСниСм owner_id-ΠΊΠ»ΡŽΡ‡Π°, Ρ‚ΠΎ всС Π½Π°ΠΉΠ΄Π΅Π½Π½Ρ‹Π΅ записи β€” ΠΊΠ°ΠΊ Ρ€Π°Π· ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ Π² Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚ΠΈΡ€ΡƒΡŽΡ‰Π΅ΠΉ Π²Ρ‹Π±ΠΎΡ€ΠΊΠ΅. ΠšΠΎΠ½Π΅Ρ‡Π½ΠΎ, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎΠΊΠ° ΠΌΡ‹ Π½Π΅ пСрСсСчСм ΠΏΡ€ΠΈΠΊΠ»Π°Π΄Π½ΠΎΠΉ ΠΊΠ»ΡŽΡ‡ Π²Ρ‚ΠΎΡ€ΠΎΠΉ записи Π² спискС.

Если ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ»ΠΎΡΡŒ, Ρ‡Ρ‚ΠΎ ΠΌΡ‹ Π²Ρ‚ΠΎΡ€ΡƒΡŽ запись «пСрСсСкли», Ρ‚ΠΎ послСдняя прочитанная запись Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π° Π² список вмСсто ΠΏΠ΅Ρ€Π²ΠΎΠΉ (с Ρ‚Π΅ΠΌ ΠΆΠ΅ owner_id), послС Ρ‡Π΅Π³ΠΎ список снова пСрСсортировываСм.

SQL HowTo: пишСм while-Ρ†ΠΈΠΊΠ» прямо Π² запросС, ΠΈΠ»ΠΈ «Π­Π»Π΅ΠΌΠ΅Π½Ρ‚арная Ρ‚Ρ€Π΅Ρ…Ρ…ΠΎΠ΄ΠΎΠ²ΠΊΠ°»

Π’ΠΎ Π΅ΡΡ‚ΡŒ Ρƒ нас всС врСмя получаСтся, Ρ‡Ρ‚ΠΎ Π² спискС Π΅ΡΡ‚ΡŒ Π½Π΅ Π±ΠΎΠ»Π΅Π΅ ΠΎΠ΄Π½ΠΎΠΉ записи ΠΏΠΎ ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡƒ ΠΈΠ· ΠΊΠ»ΡŽΡ‡Π΅ΠΉ (Ссли записи ΠΊΠΎΠ½Ρ‡ΠΈΠ»ΠΈΡΡŒ, Π° ΠΌΡ‹ Π½Π΅ «пСрСсСкли», Ρ‚ΠΎ ΠΈΠ· списка пСрвая запись просто ΠΏΡ€ΠΎΠΏΠ°Π΄Π΅Ρ‚ ΠΈ Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ добавится), ΠΏΡ€ΠΈΡ‡Π΅ΠΌ ΠΎΠ½ΠΈ всСгда отсортированы Π² порядкС возрастания ΠΏΡ€ΠΈΠΊΠ»Π°Π΄Π½ΠΎΠ³ΠΎ ΠΊΠ»ΡŽΡ‡Π° (task_date, id).

SQL HowTo: пишСм while-Ρ†ΠΈΠΊΠ» прямо Π² запросС, ΠΈΠ»ΠΈ «Π­Π»Π΅ΠΌΠ΅Π½Ρ‚арная Ρ‚Ρ€Π΅Ρ…Ρ…ΠΎΠ΄ΠΎΠ²ΠΊΠ°»

Π¨Π°Π³ 3: Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΡƒΠ΅ΠΌ ΠΈ Β«Ρ€Π°Π·Π²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Π΅ΠΌΒ» записи

Π’ части строк нашСй рСкурсивной Π²Ρ‹Π±ΠΎΡ€ΠΊΠΈ Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ записи rv Π΄ΡƒΠ±Π»ΠΈΡ€ΡƒΡŽΡ‚ΡΡ β€” сначала ΠΌΡ‹ Π½Π°Ρ…ΠΎΠ΄ΠΈΠΌ Ρ‚Π°ΠΊΠΈΠ΅ ΠΊΠ°ΠΊ Β«ΠΏΠ΅Ρ€Π΅ΡΠ΅ΠΊΠ°ΡŽΡ‰ΡƒΡŽ Π³Ρ€Π°Π½ΠΈΡ†Ρƒ 2-ΠΉ записи списка», Π° ΠΏΠΎΡ‚ΠΎΠΌ подставляСм ΠΊΠ°ΠΊ 1-ю ΠΈΠ· списка. Π’Π°ΠΊ Π²ΠΎΡ‚ ΠΏΠ΅Ρ€Π²ΠΎΠ΅ появлСниС Π½Π°Π΄ΠΎ ΠΎΡ‚Ρ„ΠΈΠ»ΡŒΡ‚Ρ€ΠΎΠ²Π°Ρ‚ΡŒ.

Π‘Ρ‚Ρ€Π°ΡˆΠ½Ρ‹ΠΉ ΠΈΡ‚ΠΎΠ³ΠΎΠ²Ρ‹ΠΉ запрос

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: пишСм while-Ρ†ΠΈΠΊΠ» прямо Π² запросС, ΠΈΠ»ΠΈ «Π­Π»Π΅ΠΌΠ΅Π½Ρ‚арная Ρ‚Ρ€Π΅Ρ…Ρ…ΠΎΠ΄ΠΎΠ²ΠΊΠ°»
[ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Π½Π° explain.tensor.ru]

Π’Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ, ΠΌΡ‹ обмСняли 50% Ρ‡Ρ‚Π΅Π½ΠΈΠΉ Π΄Π°Π½Π½Ρ‹Ρ… Π½Π° 20% Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ выполнСния. Π’ΠΎ Π΅ΡΡ‚ΡŒ Ссли Ρƒ вас Π΅ΡΡ‚ΡŒ ΠΏΡ€ΠΈΡ‡ΠΈΠ½Ρ‹ ΠΏΠΎΠ»Π°Π³Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ Ρ‡Ρ‚Π΅Π½ΠΈΠ΅ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Π΄ΠΎΠ»Π³ΠΈΠΌ (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Π΄Π°Π½Π½Ρ‹Π΅ Π·Π°Ρ‡Π°ΡΡ‚ΡƒΡŽ Π½Π΅ Π² кэшС, ΠΈ приходится Π·Π° Π½ΠΈΠΌΠΈ Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒ Π½Π° диск), Ρ‚ΠΎ Ρ‚Π°ΠΊΠΈΠΌ способом ΠΌΠΎΠΆΠ½ΠΎ Π·Π°Π²ΠΈΡΠ΅Ρ‚ΡŒ ΠΎΡ‚ чтСния мСньшС.

Π’ любом случаС, врСмя выполнСния ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ»ΠΎΡΡŒ Π»ΡƒΡ‡ΡˆΠ΅, Ρ‡Π΅ΠΌ Π² Β«Π½Π°ΠΈΠ²Π½ΠΎΠΌΒ» ΠΏΠ΅Ρ€Π²ΠΎΠΌ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π΅. Но ΠΊΠ°ΠΊΠΈΠΌ ΠΈΠ· этих 3 Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ² ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ β€” Π²Ρ‹Π±ΠΈΡ€Π°Ρ‚ΡŒ Π²Π°ΠΌ.

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com

Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ