PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: "๋ฌดํ•œ๋Œ€๋Š” ํ•œ๊ณ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!" ๋˜๋Š” ์žฌ๊ท€์— ๋Œ€ํ•œ ์•ฝ๊ฐ„์˜ ์„ค๋ช…

์žฌ๊ท€ - ๊ด€๋ จ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด ๋™์ผํ•œ "์‹ฌ์ธต" ์ž‘์—…์ด ์ˆ˜ํ–‰๋˜๋Š” ๊ฒฝ์šฐ ๋งค์šฐ ๊ฐ•๋ ฅํ•˜๊ณ  ํŽธ๋ฆฌํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ†ต์ œ๋˜์ง€ ์•Š์€ ์žฌ๊ท€๋Š” ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋Š” ํ•ด์•…์ž…๋‹ˆ๋‹ค. ๋์—†๋Š” ์‹คํ–‰ ํ”„๋กœ์„ธ์Šค ๋˜๋Š” (๋” ์ž์ฃผ ๋ฐœ์ƒํ•จ) ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋ฉ”๋ชจ๋ฆฌ๋ฅผ "๋จน๋Š”".

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: "๋ฌดํ•œ๋Œ€๋Š” ํ•œ๊ณ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!" ๋˜๋Š” ์žฌ๊ท€์— ๋Œ€ํ•œ ์•ฝ๊ฐ„์˜ ์„ค๋ช…
์ด์™€ ๊ด€๋ จํ•˜์—ฌ DBMS๋Š” ๋™์ผํ•œ ์›์น™์— ๋”ฐ๋ผ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. "ํŒŒ๋ผ๊ณ  ํ•ด์„œ ํŒŒ๋Š” ๊ฑฐ์•ผ". ๊ท€ํ•˜์˜ ์š”์ฒญ์€ ์ธ์ ‘ํ•œ ํ”„๋กœ์„ธ์Šค์˜ ์†๋„๋ฅผ ๋Šฆ์ถ”๊ณ  ์ง€์†์ ์œผ๋กœ ํ”„๋กœ์„ธ์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐจ์ง€ํ•  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ "์‚ญ์ œ"ํ•˜์—ฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋ฉ”๋ชจ๋ฆฌ๋ฅผ "๋จน์Šต๋‹ˆ๋‹ค". ๋”ฐ๋ผ์„œ ๋ฌดํ•œ ์žฌ๊ท€๋กœ๋ถ€ํ„ฐ ๋ณดํ˜ธ - ๊ฐœ๋ฐœ์ž ์ž์‹ ์˜ ์ฑ…์ž„์ž…๋‹ˆ๋‹ค.

PostgreSQL์—์„œ๋Š” ๋‹ค์Œ์„ ํ†ตํ•ด ์žฌ๊ท€ ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. WITH RECURSIVE ๋ฒ„์ „ 8.4๋ถ€ํ„ฐ ๋“ฑ์žฅํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ์ž ์žฌ์ ์œผ๋กœ ์ทจ์•ฝํ•œ "๋ฌด๋ฐฉ๋น„" ์š”์ฒญ์„ ์ž์ฃผ ์ ‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์ข…๋ฅ˜์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

์žฌ๊ท€ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ๋งˆ์„ธ์š”

๊ทธ๋ฆฌ๊ณ  ๋น„์žฌ๊ท€์ ์ธ ๊ฒƒ์„ ์ž‘์„ฑํ•˜์„ธ์š”. ์ง„์‹ฌ์œผ๋กœ, ๋‹น์‹ ์˜ K.O.

์‹ค์ œ๋กœ 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 ์•ˆํ‹ฐํŒจํ„ด: "๋ฌดํ•œ๋Œ€๋Š” ํ•œ๊ณ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!" ๋˜๋Š” ์žฌ๊ท€์— ๋Œ€ํ•œ ์•ฝ๊ฐ„์˜ ์„ค๋ช…
ํ•œ ์ฃผ์ œ์— ๋Œ€ํ•œ ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๋ผ๋Š” ์ผ๋ฐ˜์ ์ธ ์š”์ฒญ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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;

ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ํ•ญ์ƒ ๋ฃจํŠธ ๋ฉ”์‹œ์ง€์˜ ์ „์ฒด ์ฃผ์ œ๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ ๊ฐ ํ•ญ๋ชฉ์— ID๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ž๋™์  ์ธ?

-- ะดะพะฑะฐะฒะธะผ ะฟะพะปะต ั ะพะฑั‰ะธะผ ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะพะผ ั‚ะตะผั‹ ะธ ะธะฝะดะตะบั ะฝะฐ ะฝะตะณะพ
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 ์•ˆํ‹ฐํŒจํ„ด: "๋ฌดํ•œ๋Œ€๋Š” ํ•œ๊ณ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!" ๋˜๋Š” ์žฌ๊ท€์— ๋Œ€ํ•œ ์•ฝ๊ฐ„์˜ ์„ค๋ช…
์ด์ œ ์ „์ฒด ์žฌ๊ท€ ์ฟผ๋ฆฌ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SELECT
  *
FROM
  message
WHERE
  theme_id = $1;

์ ์šฉ๋œ "๋ฆฌ๋ฏธํ„ฐ" ์‚ฌ์šฉ

์–ด๋–ค ์ด์œ ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ตฌ์กฐ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, ๋ฐ์ดํ„ฐ์— ์˜ค๋ฅ˜๊ฐ€ ์žˆ์–ด๋„ ๋์—†๋Š” ๋ฐ˜๋ณต์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ๋ฌด์—‡์„ ์˜์ง€ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์žฌ๊ท€ ๊นŠ์ด ์นด์šดํ„ฐ

์šฐ๋ฆฌ๋Š” ๋ช…๋ฐฑํžˆ ๋ถ€์ ์ ˆํ•˜๋‹ค๊ณ  ์ƒ๊ฐ๋˜๋Š” ํ•œ๊ณ„์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ๊ฐ ์žฌ๊ท€ ๋‹จ๊ณ„์—์„œ ์นด์šดํ„ฐ๋ฅผ ํ•˜๋‚˜์”ฉ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.

WITH RECURSIVE T AS (
  SELECT
    0 i
  ...
UNION ALL
  SELECT
    i + 1
  ...
  WHERE
    T.i < 64 -- ะฟั€ะตะดะตะป
)

์ง์—… : ๋ฃจํ”„๋ฅผ ์‹œ๋„ํ•  ๋•Œ "๊นŠ์ด ์žˆ๋Š”" ๋ฐ˜๋ณต์˜ ์ง€์ •๋œ ์ œํ•œ ์ด์ƒ์€ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋‹จ์  : ๋™์ผํ•œ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๋ณด์žฅ์€ ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊นŠ์ด 15์™€ 25์—์„œ ๊ทธ๋ฆฌ๊ณ  +10๋งˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์•„๋ฌด๋„ "ํญ"์— ๋Œ€ํ•ด ์•ฝ์†ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๊ณต์‹์ ์œผ๋กœ ์ด๋Ÿฌํ•œ ๋ฐ˜๋ณต์€ ๋ฌดํ•œํ•˜์ง€ ์•Š์ง€๋งŒ ๊ฐ ๋‹จ๊ณ„์—์„œ ๋ ˆ์ฝ”๋“œ ์ˆ˜๊ฐ€ ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๋ฉด ์šฐ๋ฆฌ ๋ชจ๋‘๋Š” ๊ทธ๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ๋๋‚˜๋Š”์ง€ ์ž˜ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: "๋ฌดํ•œ๋Œ€๋Š” ํ•œ๊ณ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!" ๋˜๋Š” ์žฌ๊ท€์— ๋Œ€ํ•œ ์•ฝ๊ฐ„์˜ ์„ค๋ช…"์ฒด์ŠคํŒ ์œ„์˜ ๊ณก๋ฌผ ๋ฌธ์ œ"๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

"๊ธธ"์˜ ์ˆ˜ํ˜ธ์ž

์šฐ๋ฆฌ๋Š” ์žฌ๊ท€ ๊ฒฝ๋กœ๋ฅผ ๋”ฐ๋ผ ๋งŒ๋‚œ ๋ชจ๋“  ๊ฐœ์ฒด ์‹๋ณ„์ž๋ฅผ ๋ฐฐ์—ด์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๋ฐฐ์—ด์€ ๋ฐฐ์—ด์— ๋Œ€ํ•œ ๊ณ ์œ ํ•œ "๊ฒฝ๋กœ"์ž…๋‹ˆ๋‹ค.

WITH RECURSIVE T AS (
  SELECT
    ARRAY[id] path
  ...
UNION ALL
  SELECT
    path || id
  ...
  WHERE
    id <> ALL(T.path) -- ะฝะต ัะพะฒะฟะฐะดะฐะตั‚ ะฝะธ ั ะพะดะฝะธะผ ะธะท
)

์ง์—… : ๋ฐ์ดํ„ฐ์— ์ˆœํ™˜์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋™์ผํ•œ ๊ฒฝ๋กœ ๋‚ด์—์„œ ๋™์ผํ•œ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋‹จ์  : ๊ทธ๋Ÿฌ๋‚˜ ๋™์‹œ์— ์šฐ๋ฆฌ๋Š” ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๊ณ ๋„ ๋ง ๊ทธ๋Œ€๋กœ ๋ชจ๋“  ๊ธฐ๋ก์„ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: "๋ฌดํ•œ๋Œ€๋Š” ํ•œ๊ณ„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!" ๋˜๋Š” ์žฌ๊ท€์— ๋Œ€ํ•œ ์•ฝ๊ฐ„์˜ ์„ค๋ช…"๊ธฐ์‚ฌ์˜ ์ด๋™ ๋ฌธ์ œ"๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๊ฒฝ๋กœ ๊ธธ์ด ์ œํ•œ

์ดํ•ดํ•  ์ˆ˜ ์—†๋Š” ๊นŠ์ด์—์„œ ์žฌ๊ท€๊ฐ€ "๋ฐฉํ™ฉ"ํ•˜๋Š” ์ƒํ™ฉ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์ด์ „ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ๋ถˆํ•„์š”ํ•œ ํ•„๋“œ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ ค๋ฉด ๊ฒฝ๋กœ ๊ธธ์ด ์ถ”์ •์œผ๋กœ ์žฌ๊ท€๋ฅผ ๊ณ„์†ํ•˜๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด์„ ๋ณด์™„ํ•˜์„ธ์š”.

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

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