PostgreSQL Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”

ื‘ืžืขืจื›ื•ืช ERP ืžื•ืจื›ื‘ื•ืช ืœื™ืฉื•ื™ื•ืช ืจื‘ื•ืช ื™ืฉ ืื•ืคื™ ื”ื™ืจืจื›ื™ื›ืืฉืจ ื—ืคืฆื™ื ื”ื•ืžื•ื’ื ื™ื™ื ืžืกืชื“ืจื™ื ืขืฅ ืฉืœ ื™ื—ืกื™ ืื‘ื•ืช-ืฆืืฆืื™ื - ื–ื”ื• ื”ืžื‘ื ื” ื”ืืจื’ื•ื ื™ ืฉืœ ื”ืžื™ื–ื (ื›ืœ ื”ืกื ื™ืคื™ื, ื”ืžื—ืœืงื•ืช ื•ืงื‘ื•ืฆื•ืช ื”ืขื‘ื•ื“ื” ื”ืœืœื•), ื•ืงื˜ืœื•ื’ ื”ืกื—ื•ืจื•ืช ื•ืชื—ื•ืžื™ ื”ืขื‘ื•ื“ื”, ื•ื”ื’ื™ืื•ื’ืจืคื™ื” ืฉืœ ื ืงื•ื“ื•ืช ื”ืžื›ื™ืจื”,...

PostgreSQL Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”

ืœืžืขืฉื”, ืื™ืŸ ื›ื–ื” ืื–ื•ืจื™ ืื•ื˜ื•ืžืฆื™ื” ืขืกืงื™ืช, ืฉื‘ื• ืœื ืชื”ื™ื” ื”ื™ืจืจื›ื™ื” ื›ืœืฉื”ื™ ื›ืชื•ืฆืื” ืžื›ืš. ืื‘ืœ ื’ื ืื ืืชื” ืœื ืขื•ื‘ื“ "ืขื‘ื•ืจ ื”ืขืกืง", ืืชื” ืขื“ื™ื™ืŸ ื™ื›ื•ืœ ืœื”ื™ืชืงืœ ื‘ืงืœื•ืช ื‘ืงืฉืจื™ื ื”ื™ืจืจื›ื™ื™ื. ื–ื” ื ื“ื•ืฉ, ืืคื™ืœื• ืขืฅ ื”ืžืฉืคื—ื” ืฉืœืš ืื• ืชื•ื›ื ื™ืช ื”ืงื•ืžื” ืฉืœ ื”ื ื—ื•ืช ื‘ืžืจื›ื– ืงื ื™ื•ืช ื–ื” ืื•ืชื• ืžื‘ื ื”.

ื™ืฉื ืŸ ื“ืจื›ื™ื ืจื‘ื•ืช ืœืื—ืกืŸ ืขืฅ ื›ื–ื” ื‘-DBMS, ืืš ื”ื™ื•ื ื ืชืžืงื“ ื‘ืืคืฉืจื•ืช ืื—ืช ื‘ืœื‘ื“:

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

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

ื•ื‘ื–ืžืŸ ืฉืืชื ืžืฆื™ืฆื™ื ืืœ ืžืขืžืงื™ ื”ื”ื™ืจืจื›ื™ื”, ื”ื•ื ืžื—ื›ื” ื‘ืกื‘ืœื ื•ืช ืœืจืื•ืช ืขื“ ื›ืžื” ื™ื”ื™ื• ื“ืจื›ื™ ื”ืขื‘ื•ื“ื” ื”"ื ืื™ื‘ื™ื•ืช" ืฉืœื›ื ืขื ืžื‘ื ื” ื›ื–ื” [ืœื] ื™ืขื™ืœื•ืช.

PostgreSQL Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”
ื‘ื•ืื• ื ืกืชื›ืœ ืขืœ ื‘ืขื™ื•ืช ื˜ื™ืคื•ืกื™ื•ืช ืฉืฆืฆื•ืช, ื”ื˜ืžืขืชืŸ ื‘-SQL, ื•ื ื ืกื” ืœืฉืคืจ ืืช ื”ื‘ื™ืฆื•ืขื™ื ืฉืœื”ืŸ.

#1. ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘?

ื”ื‘ื” ื ืงื‘ืœ, ืœื™ืชืจ ื‘ื˜ื—ื•ืŸ, ืฉืžื‘ื ื” ื–ื” ื™ืฉืงืฃ ืืช ื›ืคื™ืคื•ืช ื”ืžื—ืœืงื•ืช ื‘ืžื‘ื ื” ื”ืืจื’ื•ืŸ: ืžื—ืœืงื•ืช, ื—ื˜ื™ื‘ื•ืช, ืžื’ื–ืจื™ื, ืกื ื™ืคื™ื, ืงื‘ื•ืฆื•ืช ืขื‘ื•ื“ื”... - ืื™ืš ืฉืœื ืชืงืจืื• ืœื”ื.
PostgreSQL Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”

ืจืืฉื™ืช, ื‘ื•ืื• ื ื™ืฆื•ืจ ืืช ื”'ืขืฅ' ืฉืœื ื• ืฉืœ 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;

ื ืชื—ื™ืœ ื‘ืžืฉื™ืžื” ื”ืคืฉื•ื˜ื” ื‘ื™ื•ืชืจ - ืœืžืฆื•ื ืืช ื›ืœ ื”ืขื•ื‘ื“ื™ื ืฉืขื•ื‘ื“ื™ื ื‘ืžื’ื–ืจ ืžืกื•ื™ื, ืื• ื‘ืžื•ื ื—ื™ื ืฉืœ ื”ื™ืจืจื›ื™ื” - ืœืžืฆื•ื ืืช ื›ืœ ื”ื™ืœื“ื™ื ืฉืœ ืฆื•ืžืช. ื™ื”ื™ื” ื ื—ืžื“ ื’ื ืœืงื‘ืœ ืืช ื”"ืขื•ืžืง" ืฉืœ ื”ืฆืืฆื... ื›ืœ ื–ื” ืขืฉื•ื™ ืœื”ื™ื•ืช ื ื—ื•ืฅ, ืœืžืฉืœ, ื›ื“ื™ ืœื‘ื ื•ืช ืื™ื–ืฉื”ื• ืกื•ื’ ืฉืœ ื‘ื—ื™ืจื” ืžื•ืจื›ื‘ืช ื”ืžื‘ื•ืกืกืช ืขืœ ืจืฉื™ืžืช ืชืขื•ื“ื•ืช ื”ื–ื”ื•ืช ืฉืœ ืขื•ื‘ื“ื™ื ืืœื”.

ื”ื›ืœ ื™ื”ื™ื” ื‘ืกื“ืจ ืื ื™ืฉ ืจืง ื›ืžื” ืจืžื•ืช ืฉืœ ืฆืืฆืื™ื ืืœื” ื•ื”ืžืกืคืจ ื”ื•ื ื‘ื˜ื•ื•ื— ืฉืœ ืชืจื™ืกืจ, ืื‘ืœ ืื ื™ืฉ ื™ื•ืชืจ ืž-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 Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”

ื‘ืจื•ืจ, ืขื ืžื•ื“ืœ ื”ื‘ืงืฉื” ื”ื–ื” ืžืกืคืจ ื”ืื™ื˜ืจืฆื™ื•ืช ื™ืชืื™ื ืœืžืกืคืจ ื”ื›ื•ืœืœ ืฉืœ ืฆืืฆืื™ื (ื•ื™ืฉ ื›ืžื” ืขืฉืจื•ืช ื›ืืœื”), ื•ื–ื” ื™ื›ื•ืœ ืœืงื—ืช ืžืฉืื‘ื™ื ืžืฉืžืขื•ืชื™ื™ื ืœืžื“ื™, ื•ื›ืชื•ืฆืื” ืžื›ืš, ื–ืžืŸ.

ื‘ื•ืื• ื ื‘ื“ื•ืง ืืช ืชืช-ื”ืขืฅ "ื”ืจื—ื‘ ื‘ื™ื•ืชืจ":

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 Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”
[ื”ืกืชื›ืœ ื‘-explain.tensor.ru]

ื›ืฆืคื•ื™, ืžืฆืื ื• ืืช ื›ืœ 30 ื”ืชืงืœื™ื˜ื™ื. ืื‘ืœ ื”ื ื”ืฉืงื™ืขื• ื‘ื–ื” 60% ืžื”ื–ืžืŸ ื”ื›ื•ืœืœ - ื›ื™ ื”ื ื’ื ื‘ื™ืฆืขื• 30 ื—ื™ืคื•ืฉื™ื ื‘ืื™ื ื“ืงืก. ื”ืื ืืคืฉืจ ืœืขืฉื•ืช ืคื—ื•ืช?

ื”ื’ื”ื” ื‘ื›ืžื•ืช ื’ื“ื•ืœื” ืœืคื™ ืื™ื ื“ืงืก

ื”ืื ืขืœื™ื ื• ืœื‘ืฆืข ืฉืื™ืœืชืช ืื™ื ื“ืงืก ื ืคืจื“ืช ืขื‘ื•ืจ ื›ืœ ืฆื•ืžืช? ืžืกืชื‘ืจ ืฉืœื - ืื ื—ื ื• ื™ื›ื•ืœื™ื ืœืงืจื•ื ืžื”ืื™ื ื“ืงืก ืฉื™ืžื•ืฉ ื‘ืžืกืคืจ ืžืงืฉื™ื ื‘ื• ื–ืžื ื™ืช ื‘ืฉื™ื—ื” ืื—ืช ื‘ืืžืฆืขื•ืช = ANY(array).

ื•ื‘ื›ืœ ืงื‘ื•ืฆื” ื›ื–ื• ืฉืœ ืžื–ื”ื™ื ื ื•ื›ืœ ืœืงื—ืช ืืช ื›ืœ ื”ืžื–ื”ื™ื ืฉื ืžืฆืื• ื‘ืฉืœื‘ ื”ืงื•ื“ื ืœืคื™ "ืฆืžืชื™ื". ื›ืœื•ืžืจ, ื‘ื›ืœ ืฉืœื‘ ื”ื‘ื ื ืขืฉื” ื—ืคืฉ ืืช ื›ืœ ื”ืฆืืฆืื™ื ืฉืœ ืจืžื” ืžืกื•ื™ืžืช ื‘ื‘ืช ืื—ืช.

ืจืง, ื›ืืŸ ื”ื‘ืขื™ื”, ื‘ื‘ื—ื™ืจื” ืจืงื•ืจืกื™ื‘ื™ืช, ืื™ื ืš ื™ื›ื•ืœ ืœื’ืฉืช ืœืขืฆืžื• ื‘ืฉืื™ืœืชื” ืžืงื•ื ื ืช, ืื‘ืœ ืื ื—ื ื• ืฆืจื™ื›ื™ื ืื™ื›ืฉื”ื• ืœื‘ื—ื•ืจ ืจืง ืืช ืžื” ืฉื ืžืฆื ื‘ืจืžื” ื”ืงื•ื“ืžืช... ืžืกืชื‘ืจ ืฉืื™ ืืคืฉืจ ืœืขืฉื•ืช ืฉืื™ืœืชื” ืžืงื•ื ื ืช ืœื›ืœ ื”ื‘ื—ื™ืจื”, ืื‘ืœ ืœืฉื“ื” ื”ืกืคืฆื™ืคื™ ืฉืœื” ื–ื” ืืคืฉืจื™. ื•ื”ืฉื“ื” ื”ื–ื” ื™ื›ื•ืœ ืœื”ื™ื•ืช ื’ื ืžืขืจืš - ื•ื–ื” ืžื” ืฉืื ื—ื ื• ืฆืจื™ื›ื™ื ืœื”ืฉืชืžืฉ ื‘ื• ANY.

ื–ื” ื ืฉืžืข ืงืฆืช ืžื˜ื•ืจืฃ, ืื‘ืœ ื‘ืชืจืฉื™ื ื”ื›ืœ ืคืฉื•ื˜.

PostgreSQL Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”

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 Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”
[ื”ืกืชื›ืœ ื‘-explain.tensor.ru]

ื•ื›ืืŸ ื”ื“ื‘ืจ ื”ื›ื™ ื—ืฉื•ื‘ ื”ื•ื ืืคื™ืœื• ืœื ืœื ืฆื— 1.5 ืคืขืžื™ื ื‘ื–ืžืŸ, ื•ื›ื™ ื”ื•ืจื“ื ื• ืคื—ื•ืช ื—ื•ืฆืฆื™ื, ืžื›ื™ื•ื•ืŸ ืฉื™ืฉ ืœื ื• ืจืง 5 ืงืจื™ืื•ืช ืœืื™ื ื“ืงืก ื‘ืžืงื•ื 30!

ื‘ื•ื ื•ืก ื ื•ืกืฃ ื”ื•ื ื”ืขื•ื‘ื“ื” ืฉืื—ืจื™ ื”ื”ืชืคืจืขื•ืช ื”ืกื•ืคื™ืช, ื”ืžื–ื”ื™ื ื™ื™ืฉืืจื• ืžืกื•ื“ืจื™ื ืœืคื™ "ืจืžื•ืช".

ืกื™ืžืŸ ืฆื•ืžืช

ื”ืฉื™ืงื•ืœ ื”ื‘ื ืฉื™ืขื–ื•ืจ ืœืฉืคืจ ืืช ื”ื‘ื™ืฆื•ืขื™ื ื”ื•ื - "ืขืœื™ื" ืœื ื™ื›ื•ืœื™ื ืœื”ื‘ื™ื ื™ืœื“ื™ื, ื›ืœื•ืžืจ, ืขื‘ื•ืจื ืื™ืŸ ืฆื•ืจืš ืœื”ืกืชื›ืœ "ืœืžื˜ื”" ื›ืœืœ. ื‘ื ื™ืกื•ื— ื”ืžืฉื™ืžื” ืฉืœื ื• ื–ื” ืื•ืžืจ ืฉืื ืขืงื‘ื ื• ืื—ืจื™ ืฉืจืฉืจืช ื”ืžื—ืœืงื•ืช ื•ื”ื’ืขื ื• ืœืขื•ื‘ื“, ืื– ืื™ืŸ ืฆื•ืจืš ืœื”ืกืชื›ืœ ื”ืœืื” ืœืื•ืจืš ื”ืกื ื™ืฃ ื”ื–ื”.

ื‘ื•ืื• ื ื™ื›ื ืก ืœื˜ื‘ืœื” ืฉืœื ื• ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพะต 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 Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”

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 Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”
[ื”ืกืชื›ืœ ื‘-explain.tensor.ru]

ื”ืฆืœื—ื ื• ืœื”ืคื—ื™ืช ืขื•ื“ ืฉื™ื—ืช ืื™ื ื“ืงืก ื• ื–ื›ื” ื™ื•ืชืจ ืžืคื™ 2 ื‘ื ืคื— ืœึฐื”ึทื’ึดื™ื”ึท.

#2. ื ื—ื–ื•ืจ ืœืฉื•ืจืฉื™ื

ืืœื’ื•ืจื™ืชื ื–ื” ื™ื”ื™ื” ืฉื™ืžื•ืฉื™ ืื ืชืฆื˜ืจืš ืœืืกื•ืฃ ืจืฉื•ืžื•ืช ืขื‘ื•ืจ ื›ืœ ื”ืจื›ื™ื‘ื™ื "ื‘ืžืขืœื” ื”ืขืฅ", ืชื•ืš ืฉืžื™ืจื” ืขืœ ืžื™ื“ืข ืขืœ ืื™ื–ื” ื’ื™ืœื™ื•ืŸ ืžืงื•ืจ (ื•ืขื ืื™ืœื• ืื™ื ื“ื™ืงื˜ื•ืจื™ื) ื’ืจื ืœื• ืœื”ื™ื›ืœืœ ื‘ืžื“ื’ื - ืœืžืฉืœ, ืœื”ืคืงืช ื“ื•ื— ืกื™ื›ื•ื ืขื ืฆื‘ื™ืจื” ืœืฆืžืชื™ื.

PostgreSQL Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”
ื™ืฉ ืœืจืื•ืช ืืช ื”ื“ื‘ืจื™ื ื”ื‘ืื™ื ื›ื”ื•ื›ื—ื” ื‘ืœื‘ื“, ืฉื›ืŸ ื”ื‘ืงืฉื” ืžืชื‘ืจืจืช ื›ืžืกื•ืจื‘ืœืช ืžืื•ื“. ืื‘ืœ ืื ื–ื” ืฉื•ืœื˜ ื‘ืžืกื“ ื”ื ืชื•ื ื™ื ืฉืœืš, ืืชื” ืฆืจื™ืš ืœื—ืฉื•ื‘ ืขืœ ืฉื™ืžื•ืฉ ื‘ื˜ื›ื ื™ืงื•ืช ื“ื•ืžื•ืช.

ื ืชื—ื™ืœ ื‘ื›ืžื” ื”ืฆื”ืจื•ืช ืคืฉื•ื˜ื•ืช:

  • ืื•ืชื” ืจืฉื•ืžื” ืžืžืกื“ ื”ื ืชื•ื ื™ื ืขื“ื™ืฃ ืœืงืจื•ื ืื•ืชื• ืจืง ืคืขื ืื—ืช.
  • ืจืฉื•ืžื•ืช ืžืžืกื“ ื”ื ืชื•ื ื™ื ื™ื•ืชืจ ื™ืขื™ืœ ืœืงืจื•ื ื‘ืงื‘ื•ืฆื•ืชืžืืฉืจ ืœื‘ื“.

ืขื›ืฉื™ื• ื‘ื•ืื• ื ื ืกื” ืœื‘ื ื•ืช ืืช ื”ื‘ืงืฉื” ืฉืื ื—ื ื• ืฆืจื™ื›ื™ื.

ืฉืœื‘ 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

ื›ืขืช ื ืงื‘ืœ ืงื‘ื•ืฆื” ืฉืœ ืžื–ื”ื™ ืžืงื˜ืขื™ื ืฉื™ื”ื™ื” ืฆื•ืจืš ืœืงืจื•ื ืขื•ื“ ื™ื•ืชืจ. ื›ืžืขื˜ ืชืžื™ื“ ื”ื ื™ืฉื•ื›ืคืœื• ื‘ืจืฉื•ืžื•ืช ืฉื•ื ื•ืช ืฉืœ ื”ืกื˜ ื”ืžืงื•ืจื™ - ื›ืš ื”ื™ื™ื ื• ืœืงื‘ืฅ ืื•ืชื, ืชื•ืš ืฉืžื™ืจื” ืขืœ ืžื™ื“ืข ืขืœ ื“ืคื™ ื”ืžืงื•ืจ.

ืื‘ืœ ื›ืืŸ ืžื—ื›ื•ืช ืœื ื• ืฉืœื•ืฉ ืฆืจื•ืช:

  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

ื›ืขืช ืื ื• ืจื•ืื™ื ืžื“ื•ืข ื”ืคืš ื”ืžื–ื”ื” ื”ืžืกืคืจื™ ืœื˜ืงืกื˜ - ื›ืš ืฉื ื™ืชืŸ ื™ื”ื™ื” ืœื—ื‘ืจ ืื•ืชื ื™ื—ื“ื™ื• ืžื•ืคืจื“ื™ื ื‘ืคืกื™ืงื™ื!

ืฉืœื‘ 3

ืœื’ืžืจ ืœื ื ืฉืืจ ื›ืœื•ื:

  • ืื ื• ืงื•ืจืื™ื ืจืฉื•ืžื•ืช "ืžื“ื•ืจ" ื”ืžื‘ื•ืกืกื•ืช ืขืœ ืงื‘ื•ืฆื” ืฉืœ ืžื–ื”ื™ื ืžืงื•ื‘ืฆื™ื
  • ืื ื• ืžืฉื•ื•ื™ื ืืช ื”ืงื˜ืขื™ื ื”ืžื•ืคื—ืชื™ื ืขื ื”"ืกื˜ื™ื" ืฉืœ ื”ื’ื™ืœื™ื•ื ื•ืช ื”ืžืงื•ืจื™ื™ื
  • "ื”ืจื—ื‘" ืืช ืžื—ืจื•ื–ืช ื”ืกื˜ ื‘ืืžืฆืขื•ืช 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 Antipatterns: ื›ืžื” ืขืžื•ืง ื—ื•ืจ ื”ืืจื ื‘? ื‘ื•ืื• ื ืขื‘ื•ืจ ื‘ื”ื™ืจืจื›ื™ื”
[ื”ืกืชื›ืœ ื‘-explain.tensor.ru]

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”