PostgreSQL Antipatterns: Β«Π‘Π΅ΡΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΡΡ‚ΡŒ β€” Π½Π΅ ΠΏΡ€Π΅Π΄Π΅Π»!Β», ΠΈΠ»ΠΈ НСмного ΠΎ рСкурсии

РСкурсия β€” ΠΎΡ‡Π΅Π½ΡŒ ΠΌΠΎΡ‰Π½Ρ‹ΠΉ ΠΈ ΡƒΠ΄ΠΎΠ±Π½Ρ‹ΠΉ ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌ, Ссли Π½Π°Π΄ связанными Π΄Π°Π½Π½Ρ‹ΠΌΠΈ Π΄Π΅Π»Π°ΡŽΡ‚ΡΡ ΠΎΠ΄Π½ΠΈ ΠΈ Ρ‚Π΅ ΠΆΠ΅ дСйствия Β«Π²Π³Π»ΡƒΠ±ΡŒΒ». Но нСконтролируСмая рСкурсия β€” Π·Π»ΠΎ, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ΡŒ ΠΈΠ»ΠΈ ΠΊ бСсконСчному Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΡŽ процСсса, ΠΈΠ»ΠΈ (Ρ‡Ρ‚ΠΎ случаСтся Ρ‡Π°Ρ‰Π΅) ΠΊ Β«Π²Ρ‹ΠΆΠΈΡ€Π°Π½ΠΈΡŽΒ» всСй доступной памяти.

PostgreSQL Antipatterns: «Π‘Π΅ΡΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΡΡ‚ΡŒ — Π½Π΅ ΠΏΡ€Π΅Π΄Π΅Π»!», ΠΈΠ»ΠΈ НСмного ΠΎ рСкурсии
Π‘Π£Π‘Π” Π² этом ΠΎΡ‚Π½ΠΎΡˆΠ΅Π½ΠΈΠΈ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚ ΠΏΠΎ Ρ‚Π΅ΠΌ ΠΆΠ΅ ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΠ°ΠΌ β€” "сказали ΠΊΠΎΠΏΠ°Ρ‚ΡŒ, я ΠΈ копаю". Π’Π°Ρˆ запрос ΠΌΠΎΠΆΠ΅Ρ‚ Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π·Π°Ρ‚ΠΎΡ€ΠΌΠΎΠ·ΠΈΡ‚ΡŒ сосСдниС процСссы, постоянно занимая рСсурсы процСссора, Π½ΠΎ ΠΈ Β«ΡƒΡ€ΠΎΠ½ΠΈΡ‚ΡŒΒ» всю Π±Π°Π·Ρƒ Ρ†Π΅Π»ΠΈΠΊΠΎΠΌ, «съСв» всю Π΄ΠΎΡΡ‚ΡƒΠΏΠ½ΡƒΡŽ ΠΏΠ°ΠΌΡΡ‚ΡŒ. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ Π·Π°Ρ‰ΠΈΡ‚Π° ΠΎΡ‚ бСсконСчной рСкурсии β€” ΠΎΠ±ΡΠ·Π°Π½Π½ΠΎΡΡ‚ΡŒ самого Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°.

Π’ PostgreSQL Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ рСкурсивныС запросы Ρ‡Π΅Ρ€Π΅Π· WITH RECURSIVE появилась Π΅Ρ‰Π΅ Π² нСзапамятныС Π²Ρ€Π΅ΠΌΠ΅Π½Π° вСрсии 8.4, Π½ΠΎ Π΄ΠΎ сих ΠΏΠΎΡ€ ΠΌΠΎΠΆΠ½ΠΎ рСгулярно Π²ΡΡ‚Ρ€Π΅Ρ‚ΠΈΡ‚ΡŒ ΠΏΠΎΡ‚Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ-уязвимыС Β«Π±Π΅Π·Π·Π°Ρ‰ΠΈΡ‚Π½Ρ‹Π΅Β» запросы. Как ΠΈΠ·Π±Π°Π²ΠΈΡ‚ΡŒ сСбя ΠΎΡ‚ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌ ΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠ³ΠΎ Ρ€ΠΎΠ΄Π°?

НС ΠΏΠΈΡΠ°Ρ‚ΡŒ рСкурсивныС запросы

А ΠΏΠΈΡΠ°Ρ‚ΡŒ нСрСкурсивныС. Π‘ ΡƒΠ²Π°ΠΆΠ΅Π½ΠΈΠ΅ΠΌ, Π’Π°Ρˆ К.О.

На самом Π΄Π΅Π»Π΅, PostgreSQL прСдоставляСт достаточно большоС количСство Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»Π°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ ΠΌΠΎΠΆΠ½ΠΎ Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π½Π΅ ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒ Ρ€Π΅ΠΊΡƒΡ€ΡΠΈΡŽ.

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΠΈΠ°Π»ΡŒΠ½ΠΎ Π΄Ρ€ΡƒΠ³ΠΎΠΉ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ ΠΊ Π·Π°Π΄Π°Ρ‡Π΅

Иногда ΠΌΠΎΠΆΠ½ΠΎ просто Π²Π·Π³Π»ΡΠ½ΡƒΡ‚ΡŒ Π½Π° Π·Π°Π΄Π°Ρ‡Ρƒ «с Π΄Ρ€ΡƒΠ³ΠΎΠΉ стороны». ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ‚Π°ΠΊΠΎΠΉ ситуации я ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΠ» Π² ΡΡ‚Π°Ρ‚ΡŒΠ΅ Β«SQL HowTo: 1000 ΠΈ ΠΎΠ΄ΠΈΠ½ способ Π°Π³Ρ€Π΅Π³Π°Ρ†ΠΈΠΈΒ» β€” ΠΏΠ΅Ρ€Π΅ΠΌΠ½ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π½Π°Π±ΠΎΡ€Π° чисСл Π±Π΅Π· примСнСния ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΡ… Π°Π³Ρ€Π΅Π³Π°Ρ‚Π½Ρ‹Ρ… Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ:

WITH RECURSIVE src AS (
  SELECT '{2,3,5,7,11,13,17,19}'::integer[] arr
)
, T(i, val) AS (
  SELECT
    1::bigint
  , 1
UNION ALL
  SELECT
    i + 1
  , val * arr[i]
  FROM
    T
  , src
  WHERE
    i <= array_length(arr, 1)
)
SELECT
  val
FROM
  T
ORDER BY -- ΠΎΡ‚Π±ΠΎΡ€ Ρ„ΠΈΠ½Π°Π»ΡŒΠ½ΠΎΠ³ΠΎ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π°
  i DESC
LIMIT 1;

Π’Π°ΠΊΠΎΠΉ запрос ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Π½Π° Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ ΠΎΡ‚ Π·Π½Π°Ρ‚ΠΎΠΊΠΎΠ² ΠΌΠ°Ρ‚Π΅ΠΌΠ°Ρ‚ΠΈΠΊΠΈ:

WITH src AS (
  SELECT unnest('{2,3,5,7,11,13,17,19}'::integer[]) prime
)
SELECT
  exp(sum(ln(prime)))::integer val
FROM
  src;

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ generate_series вмСсто Ρ†ΠΈΠΊΠ»ΠΎΠ²

Допустим, ΠΏΠ΅Ρ€Π΅Π΄ Π½Π°ΠΌΠΈ стоит Π·Π°Π΄Π°Ρ‡Π° ΡΠ³Π΅Π½Π΅Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ всС Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ прСфиксы для строки 'abcdefgh':

WITH RECURSIVE T AS (
  SELECT 'abcdefgh' str
UNION ALL
  SELECT
    substr(str, 1, length(str) - 1)
  FROM
    T
  WHERE
    length(str) > 1
)
TABLE T;

Π’ΠΎΡ‡Π½ΠΎ-Ρ‚ΠΎΡ‡Π½ΠΎ Ρ‚ΡƒΡ‚ Π½ΡƒΠΆΠ½Π° рСкурсия?.. Если Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ LATERAL ΠΈ generate_series, Ρ‚ΠΎ Π΄Π°ΠΆΠ΅ CTE Π½Π΅ понадобятся:

SELECT
  substr(str, 1, ln) str
FROM
  (VALUES('abcdefgh')) T(str)
, LATERAL(
    SELECT generate_series(length(str), 1, -1) ln
  ) X;

Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ структуру Π‘Π”

НапримСр, Ρƒ вас Π΅ΡΡ‚ΡŒ Ρ‚Π°Π±Π»ΠΈΡ†Π° сообщСний Ρ„ΠΎΡ€ΡƒΠΌΠ° со связями ΠΊΡ‚ΠΎ-ΠΊΠΎΠΌΡƒ ΠΎΡ‚Π²Π΅Ρ‚ΠΈΠ» ΠΈΠ»ΠΈ Ρ‚Ρ€Π΅Π΄ Π² ΡΠΎΡ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠΉ сСти:

CREATE TABLE message(
  message_id
    uuid
      PRIMARY KEY
, reply_to
    uuid
      REFERENCES message
, body
    text
);
CREATE INDEX ON message(reply_to);

PostgreSQL Antipatterns: «Π‘Π΅ΡΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΡΡ‚ΡŒ — Π½Π΅ ΠΏΡ€Π΅Π΄Π΅Π»!», ΠΈΠ»ΠΈ НСмного ΠΎ рСкурсии
Ну ΠΈ Ρ‚ΠΈΠΏΠΎΠ²ΠΎΠΉ запрос Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ всСх сообщСний ΠΏΠΎ ΠΎΠ΄Π½ΠΎΠΉ Ρ‚Π΅ΠΌΠ΅ выглядит ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ Ρ‚Π°ΠΊ:

WITH RECURSIVE T AS (
  SELECT
    *
  FROM
    message
  WHERE
    message_id = $1
UNION ALL
  SELECT
    m.*
  FROM
    T
  JOIN
    message m
      ON m.reply_to = T.message_id
)
TABLE T;

Но Ρ€Π°Π· Ρƒ нас всСгда Π½ΡƒΠΆΠ½Π° вся Ρ‚Π΅ΠΌΠ° ΠΎΡ‚ ΠΊΠΎΡ€Π½Π΅Π²ΠΎΠ³ΠΎ сообщСния, Ρ‚ΠΎ ΠΏΠΎΡ‡Π΅ΠΌΡƒ Π±Ρ‹ Π½Π°ΠΌ Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ Π΅Π³ΠΎ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ Π² ΠΊΠ°ΠΆΠ΄ΡƒΡŽ запись Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΎΠΌ?

-- Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΠΏΠΎΠ»Π΅ с ΠΎΠ±Ρ‰ΠΈΠΌ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ΠΎΠΌ Ρ‚Π΅ΠΌΡ‹ ΠΈ индСкс Π½Π° Π½Π΅Π³ΠΎ
ALTER TABLE message
  ADD COLUMN theme_id uuid;
CREATE INDEX ON message(theme_id);

-- ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ Ρ‚Π΅ΠΌΡ‹ Π² Ρ‚Ρ€ΠΈΠ³Π³Π΅Ρ€Π΅ ΠΏΡ€ΠΈ вставкС
CREATE OR REPLACE FUNCTION ins() RETURNS TRIGGER AS $$
BEGIN
  NEW.theme_id = CASE
    WHEN NEW.reply_to IS NULL THEN NEW.message_id -- Π±Π΅Ρ€Π΅ΠΌ ΠΈΠ· стартового события
    ELSE ( -- ΠΈΠ»ΠΈ ΠΈΠ· сообщСния, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ ΠΎΡ‚Π²Π΅Ρ‡Π°Π΅ΠΌ
      SELECT
        theme_id
      FROM
        message
      WHERE
        message_id = NEW.reply_to
    )
  END;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER ins BEFORE INSERT
  ON message
    FOR EACH ROW
      EXECUTE PROCEDURE ins();

PostgreSQL Antipatterns: «Π‘Π΅ΡΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΡΡ‚ΡŒ — Π½Π΅ ΠΏΡ€Π΅Π΄Π΅Π»!», ΠΈΠ»ΠΈ НСмного ΠΎ рСкурсии
Π’Π΅ΠΏΠ΅Ρ€ΡŒ вСсь наш рСкурсивный запрос ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ свСдСн всСго лишь ΠΊ Π²ΠΎΡ‚ Ρ‚Π°ΠΊΠΎΠΌΡƒ:

SELECT
  *
FROM
  message
WHERE
  theme_id = $1;

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΡ€ΠΈΠΊΠ»Π°Π΄Π½Ρ‹Π΅ Β«ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΡ‚Π΅Π»ΠΈΒ»

Если ΠΏΠΎΠΌΠ΅Π½ΡΡ‚ΡŒ структуру Π±Π°Π·Ρ‹ ΠΌΡ‹ Π½Π΅ Π² силах ΠΏΠΎ ΠΊΠ°ΠΊΠΈΠΌ-Ρ‚ΠΎ ΠΏΡ€ΠΈΡ‡ΠΈΠ½Π°ΠΌ, Π΄Π°Π²Π°ΠΉΡ‚Π΅ посмотрим, Π½Π° Ρ‡Ρ‚ΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΠΎΠΏΠ΅Ρ€Π΅Ρ‚ΡŒΡΡ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π΄Π°ΠΆΠ΅ Π½Π°Π»ΠΈΡ‡ΠΈΠ΅ ошибки Π² Π΄Π°Π½Π½Ρ‹Ρ… Π½Π΅ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΠ»ΠΎ ΠΊ бСсконСчному Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΡŽ рСкурсии.

Π‘Ρ‡Π΅Ρ‚Ρ‡ΠΈΠΊ Β«Π³Π»ΡƒΠ±ΠΈΠ½Ρ‹Β» рСкурсии

ΠŸΡ€ΠΎΡΡ‚ΠΎ ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅ΠΌ счСтчик Π½Π° Π΅Π΄ΠΈΠ½ΠΈΡ†Ρƒ Π½Π° ΠΊΠ°ΠΆΠ΄ΠΎΠΌ шагС рСкурсии Π²ΠΏΠ»ΠΎΡ‚ΡŒ Π΄ΠΎ ΠΌΠΎΠΌΠ΅Π½Ρ‚Π° достиТСния ΠΏΡ€Π΅Π΄Π΅Π»Π°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΡ‹ считаСм Π·Π°Π²Π΅Π΄ΠΎΠΌΠΎ Π½Π΅Π°Π΄Π΅ΠΊΠ²Π°Ρ‚Π½Ρ‹ΠΌ:

WITH RECURSIVE T AS (
  SELECT
    0 i
  ...
UNION ALL
  SELECT
    i + 1
  ...
  WHERE
    T.i < 64 -- ΠΏΡ€Π΅Π΄Π΅Π»
)

Pro: ΠŸΡ€ΠΈ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ΅ зацикливания ΠΌΡ‹ всС Ρ€Π°Π²Π½ΠΎ сдСлаСм Π½Π΅ Π±ΠΎΠ»Π΅Π΅ ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠ³ΠΎ Π»ΠΈΠΌΠΈΡ‚Π° ΠΈΡ‚Π΅Ρ€Π°Ρ†ΠΈΠΉ Β«Π²Π³Π»ΡƒΠ±ΡŒΒ».
Contra: НСт Π³Π°Ρ€Π°Π½Ρ‚ΠΈΠΈ, Ρ‡Ρ‚ΠΎ ΠΌΡ‹ Π½Π΅ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π΅ΠΌ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΠΎ ΠΎΠ΄Π½Ρƒ ΠΈ Ρ‚Ρƒ ΠΆΠ΅ запись β€” Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Π½Π° Π³Π»ΡƒΠ±ΠΈΠ½Π΅ 15 ΠΈ 25, Π½Ρƒ ΠΈ дальшС Π±ΡƒΠ΄Π΅Ρ‚ ΠΊΠ°ΠΆΠ΄Ρ‹Π΅ +10. Π”Π° ΠΈ ΠΏΡ€ΠΎ Β«Π²ΡˆΠΈΡ€ΡŒΒ» Π½ΠΈΠΊΡ‚ΠΎ Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ ΠΎΠ±Π΅Ρ‰Π°Π».

Π€ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½ΠΎ, такая рСкурсия Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ бСсконСчной, Π½ΠΎ Ссли Π½Π° ΠΊΠ°ΠΆΠ΄ΠΎΠΌ шагС количСство записСй увСличиваСтся ΠΏΠΎ экспонСнтС, ΠΌΡ‹ всС Ρ…ΠΎΡ€ΠΎΡˆΠΎ Π·Π½Π°Π΅ΠΌ, Ρ‡Π΅ΠΌ это кончаСтся…

PostgreSQL Antipatterns: «Π‘Π΅ΡΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΡΡ‚ΡŒ — Π½Π΅ ΠΏΡ€Π΅Π΄Π΅Π»!», ΠΈΠ»ΠΈ НСмного ΠΎ рСкурсиисм. Β«Π—Π°Π΄Π°Ρ‡Π° ΠΎ Π·Ρ‘Ρ€Π½Π°Ρ… Π½Π° ΡˆΠ°Ρ…ΠΌΠ°Ρ‚Π½ΠΎΠΉ доскС»

Π₯Ρ€Π°Π½ΠΈΡ‚Π΅Π»ΡŒ Β«ΠΏΡƒΡ‚ΠΈΒ»

ΠŸΠΎΠΎΡ‡Π΅Ρ€Π΅Π΄Π½ΠΎ дописываСм всС Π²ΡΡ‚Ρ€Π΅Ρ‚ΠΈΠ²ΡˆΠΈΠ΅ΡΡ Π½Π°ΠΌ ΠΏΠΎ ΠΏΡƒΡ‚ΠΈ рСкурсии ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² Π² массив, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ являСтся ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΌ Β«ΠΏΡƒΡ‚Π΅ΠΌΒ» Π΄ΠΎ Π½Π΅Π³ΠΎ:

WITH RECURSIVE T AS (
  SELECT
    ARRAY[id] path
  ...
UNION ALL
  SELECT
    path || id
  ...
  WHERE
    id <> ALL(T.path) -- Π½Π΅ совпадаСт Π½ΠΈ с ΠΎΠ΄Π½ΠΈΠΌ ΠΈΠ·
)

Pro: ΠŸΡ€ΠΈ Π½Π°Π»ΠΈΡ‡ΠΈΠΈ Ρ†ΠΈΠΊΠ»Π° Π² Π΄Π°Π½Π½Ρ‹Ρ… ΠΌΡ‹ Π°Π±ΡΠΎΠ»ΡŽΡ‚Π½ΠΎ Ρ‚ΠΎΡ‡Π½ΠΎ Π½Π΅ станСм ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΠΎ ΠΎΠ΄Π½Ρƒ ΠΈ Ρ‚Ρƒ ΠΆΠ΅ запись Π² Ρ€Π°ΠΌΠΊΠ°Ρ… ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΏΡƒΡ‚ΠΈ.
Contra: Но ΠΏΡ€ΠΈ этом ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ ΠΎΠ±ΠΎΠΉΡ‚ΠΈ, Π±ΡƒΠΊΠ²Π°Π»ΡŒΠ½ΠΎ, всС записи, Ρ‚Π°ΠΊ ΠΈ Π½Π΅ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΈΠ²ΡˆΠΈΡΡŒ.

PostgreSQL Antipatterns: «Π‘Π΅ΡΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΡΡ‚ΡŒ — Π½Π΅ ΠΏΡ€Π΅Π΄Π΅Π»!», ΠΈΠ»ΠΈ НСмного ΠΎ рСкурсиисм. Β«Π—Π°Π΄Π°Ρ‡Π° ΠΎ Ρ…ΠΎΠ΄Π΅ коня»

ΠžΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½ΠΈΠ΅ Π΄Π»ΠΈΠ½Ρ‹ ΠΏΡƒΡ‚ΠΈ

Π§Ρ‚ΠΎΠ±Ρ‹ ΠΈΠ·Π±Π΅ΠΆΠ°Ρ‚ΡŒ ситуации «блуТдания» рСкурсии Π½Π° нСпонятной Π³Π»ΡƒΠ±ΠΈΠ½Π΅, ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠΊΠΎΠΌΠ±ΠΈΠ½ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π΄Π²Π° ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΡ… ΠΌΠ΅Ρ‚ΠΎΠ΄Π°. Или, Ссли Π½Π΅ Ρ…ΠΎΡ‚ΠΈΠΌ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Ρ‚ΡŒ Π»ΠΈΡˆΠ½ΠΈΡ… ΠΏΠΎΠ»Π΅ΠΉ, Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ условиС продолТСния рСкурсии ΠΎΡ†Π΅Π½ΠΊΠΎΠΉ Π΄Π»ΠΈΠ½Ρ‹ ΠΏΡƒΡ‚ΠΈ:

WITH RECURSIVE T AS (
  SELECT
    ARRAY[id] path
  ...
UNION ALL
  SELECT
    path || id
  ...
  WHERE
    id <> ALL(T.path) AND
    array_length(T.path, 1) < 10
)

Π’Ρ‹Π±ΠΈΡ€Π°ΠΉΡ‚Π΅ способ Π½Π° свой вкус!

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

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