Ang DBMS sa bagay na ito ay gumagana sa parehong mga prinsipyo - "Sinabi nila sa akin na maghukay, kaya hinukay ko". Hindi lamang maaaring pabagalin ng iyong kahilingan ang mga kalapit na proseso, patuloy na kumukuha ng mga mapagkukunan ng processor, ngunit "i-drop" din ang buong database, "kinakain" ang lahat ng magagamit na memorya. proteksyon laban sa walang katapusang recursion - ang responsibilidad ng developer mismo.
Sa PostgreSQL, ang kakayahang gumamit ng mga recursive na query sa pamamagitan ng WITH RECURSIVE
Huwag magsulat ng mga recursive na query
At sumulat ng mga hindi recursive. Taos-puso, ang iyong K.O.
Sa katunayan, ang PostgreSQL ay nagbibigay ng napakaraming functionality na magagamit mo hindi ilapat ang recursion.
Gumamit ng isang panimula na naiibang diskarte sa problema
Minsan maaari mong tingnan ang problema mula sa "iba't ibang panig". Nagbigay ako ng isang halimbawa ng ganoong sitwasyon sa artikulo
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;
Maaaring palitan ang kahilingang ito ng opsyon mula sa mga eksperto sa matematika:
WITH src AS (
SELECT unnest('{2,3,5,7,11,13,17,19}'::integer[]) prime
)
SELECT
exp(sum(ln(prime)))::integer val
FROM
src;
Gumamit ng generate_series sa halip na mga loop
Sabihin nating nahaharap tayo sa gawain ng pagbuo ng lahat ng posibleng prefix para sa isang string '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;
Sigurado ka bang kailangan mo ng recursion dito?.. Kung gagamitin mo LATERAL
ΠΈ generate_series
, pagkatapos ay hindi mo na kakailanganin ang CTE:
SELECT
substr(str, 1, ln) str
FROM
(VALUES('abcdefgh')) T(str)
, LATERAL(
SELECT generate_series(length(str), 1, -1) ln
) X;
Baguhin ang istraktura ng database
Halimbawa, mayroon kang talahanayan ng mga mensahe sa forum na may mga koneksyon mula sa kung kanino tumugon, o isang thread sa loob
CREATE TABLE message(
message_id
uuid
PRIMARY KEY
, reply_to
uuid
REFERENCES message
, body
text
);
CREATE INDEX ON message(reply_to);
Well, ang isang karaniwang kahilingan na i-download ang lahat ng mga mensahe sa isang paksa ay mukhang ganito:
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;
Ngunit dahil lagi nating kailangan ang buong paksa mula sa ugat na mensahe, kung gayon bakit hindi natin idagdag ang ID nito sa bawat entry awtomatiko?
-- Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΠΏΠΎΠ»Π΅ Ρ ΠΎΠ±ΡΠΈΠΌ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡΠΎΠΌ ΡΠ΅ΠΌΡ ΠΈ ΠΈΠ½Π΄Π΅ΠΊΡ Π½Π° Π½Π΅Π³ΠΎ
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();
Ngayon ang aming buong recursive query ay maaaring bawasan sa ito lamang:
SELECT
*
FROM
message
WHERE
theme_id = $1;
Gumamit ng inilapat na "mga limitasyon"
Kung hindi natin mababago ang istraktura ng database sa ilang kadahilanan, tingnan natin kung ano ang maaasahan natin upang kahit na ang pagkakaroon ng isang error sa data ay hindi humantong sa walang katapusang recursion.
Recursion depth counter
Dinadagdagan lang namin ng isa ang counter sa bawat hakbang ng recursion hanggang sa maabot namin ang limitasyon na itinuturing naming malinaw na hindi sapat:
WITH RECURSIVE T AS (
SELECT
0 i
...
UNION ALL
SELECT
i + 1
...
WHERE
T.i < 64 -- ΠΏΡΠ΅Π΄Π΅Π»
)
Pro: Kapag sinubukan naming mag-loop, gagawa pa rin kami ng hindi hihigit sa tinukoy na limitasyon ng mga pag-ulit "sa lalim".
cons: Walang garantiya na hindi namin muling ipoproseso ang parehong record - halimbawa, sa lalim na 15 at 25, at pagkatapos ay tuwing +10. At walang nangako ng anuman tungkol sa "lawak".
Sa pormal na paraan, ang gayong recursion ay hindi magiging walang hanggan, ngunit kung sa bawat hakbang ang bilang ng mga talaan ay tumaas nang husto, alam nating lahat kung paano ito nagtatapos...
Tagapangalaga ng "landas"
Halili naming idinaragdag ang lahat ng object identifier na nakatagpo namin kasama ang recursion path sa isang array, na isang natatanging "path" dito:
WITH RECURSIVE T AS (
SELECT
ARRAY[id] path
...
UNION ALL
SELECT
path || id
...
WHERE
id <> ALL(T.path) -- Π½Π΅ ΡΠΎΠ²ΠΏΠ°Π΄Π°Π΅Ρ Π½ΠΈ Ρ ΠΎΠ΄Π½ΠΈΠΌ ΠΈΠ·
)
Pro: Kung mayroong isang cycle sa data, talagang hindi namin ipoproseso ang parehong record nang paulit-ulit sa loob ng parehong landas.
cons: Ngunit sa parehong oras, maaari nating literal na i-bypass ang lahat ng mga talaan nang hindi inuulit ang ating sarili.
Limitasyon sa Haba ng Landas
Upang maiwasan ang sitwasyon ng recursion na "paglaboy-laboy" sa isang hindi maintindihan na lalim, maaari nating pagsamahin ang dalawang naunang pamamaraan. O, kung ayaw naming suportahan ang mga hindi kinakailangang field, dagdagan ang kundisyon para sa pagpapatuloy ng recursion na may pagtatantya ng haba ng path:
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
)
Pumili ng paraan sa iyong panlasa!
Pinagmulan: www.habr.com