Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy

I nā ʻōnaehana ERP paʻakikī he ʻano hierarchical nā hui he nuii ka wā e laina ai nā mea homogeneous lāʻau pili kupuna-moopuna - ʻo kēia ka hoʻonohonoho hoʻonohonoho o ka ʻoihana (ʻo kēia mau lālā āpau, nā keʻena a me nā pūʻulu hana), a me ka papa inoa o nā waiwai, a me nā wahi hana, a me ka ʻāina o nā wahi kūʻai, ...

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy

ʻOiaʻiʻo,ʻaʻohe mea nā wahi ʻoihana ʻoihana, kahi i loaʻa ʻole kahi hierarchy ma muli o ka hopena. Akā inā ʻaʻole ʻoe e hana "no ka ʻoihana," hiki iā ʻoe ke hālāwai maʻalahi i nā pilina hierarchical. He mea maʻamau, ʻo kāu lāʻau ʻohana a i ʻole ka papahele papahele o kahi hale kūʻai ke ʻano like.

Nui nā ala e mālama ai i kēlā lāʻau i loko o kahi DBMS, akā i kēia lā e nānā mākou i hoʻokahi wale nō koho:

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

CREATE INDEX ON hier(pid); -- не забываем, что FK не подразумевает автосоздание индекса, в отличие от PK

A ʻoiai ʻoe e nānā ana i ka hohonu o ka hierarchy, ke kali hoʻomanawanui nei ʻoe e ʻike i ka maikaʻi o kāu mau ʻano "naive" e hana ai me ia ʻano hale.

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy
E nānā i nā pilikia maʻamau e kū mai ana, kā lākou hoʻokō ʻana ma SQL, a hoʻāʻo e hoʻomaikaʻi i kā lākou hana.

#1. Pehea ka hohonu o ka lua rabbit?

E ʻae mākou, no ka maopopo ʻana, e hōʻike ana kēia ʻano i ka subordination o nā keʻena i ke ʻano o ka hui: nā keʻena, nā māhele, nā ʻāpana, nā lālā, nā pūʻulu hana ... - nā mea āu i kapa ai iā lākou.
Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy

ʻO ka mua, e hoʻohua i kā mākou 'lāʻau' o 10K mau mea

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;

E hoʻomaka kākou me ka hana maʻalahi - e ʻimi ana i nā limahana a pau e hana ana i loko o kahi ʻāpana kikoʻī, a i ʻole ma ke ʻano o ka hierarchy - e huli i na keiki a pau o ka node. He mea maikaʻi nō hoʻi e kiʻi i ka "hohonu" o ka mamo ... Pono paha kēia mau mea a pau, no ka laʻana, e kūkulu i kekahi ʻano. koho paʻakikī e pili ana i ka papa inoa o nā ID o kēia mau limahana.

Maikaʻi nā mea a pau inā ʻelua wale nō pae o kēia mau mamo a aia ka helu i loko o ke kakini, akā inā ʻoi aku ka nui ma mua o 5 pae, a he mau kakini o nā mamo, aia paha nā pilikia. E nānā kākou pehea i kākau ʻia ai nā koho hulina ma lalo o ka lāʻau (a me ka hana). Akā ʻo ka mea mua, e hoʻoholo kākou i nā nodes e hoihoi loa i kā mākou noiʻi.

ʻO ka nui loa "hohonu" nā kumu lāʻau:

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}
...

ʻO ka nui loa "ākea" nā kumu lāʻau:

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

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

No kēia mau nīnau ua hoʻohana mākou i ka maʻamau recursive HUI:
Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy

ʻIke loa, me kēia hiʻohiʻona noi ʻo ka helu o nā hoʻomaʻamaʻa e like me ka huina o nā mamo (a he mau kakini o lakou), a hiki i keia ke lawe i na kumu waiwai nui, a, no ka hopena, he manawa.

E nānā kākou i ka subtree "ākea loa":

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;

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy
[nānā ma explain.tensor.ru]

E like me ka mea i manaʻo ʻia, loaʻa iā mākou nā moʻolelo 30 āpau. Akā ua hoʻohana lākou i 60% o ka manawa āpau ma kēia - no ka mea ua hana lākou i 30 mau hulina ma ka papa kuhikuhi. Hiki ke hana emi?

Heluhelu nui ma ka index

Pono mākou e hana i kahi hulina kuhikuhi no kēlā me kēia node? ʻIke ʻia ʻaʻole - hiki iā mākou ke heluhelu mai ka index e hoʻohana ana i kekahi mau kī i ka manawa hoʻokahi i hoʻokahi kelepona me ke kōkuaʻana o = ANY(array).

A i loko o kēlā me kēia pūʻulu o nā mea hōʻike hiki iā mākou ke lawe i nā ID āpau i loaʻa ma ka pae mua e nā "nodes". ʻO ia hoʻi, ma kēlā me kēia pae aʻe mākou e hana ai huli i nā mamo a pau o kekahi pae i ka manawa hoʻokahi.

Eia wale nō ka pilikia, i ke koho recursive, ʻaʻole hiki iā ʻoe ke komo iā ia iho i kahi nīnau nested, akā pono mākou e koho wale i ka mea i loaʻa ma ka pae mua ... Ua ʻike ʻia ʻaʻole hiki ke hana i kahi nīnau nested no ke koho holoʻokoʻa, akā no kāna kahua kikoʻī hiki ke hiki. A hiki i kēia kahua ke hoʻonohonoho - ʻo ia ka mea e pono ai mākou e hoʻohana ANY.

He mea pupule iki ia, akā ma ke kiʻikuhi he mea maʻalahi nā mea a pau.

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy

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;

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy
[nānā ma explain.tensor.ru]

A eia ka mea nui ʻaʻole like lanakila 1.5 manawa i ka manawa, a ua unuhi mākou i nā mea hoʻopaʻa liʻiliʻi, ʻoiai he 5 wale nō kā mākou kelepona i ka papa kuhikuhi ma mua o 30!

ʻO kahi bonus ʻē aʻe ka ʻoiaʻiʻo ma hope o ka unnest hope loa, e hoʻomau ʻia nā mea ʻike e nā "pae".

hōʻailona node

ʻO ka manaʻo hou aʻe e kōkua i ka hoʻomaikaʻi ʻana i ka hana ʻo − ʻAʻole hiki i nā "lau" ke hānau keiki, ʻo ia hoʻi, no lākou ʻaʻole pono e nānā "i lalo". I ka hoʻokumu ʻana i kā mākou hana, ʻo ia ka mea inā mākou e hahai i ke kaulahao o nā keʻena a hiki i kahi limahana, a laila ʻaʻohe pono e nānā hou aku ma kēia lālā.

E komo kāua i loko o kā mākou papaʻaina hou aku boolean- kahua, e haʻi koke mai iā mākou inā he "node" kēia komo kikoʻī i loko o kā mākou kumulāʻau - ʻo ia hoʻi, inā hiki iā ia ke loaʻa nā mamo.

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 мс.

Nui! ʻIke ʻia he ʻoi aku ka liʻiliʻi ma mua o 30% o nā kumu lāʻau āpau i loaʻa nā mamo.

I kēia manawa, e hoʻohana kākou i kahi mīkini ʻokoʻa ʻē aʻe - pili i ka ʻāpana recursive ma LATERAL, e ʻae iā mākou e komo koke i nā kahua o ka "papa" recursive, a hoʻohana i kahi hana aggregate me kahi kūlana kānana e pili ana i kahi node e hōʻemi i ka hoʻonohonoho o nā kī:

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy

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;

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy
[nānā ma explain.tensor.ru]

Ua hiki iā mākou ke hōʻemi i hoʻokahi kelepona kuhikuhi a lanakila ma mua o 2 manawa i ka leo hoʻoponopono.

#2. E hoʻi kāua i ke aʻa

Pono kēia algorithm inā pono ʻoe e hōʻiliʻili i nā moʻolelo no nā mea āpau "i luna o ka lāʻau", ʻoiai e mālama ana i ka ʻike e pili ana i ka pepa kumu (a me nā mea hōʻailona) i hoʻokomo ʻia i loko o ka laʻana - no ka laʻana, e hana i kahi hōʻike hōʻuluʻulu. me ka houluulu ana i na node.

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy
Pono e lawe wale ʻia nā mea e hiki mai ana ma ke ʻano he hōʻoia-o-manaʻo, no ka mea, lilo ka noi i mea paʻakikī loa. Akā inā ʻoi aku ka mana o kāu waihona, pono ʻoe e noʻonoʻo e pili ana i ka hoʻohana ʻana i nā ʻenehana like.

E hoʻomaka kākou me kekahi mau ʻōlelo maʻalahi:

  • ʻO ka moʻolelo like mai ka waihona ʻOi aku ka maikaʻi e heluhelu hoʻokahi wale nō.
  • Nā moʻolelo mai ka waihona ʻOi aku ka maikaʻi o ka heluhelu ʻana i nā pūʻuluma mua o ka hoʻokahi.

I kēia manawa e ho'āʻo mākou e kūkulu i ka noi a mākou e pono ai.

pani 1

ʻIke loa, i ka hoʻomaka ʻana i ka recursion (i hea mākou me ka ʻole!) pono mākou e unuhi i nā moʻolelo o nā lau iā lākou iho ma muli o ka hoʻonohonoho o nā mea hōʻike mua:

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
  ...

Inā he mea ʻē aʻe i kekahi e mālama ʻia ka "set" ma ke ʻano he kaula a ʻaʻole he ʻano, a laila aia kahi wehewehe maʻalahi no kēia. Aia kahi hana "gluing" i kūkulu ʻia no nā kaula string_agg, ʻaʻole naʻe no nā papa kuhikuhi. ʻOiai ʻo ia maʻalahi e hoʻokō iā ʻoe iho.

pani 2

I kēia manawa e loaʻa iā mākou kahi hoʻonohonoho o nā ID ʻāpana e pono e heluhelu hou ʻia. Aneane i nā manawa a pau e hoʻopaʻi ʻia lākou i nā moʻolelo like ʻole o ka hoʻonohonoho kumu - pēlā mākou e hana ai hui iā lākou, ʻoiai e mālama ana i ka ʻike e pili ana i nā lau kumu.

Akā eia nā pilikia ʻekolu e kali nei iā mākou:

  1. ʻAʻole hiki i ka ʻāpana "subrecursive" o ka nīnau ke loaʻa nā hana aggregate me GROUP BY.
  2. ʻAʻole hiki ke kuhikuhi ʻia i kahi "papa" recursive ma kahi subquery pūnana.
  3. ʻAʻole hiki ke loaʻa kahi CTE i kahi noi ma ka ʻāpana recursive.

ʻO ka mea pōmaikaʻi, ua maʻalahi kēia mau pilikia āpau e hana a puni. E hoʻomaka kākou mai ka hopena.

CTE ma ka ʻāpana recursive

Eia nō ole hana:

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

A pela no ka hana ana, na ka makua ka mea i okoa!

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

Huli hoʻopaʻa ʻia e pili ana i kahi "papakaukau" recursive

Hmm... ʻAʻole hiki ke kiʻi ʻia kahi CTE recursive ma kahi subquery. Akā aia paha i loko o CTE! A hiki i kahi noi pūnana ke komo i kēia CTE!

GROUP BY loko recursion

He mea leʻaleʻa, akā ... Loaʻa iā mākou kahi ala maʻalahi e hoʻohālike i ka GROUP BY ka hoʻohana ʻana DISTINCT ON a me nā hana pukaaniani!

SELECT
  (rec).pid id
, string_agg(chld::text, ',') chld
FROM
  tree
WHERE
  (rec).pid IS NOT NULL
GROUP BY 1 -- не работает!

A penei ka hana ana!

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

I kēia manawa, ʻike mākou i ke kumu i hoʻololi ʻia ai ka ID helu i kikokikona - i hiki ke hoʻohui pū ʻia i ka hoʻokaʻawale ʻia e nā koma!

pani 3

No ka hope ʻaʻohe mea i koe:

  • heluhelu mākou i nā moʻolelo "ʻāpana" e pili ana i kahi pūʻulu o nā ID hui
  • hoʻohālikelike mākou i nā ʻāpana i unuhi ʻia me nā "set" o nā pepa kumu
  • "hoʻonui" i ke kaula hoʻonohonoho me ka hoʻohana ʻana 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;

Nā Antipatterns PostgreSQL: Pehea ka hohonu o ka lua rabbit? e hele kakou ma ka hierarchy
[nānā ma explain.tensor.ru]

Source: www.habr.com

Pākuʻi i ka manaʻo hoʻopuka