Zentzu honetan DBMSk printzipio berdinetan lan egiten du - "Zulatzeko esan zidaten, beraz, zulatzen dut". Zure eskaerak aldameneko prozesuak moteltzeaz gain, prozesadorearen baliabideak etengabe hartuz, datu-base osoa ere "jaregin" dezake, eskuragarri dagoen memoria guztia "jaten". errekurtsio infinituaren aurkako babesa - Garatzailearen beraren erantzukizuna.
PostgreSQL-n, kontsulta errekurtsiboak erabiltzeko gaitasuna WITH RECURSIVE
Ez idatzi kontsulta errekurtsiboak
Eta idatzi ez-errekurtsiboak. Adeitasunez, Zure K.O.
Izan ere, PostgreSQL-k erabil ditzakezun funtzionalitate asko eskaintzen ditu ez errekurtsioa aplikatu.
Erabili arazoaren ikuspegi oso desberdina
Batzuetan arazoa "alde ezberdinetik" begiratu dezakezu. Horrelako egoera baten adibide bat jarri nuen artikuluan
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;
Eskaera hau matematika adituen aukera batekin ordezkatu daiteke:
WITH src AS (
SELECT unnest('{2,3,5,7,11,13,17,19}'::integer[]) prime
)
SELECT
exp(sum(ln(prime)))::integer val
FROM
src;
Erabili generate_series begizten ordez
Demagun kate baterako aurrizki posible guztiak sortzeko zereginaren aurrean gaudela '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;
Ziur hemen errekurtsioa behar duzula?... Erabiltzen baduzu LATERAL
и generate_series
, orduan ez duzu CTE ere beharko:
SELECT
substr(str, 1, ln) str
FROM
(VALUES('abcdefgh')) T(str)
, LATERAL(
SELECT generate_series(length(str), 1, -1) ln
) X;
Datu-basearen egitura aldatu
Adibidez, foroko mezuen taula bat duzu, nork erantzun dion nori edo hari bat
CREATE TABLE message(
message_id
uuid
PRIMARY KEY
, reply_to
uuid
REFERENCES message
, body
text
);
CREATE INDEX ON message(reply_to);
Beno, gai bateko mezu guztiak deskargatzeko eskaera arrunt batek honelako itxura du:
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;
Baina beti erroko mezutik gai osoa behar dugunez, zergatik ez dugu gehitu bere ID sarrera bakoitzean automatikoa?
-- добавим поле с общим идентификатором темы и индекс на него
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();
Orain gure kontsulta errekurtsibo osoa honetara murriztu daiteke:
SELECT
*
FROM
message
WHERE
theme_id = $1;
Erabili aplikatutako "mugatzaileak"
Arrazoiren bategatik datu-basearen egitura aldatu ezin badugu, ikus dezagun zertan fida gaitezkeen, datuetan errore bat egoteak ere errekurtsio amaigabea ekar ez dezan.
Errekurtsio-sakonera-kontagailua
Errekurtsio-urrats bakoitzean kontagailua bat handitzen dugu, jakina desegokia den muga batera iritsi arte:
WITH RECURSIVE T AS (
SELECT
0 i
...
UNION ALL
SELECT
i + 1
...
WHERE
T.i < 64 -- предел
)
Pro: Begizta egiten saiatzen garenean, oraindik ez dugu "sakonean" zehaztutako iterazioen muga baino gehiago egingo.
Cons: Ez dago bermerik erregistro bera berriro prozesatuko ez dugunik - adibidez, 15 eta 25eko sakoneran, eta gero +10 bakoitzean. Eta inork ez zuen ezer hitzeman “zabaltasunari buruz”.
Formalki, halako errekurtsio bat ez da infinitua izango, baina urrats bakoitzean erregistro kopurua esponentzialki handitzen bada, denok dakigu nola bukatzen den...
"Bidearen" zaindaria
Errekurtsio-bidean zehar aurkitu ditugun objektu-identifikatzaile guztiak txandaka gehitzen ditugu array batean, "bide" bakarra dena:
WITH RECURSIVE T AS (
SELECT
ARRAY[id] path
...
UNION ALL
SELECT
path || id
...
WHERE
id <> ALL(T.path) -- не совпадает ни с одним из
)
Pro: Datuetan ziklo bat badago, ez dugu erregistro bera behin eta berriz prozesatuko bide berean.
Cons: Baina, aldi berean, literalki erregistro guztiak saihestu ditzakegu geure burua errepikatu gabe.
Bidearen luzera muga
Sakonera ulertezinean errekurtsioaren egoera saihesteko, aurreko bi metodoak konbina ditzakegu. Edo, alferrikako eremuak onartu nahi ez baditugu, osatu errekurtsioarekin jarraitzeko baldintza bidearen luzeraren estimazio batekin:
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
)
Aukeratu zure gustuko metodoa!
Iturria: www.habr.com