PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž

๋ณต์žกํ•œ ERP ์‹œ์Šคํ…œ์—์„œ ๋งŽ์€ ์—”ํ„ฐํ‹ฐ๋Š” ๊ณ„์ธต์  ์„ฑ๊ฒฉ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.๊ฐ™์€ ์ข…๋ฅ˜์˜ ๋ฌผ์ฒด๊ฐ€ ์ค„์ง€์–ด ๋Š˜์–ด์„œ ์žˆ์„ ๋•Œ ์กฐ์ƒ-ํ›„์† ๊ด€๊ณ„์˜ ๋‚˜๋ฌด - ์ด๊ฒƒ์€ ๊ธฐ์—…์˜ ์กฐ์ง ๊ตฌ์กฐ(๋ชจ๋“  ์ง€์ , ๋ถ€์„œ ๋ฐ ์ž‘์—… ๊ทธ๋ฃน), ์ƒํ’ˆ ์นดํƒˆ๋กœ๊ทธ, ์ž‘์—… ์˜์—ญ, ํŒ๋งค ์ง€์ ์˜ ์ง€๋ฆฌ์ž…๋‹ˆ๋‹ค...

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž

์‚ฌ์‹ค์€ ํ•˜๋‚˜๋„ ์—†์–ด์š” ๋น„์ฆˆ๋‹ˆ์Šค ์ž๋™ํ™” ์˜์—ญ, ๊ฒฐ๊ณผ์ ์œผ๋กœ ๊ณ„์ธต ๊ตฌ์กฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ "๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ์œ„ํ•ด" ์ผํ•˜์ง€ ์•Š๋”๋ผ๋„ ์—ฌ์ „ํžˆ ๊ณ„์ธต์  ๊ด€๊ณ„๋ฅผ ์‰ฝ๊ฒŒ ์ ‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง„๋ถ€ํ•œ ์ผ์ž…๋‹ˆ๋‹ค. ๊ฐ€๊ณ„๋„๋‚˜ ์‡ผํ•‘ ์„ผํ„ฐ ๊ฑด๋ฌผ์˜ ํ‰๋ฉด๋„๋„ ๋™์ผํ•œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ํŠธ๋ฆฌ๋ฅผ DBMS์— ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์ง€๋งŒ ์˜ค๋Š˜์€ ํ•œ ๊ฐ€์ง€ ์˜ต์…˜์—๋งŒ ์ค‘์ ์„ ๋‘˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

CREATE TABLE hier(
  id
    integer
      PRIMARY KEY
, pid
    integer
      REFERENCES hier
, data
    json
);

CREATE INDEX ON hier(pid); -- ะฝะต ะทะฐะฑั‹ะฒะฐะตะผ, ั‡ั‚ะพ FK ะฝะต ะฟะพะดั€ะฐะทัƒะผะตะฒะฐะตั‚ ะฐะฒั‚ะพัะพะทะดะฐะฝะธะต ะธะฝะดะตะบัะฐ, ะฒ ะพั‚ะปะธั‡ะธะต ะพั‚ PK

๊ทธ๋ฆฌ๊ณ  ๋‹น์‹ ์ด ๊ณ„์ธต ๊ตฌ์กฐ์˜ ๊นŠ์ด๋ฅผ ๋“ค์—ฌ๋‹ค๋ณด๋Š” ๋™์•ˆ, ๊ทธ๋Ÿฌํ•œ ๊ตฌ์กฐ๋กœ ์ž‘์—…ํ•˜๋Š” "์ˆœ์ง„ํ•œ" ๋ฐฉ์‹์ด ์–ผ๋งˆ๋‚˜ ํšจ๊ณผ์ ์ด์ง€ ์•Š์„์ง€ ์ธ๋‚ด์‹ฌ์„ ๊ฐ–๊ณ  ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž
๋ฐœ์ƒํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ์™€ SQL์—์„œ์˜ ๊ตฌํ˜„์„ ์‚ดํŽด๋ณด๊ณ  ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

#1. ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”?

๋ช…ํ™•์„ฑ์„ ์œ„ํ•ด ์ด ๊ตฌ์กฐ๋Š” ๋ถ€์„œ, ์‚ฌ์—…๋ถ€, ๋ถ€๋ฌธ, ์ง€์ , ์ž‘์—… ๊ทธ๋ฃน ๋“ฑ ์กฐ์ง ๊ตฌ์กฐ์—์„œ ๋ถ€์„œ์˜ ์ข…์†์„ฑ์„ ๋ฐ˜์˜ํ•œ๋‹ค๋Š” ์ ์„ ์ธ์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž

๋จผ์ € 10K ์š”์†Œ๋กœ ๊ตฌ์„ฑ๋œ 'ํŠธ๋ฆฌ'๋ฅผ ์ƒ์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

INSERT INTO hier
WITH RECURSIVE T AS (
  SELECT
    1::integer id
  , '{1}'::integer[] pids
UNION ALL
  SELECT
    id + 1
  , pids[1:(random() * array_length(pids, 1))::integer] || (id + 1)
  FROM
    T
  WHERE
    id < 10000
)
SELECT
  pids[array_length(pids, 1)] id
, pids[array_length(pids, 1) - 1] pid
FROM
  T;

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์ž‘์—…๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ํŠน์ • ๋ถ€๋ฌธ ๋‚ด์—์„œ ๋˜๋Š” ๊ณ„์ธต ๊ตฌ์กฐ ์ธก๋ฉด์—์„œ ์ผํ•˜๋Š” ๋ชจ๋“  ์ง์›์„ ์ฐพ์•„๋ณด์„ธ์š”. ๋…ธ๋“œ์˜ ๋ชจ๋“  ์ž์‹ ์ฐพ๊ธฐ. ์ž์†์˜ "๊นŠ์ด"๋ฅผ ์–ป๋Š” ๊ฒƒ๋„ ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค... ์˜ˆ๋ฅผ ๋“ค์–ด ์ผ์ข…์˜ ๊ฒƒ์„ ๊ตฌ์ถ•ํ•˜๋ ค๋ฉด ์ด ๋ชจ๋“  ๊ฒƒ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ์ง์›์˜ ID ๋ชฉ๋ก์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ๋ณต์žกํ•œ ์„ ํƒ.

์ด๋Ÿฌํ•œ ํ›„์†์˜ ๋ ˆ๋ฒจ์ด ๋ช‡ ๊ฐœ๋งŒ ์žˆ๊ณ  ๊ทธ ์ˆ˜๊ฐ€ ์‹ญ์—ฌ ๊ฐœ ์ด๋‚ด๋ผ๋ฉด ๋ชจ๋“  ๊ฒƒ์ด ๊ดœ์ฐฎ๊ฒ ์ง€๋งŒ, ๋ ˆ๋ฒจ์ด 5๊ฐœ๊ฐ€ ๋„˜๊ณ  ์ด๋ฏธ ์ž์†์ด ์ˆ˜์‹ญ ๊ฐœ๋ผ๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „ํ†ต์ ์ธ ํ•˜์œ„ ํŠธ๋ฆฌ ๊ฒ€์ƒ‰ ์˜ต์…˜์ด ์–ด๋–ป๊ฒŒ ์ž‘์„ฑ๋˜๊ณ  ์ž‘๋™ํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋จผ์ € ์šฐ๋ฆฌ ์—ฐ๊ตฌ์— ๊ฐ€์žฅ ํฅ๋ฏธ๋กœ์šด ๋…ธ๋“œ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๊ฒฐ์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฐ€์žฅ ์ข‹์€ "๊นŠ์€" ํ•˜์œ„ ํŠธ๋ฆฌ:

WITH RECURSIVE T AS (
  SELECT
    id
  , pid
  , ARRAY[id] path
  FROM
    hier
  WHERE
    pid IS NULL
UNION ALL
  SELECT
    hier.id
  , hier.pid
  , T.path || hier.id
  FROM
    T
  JOIN
    hier
      ON hier.pid = T.id
)
TABLE T ORDER BY array_length(path, 1) DESC;

 id  | pid  | path
---------------------------------------------
7624 | 7623 | {7615,7620,7621,7622,7623,7624}
4995 | 4994 | {4983,4985,4988,4993,4994,4995}
4991 | 4990 | {4983,4985,4988,4989,4990,4991}
...

๊ฐ€์žฅ ์ข‹์€ "๋„“์€" ํ•˜์œ„ ํŠธ๋ฆฌ:

...
SELECT
  path[1] id
, count(*)
FROM
  T
GROUP BY
  1
ORDER BY
  2 DESC;

id   | count
------------
5300 |   30
 450 |   28
1239 |   27
1573 |   25

์ด๋Ÿฌํ•œ ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์šฐ๋ฆฌ๋Š” ์ผ๋ฐ˜์ ์ธ ์žฌ๊ท€์  JOIN:
PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž

๋ถ„๋ช…ํžˆ ์ด ์š”์ฒญ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ˜๋ณต ํšŸ์ˆ˜๋Š” ์ด ํ•˜์œ„ ํ•ญ๋ชฉ ์ˆ˜์™€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค. (๊ทธ๋ฆฌ๊ณ  ์ˆ˜์‹ญ ๊ฐœ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค) ์ด๋Š” ์ƒ๋‹นํ•œ ๋ฆฌ์†Œ์Šค์™€ ๊ฒฐ๊ณผ์ ์œผ๋กœ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"๊ฐ€์žฅ ๋„“์€" ํ•˜์œ„ ํŠธ๋ฆฌ๋ฅผ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

WITH RECURSIVE T AS (
  SELECT
    id
  FROM
    hier
  WHERE
    id = 5300
UNION ALL
  SELECT
    hier.id
  FROM
    T
  JOIN
    hier
      ON hier.pid = T.id
)
TABLE T;

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž
[explain.tensor.ru ์ฐธ์กฐ]

์˜ˆ์ƒ๋Œ€๋กœ 30๊ฐœ์˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ชจ๋‘ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๋“ค์€ ์—ฌ๊ธฐ์— ์ด ์‹œ๊ฐ„์˜ 60%๋ฅผ ์†Œ๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๊ทธ๋“ค์€ ์ธ๋ฑ์Šค์—์„œ 30๋ฒˆ์˜ ๊ฒ€์ƒ‰๋„ ์ˆ˜ํ–‰ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ๊ฐ€์š”?

์ƒ‰์ธ๋ณ„ ๋Œ€๋Ÿ‰๊ต์ •

๊ฐ ๋…ธ๋“œ์— ๋Œ€ํ•ด ๋ณ„๋„์˜ ์ธ๋ฑ์Šค ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๊นŒ? ์•„๋‹ˆ์š”. ์ƒ‰์ธ์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ์˜ ํ˜ธ์ถœ๋กœ ์—ฌ๋Ÿฌ ํ‚ค๋ฅผ ๋™์‹œ์— ์‚ฌ์šฉ ๋ฅผ ํ†ตํ•ด = ANY(array).

๊ทธ๋ฆฌ๊ณ  ๊ฐ ์‹๋ณ„์ž ๊ทธ๋ฃน์—์„œ ์ด์ „ ๋‹จ๊ณ„์—์„œ "๋…ธ๋“œ"๋กœ ์ฐพ์€ ๋ชจ๋“  ID๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๋‹ค์Œ ๋‹จ๊ณ„๋งˆ๋‹ค ์šฐ๋ฆฌ๋Š” ํŠน์ • ๋ ˆ๋ฒจ์˜ ๋ชจ๋“  ์ž์†์„ ํ•œ๋ฒˆ์— ๊ฒ€์ƒ‰.

๋‹ค๋งŒ, ๋ฌธ์ œ๋Š” ๋ฐ”๋กœ ์ด๊ฒƒ์ด๋‹ค. ์žฌ๊ท€ ์„ ํƒ์—์„œ๋Š” ์ค‘์ฒฉ ์ฟผ๋ฆฌ์—์„œ ์ž์ฒด์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค., ํ•˜์ง€๋งŒ ์ด์ „ ์ˆ˜์ค€์—์„œ ์ฐพ์€ ๊ฒƒ๋งŒ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค... ์ „์ฒด ์„ ํƒ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ์ค‘์ฒฉ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ํŠน์ • ํ•„๋“œ์— ๋Œ€ํ•ด์„œ๋Š” ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์ด ๋ฐํ˜€์กŒ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ํ•„๋“œ๋Š” ๋ฐฐ์—ด์ด ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ANY.

์กฐ๊ธˆ ์ด์ƒํ•˜๊ฒŒ ๋“ค๋ฆฌ์ง€๋งŒ ๋‹ค์ด์–ด๊ทธ๋žจ์—์„œ๋Š” ๋ชจ๋“  ๊ฒƒ์ด ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž

WITH RECURSIVE T AS (
  SELECT
    ARRAY[id] id$
  FROM
    hier
  WHERE
    id = 5300
UNION ALL
  SELECT
    ARRAY(
      SELECT
        id
      FROM
        hier
      WHERE
        pid = ANY(T.id$)
    ) id$
  FROM
    T
  WHERE
    coalesce(id$, '{}') <> '{}' -- ัƒัะปะพะฒะธะต ะฒั‹ั…ะพะดะฐ ะธะท ั†ะธะบะปะฐ - ะฟัƒัั‚ะพะน ะผะฐััะธะฒ
)
SELECT
  unnest(id$) id
FROM
  T;

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž
[explain.tensor.ru ์ฐธ์กฐ]

๊ทธ๋ฆฌ๊ณ  ์—ฌ๊ธฐ์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์‹œ๊ฐ„ ๋‚ด์— 1.5๋ฐฐ ์Šน๋ฆฌ, ์ธ๋ฑ์Šค์— ๋Œ€ํ•œ ํ˜ธ์ถœ์ด 5๊ฐœ๊ฐ€ ์•„๋‹Œ 30๊ฐœ๋ฟ์ด๋ฏ€๋กœ ๋” ์ ์€ ์ˆ˜์˜ ๋ฒ„ํผ๋ฅผ ๋บ๋‹ˆ๋‹ค!

์ถ”๊ฐ€ ๋ณด๋„ˆ์Šค๋Š” ์ตœ์ข… unnest ํ›„์—๋„ ์‹๋ณ„์ž๊ฐ€ "๋ ˆ๋ฒจ"๋ณ„๋กœ ์ •๋ ฌ๋˜์–ด ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์ž…๋‹ˆ๋‹ค.

๋…ธ๋“œ ์‚ฌ์ธ

์„ฑ๋Šฅ ํ–ฅ์ƒ์— ๋„์›€์ด ๋˜๋Š” ๋‹ค์Œ ๊ณ ๋ ค ์‚ฌํ•ญ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. "์žŽ"์€ ์ž์‹์„ ๊ฐ€์งˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค์ฆ‰, ๊ทธ๋“ค์—๊ฒŒ๋Š” ์ „ํ˜€ "์•„๋ž˜"๋ฅผ ๋ณผ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ ์ž‘์—…์˜ ๊ณต์‹ํ™”์—์„œ ์ด๋Š” ๋ถ€์„œ ์ฒด์ธ์„ ๋”ฐ๋ผ ์ง์›์—๊ฒŒ ์—ฐ๋ฝํ•˜๋ฉด ์ด ์ง€์ ์„ ๋” ์ด์ƒ ๋ณผ ํ•„์š”๊ฐ€ ์—†์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์šฐ๋ฆฌ ํ…Œ์ด๋ธ”์— ๋“ค์–ด๊ฐ€์ž ์ถ”๊ฐ€์˜ boolean-ํ•„๋“œ, ์ด๋Š” ํŠธ๋ฆฌ์˜ ์ด ํŠน์ • ํ•ญ๋ชฉ์ด "๋…ธ๋“œ"์ธ์ง€, ์ฆ‰ ํ•˜์œ„ ํ•ญ๋ชฉ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์ฆ‰์‹œ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.

ALTER TABLE hier
  ADD COLUMN branch boolean;

UPDATE
  hier T
SET
  branch = TRUE
WHERE
  EXISTS(
    SELECT
      NULL
    FROM
      hier
    WHERE
      pid = T.id
    LIMIT 1
);
-- ะ—ะฐะฟั€ะพั ัƒัะฟะตัˆะฝะพ ะฒั‹ะฟะพะปะฝะตะฝ: 3033 ัั‚ั€ะพะบ ะธะทะผะตะฝะตะฝะพ ะทะฐ 42 ะผั.

์—„์ฒญ๋‚œ! ๋ชจ๋“  ํŠธ๋ฆฌ ์š”์†Œ ์ค‘ 30% ๋‚จ์ง“๋งŒ์ด ์ž์†์„ ๊ฐ–๊ณ  ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ๋‚˜ํƒ€๋‚ฌ์Šต๋‹ˆ๋‹ค.

์ด์ œ ์•ฝ๊ฐ„ ๋‹ค๋ฅธ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์‚ฌ์šฉํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๋‹ค์Œ์„ ํ†ตํ•ด ์žฌ๊ท€ ๋ถ€๋ถ„์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. LATERAL์ด๋ฅผ ํ†ตํ•ด ์žฌ๊ท€ "ํ…Œ์ด๋ธ”"์˜ ํ•„๋“œ์— ์ฆ‰์‹œ ์•ก์„ธ์Šคํ•˜๊ณ  ๋…ธ๋“œ ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ๋ง ์กฐ๊ฑด๊ณผ ํ•จ๊ป˜ ์ง‘๊ณ„ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ‚ค ์„ธํŠธ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž

WITH RECURSIVE T AS (
  SELECT
    array_agg(id) id$
  , array_agg(id) FILTER(WHERE branch) ns$
  FROM
    hier
  WHERE
    id = 5300
UNION ALL
  SELECT
    X.*
  FROM
    T
  JOIN LATERAL (
    SELECT
      array_agg(id) id$
    , array_agg(id) FILTER(WHERE branch) ns$
    FROM
      hier
    WHERE
      pid = ANY(T.ns$)
  ) X
    ON coalesce(T.ns$, '{}') <> '{}'
)
SELECT
  unnest(id$) id
FROM
  T;

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž
[explain.tensor.ru ์ฐธ์กฐ]

์ธ๋ฑ์Šค ํ˜ธ์ถœ์„ ํ•œ ๋ฒˆ ๋” ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ๊ณ  ์–‘์ ์œผ๋กœ 2๋ฐฐ ์ด์ƒ ์Šน๋ฆฌ ๊ต์ •ํ•˜๋‹ค.

#2. ๋ฟŒ๋ฆฌ๋กœ ๋Œ์•„๊ฐ€์ž

์ด ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ "ํŠธ๋ฆฌ ์œ„"์˜ ๋ชจ๋“  ์š”์†Œ์— ๋Œ€ํ•œ ๊ธฐ๋ก์„ ์ˆ˜์ง‘ํ•ด์•ผ ํ•˜๋Š” ๋™์‹œ์— ์–ด๋–ค ์†Œ์Šค ์‹œํŠธ(๋ฐ ์–ด๋–ค ์ง€ํ‘œ๋กœ)๊ฐ€ ์ƒ˜ํ”Œ์— ํฌํ•จ๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: ์š”์•ฝ ๋ณด๊ณ ์„œ ์ƒ์„ฑ). ๋…ธ๋“œ๋กœ ์ง‘๊ณ„๋ฉ๋‹ˆ๋‹ค.

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž
์š”์ฒญ์ด ๋งค์šฐ ๋ฒˆ๊ฑฐ๋กญ๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ ๋‚ด์šฉ์€ ๊ฐœ๋… ์ฆ๋ช…์œผ๋กœ๋งŒ ๋ฐ›์•„๋“ค์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง€๋ฐฐํ•œ๋‹ค๋ฉด ์œ ์‚ฌํ•œ ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ช‡ ๊ฐ€์ง€ ๊ฐ„๋‹จํ•œ ๋ฌธ์žฅ์œผ๋กœ ์‹œ์ž‘ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋™์ผํ•œ ๋ ˆ์ฝ”๋“œ ํ•œ ๋ฒˆ๋งŒ ์ฝ์–ด๋ณด๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์Šต๋‹ˆ๋‹ค..
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ ˆ์ฝ”๋“œ ์ผ๊ด„์ ์œผ๋กœ ์ฝ๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.ํ˜ผ์ž๋ณด๋‹ค.

์ด์ œ ํ•„์š”ํ•œ ์š”์ฒญ์„ ๊ตฌ์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

1 ๋‹จ๊ณ„

๋ถ„๋ช…ํžˆ, ์žฌ๊ท€๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ(์žฌ๊ท€๊ฐ€ ์—†์œผ๋ฉด ์–ด๋””์— ์žˆ์„๊นŒ์š”!) ์ดˆ๊ธฐ ์‹๋ณ„์ž ์„ธํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฆฌํ”„ ์ž์ฒด์˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋นผ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

WITH RECURSIVE tree AS (
  SELECT
    rec -- ัั‚ะพ ั†ะตะปัŒะฝะฐั ะทะฐะฟะธััŒ ั‚ะฐะฑะปะธั†ั‹
  , id::text chld -- ัั‚ะพ "ะฝะฐะฑะพั€" ะฟั€ะธะฒะตะดัˆะธั… ััŽะดะฐ ะธัั…ะพะดะฝั‹ั… ะปะธัั‚ัŒะตะฒ
  FROM
    hier rec
  WHERE
    id = ANY('{1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192}'::integer[])
UNION ALL
  ...

"์„ธํŠธ"๊ฐ€ ๋ฐฐ์—ด์ด ์•„๋‹Œ ๋ฌธ์ž์—ด๋กœ ์ €์žฅ๋œ๋‹ค๋Š” ๊ฒƒ์ด ๋ˆ„๊ตฐ๊ฐ€์—๊ฒŒ ์ด์ƒํ•˜๊ฒŒ ๋ณด์ธ๋‹ค๋ฉด ์ด์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์„ค๋ช…์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ž์—ด์— ๋Œ€ํ•œ ์ง‘๊ณ„ "์ ‘์ฐฉ" ๊ธฐ๋Šฅ์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. string_agg, ๊ทธ๋Ÿฌ๋‚˜ ๋ฐฐ์—ด์—๋Š” ํ•ด๋‹น๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋น„๋ก ๊ทธ๋…€๋Š” ์Šค์Šค๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ ์‰ฝ๋‹ค.

2 ๋‹จ๊ณ„

์ด์ œ ๋” ์ฝ์–ด์•ผ ํ•  ์„น์…˜ ID ์„ธํŠธ๋ฅผ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ฑฐ์˜ ํ•ญ์ƒ ์›๋ณธ ์„ธํŠธ์˜ ๋‹ค๋ฅธ ๋ ˆ์ฝ”๋“œ์— ๋ณต์ œ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฃนํ™”, ์†Œ์Šค ์žŽ์— ๋Œ€ํ•œ ์ •๋ณด๋Š” ๋ณด์กดํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์—ฌ๊ธฐ์„œ๋Š” ์„ธ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์šฐ๋ฆฌ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ฟผ๋ฆฌ์˜ "ํ•˜์œ„ ์žฌ๊ท€" ๋ถ€๋ถ„์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ง‘๊ณ„ ํ•จ์ˆ˜๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. GROUP BY.
  2. ์žฌ๊ท€์  "ํ…Œ์ด๋ธ”"์— ๋Œ€ํ•œ ์ฐธ์กฐ๋Š” ์ค‘์ฒฉ๋œ ํ•˜์œ„ ์ฟผ๋ฆฌ์— ํฌํ•จ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  3. ์žฌ๊ท€ ๋ถ€๋ถ„์˜ ์š”์ฒญ์—๋Š” CTE๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋‹คํ–‰ํžˆ๋„ ์ด๋Ÿฌํ•œ ๋ชจ๋“  ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ฉ์‹œ๋‹ค.

์žฌ๊ท€ ๋ถ€๋ถ„์˜ CTE

์—ฌ๊ธฐ ๊ทธ๋ ‡๊ฒŒ ์•„๋‹ˆ ๊ณต์žฅ:

WITH RECURSIVE tree AS (
  ...
UNION ALL
  WITH T (...)
  SELECT ...
)

๊ทธ๋ž˜์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ด„ํ˜ธ๊ฐ€ ์ฐจ์ด๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค!

WITH RECURSIVE tree AS (
  ...
UNION ALL
  (
    WITH T (...)
    SELECT ...
  )
)

์žฌ๊ท€์  "ํ…Œ์ด๋ธ”"์— ๋Œ€ํ•œ ์ค‘์ฒฉ ์ฟผ๋ฆฌ

ํ ... ์žฌ๊ท€ CTE๋Š” ํ•˜์œ„ ์ฟผ๋ฆฌ์—์„œ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ CTE ๋‚ด๋ถ€์— ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค! ๊ทธ๋ฆฌ๊ณ  ์ค‘์ฒฉ๋œ ์š”์ฒญ์€ ์ด๋ฏธ ์ด CTE์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

GROUP BY ๋‚ด๋ถ€ ์žฌ๊ท€

๋ถˆ์พŒํ•˜์ง€๋งŒ... ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ GROUP BY๋ฅผ ์—๋ฎฌ๋ ˆ์ดํŠธํ•˜๋Š” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. DISTINCT ON ๊ทธ๋ฆฌ๊ณ  ์ฐฝ ๊ธฐ๋Šฅ!

SELECT
  (rec).pid id
, string_agg(chld::text, ',') chld
FROM
  tree
WHERE
  (rec).pid IS NOT NULL
GROUP BY 1 -- ะฝะต ั€ะฐะฑะพั‚ะฐะตั‚!

๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์ด ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค!

SELECT DISTINCT ON((rec).pid)
  (rec).pid id
, string_agg(chld::text, ',') OVER(PARTITION BY (rec).pid) chld
FROM
  tree
WHERE
  (rec).pid IS NOT NULL

์ด์ œ ์ˆซ์ž ID๊ฐ€ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜๋œ ์ด์œ ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์„œ๋กœ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3 ๋‹จ๊ณ„

๊ฒฐ์Šน์ „์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ๋‚จ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

  • ๊ทธ๋ฃนํ™”๋œ ID ์„ธํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ "์„น์…˜" ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค.
  • ๋บ€ ๋ถ€๋ถ„์„ ์›๋ณธ ์‹œํŠธ์˜ "์„ธํŠธ"์™€ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์ • ๋ฌธ์ž์—ด์„ "ํ™•์žฅ"ํ•ฉ๋‹ˆ๋‹ค. unnest(string_to_array(chld, ',')::integer[])

WITH RECURSIVE tree AS (
  SELECT
    rec
  , id::text chld
  FROM
    hier rec
  WHERE
    id = ANY('{1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192}'::integer[])
UNION ALL
  (
    WITH prnt AS (
      SELECT DISTINCT ON((rec).pid)
        (rec).pid id
      , string_agg(chld::text, ',') OVER(PARTITION BY (rec).pid) chld
      FROM
        tree
      WHERE
        (rec).pid IS NOT NULL
    )
    , nodes AS (
      SELECT
        rec
      FROM
        hier rec
      WHERE
        id = ANY(ARRAY(
          SELECT
            id
          FROM
            prnt
        ))
    )
    SELECT
      nodes.rec
    , prnt.chld
    FROM
      prnt
    JOIN
      nodes
        ON (nodes.rec).id = prnt.id
  )
)
SELECT
  unnest(string_to_array(chld, ',')::integer[]) leaf
, (rec).*
FROM
  tree;

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ํ† ๋ผ๊ตด์€ ์–ผ๋งˆ๋‚˜ ๊นŠ๋‚˜์š”? ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž
[explain.tensor.ru ์ฐธ์กฐ]

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€