â njĂ« mekanizĂ«m shumĂ« i fuqishĂ«m dhe tĂ« dobishĂ«m, nĂ«se veprimet e njĂ«jta bĂ«hen 'thellĂ«' mbi tĂ« dhĂ«nat e lidhura. Por rekursioni i pakontrolluar Ă«shtĂ« njĂ« e keqe qĂ« mund tĂ« çojĂ« ose nĂ« ekzekutimin e pafund tĂ« procesit, ose (çka ndodh mĂ« shpesh) nĂ« âshkrirjenâ e gjithĂ« memories sĂ« disponueshme.

Baza e tĂ« dhĂ«nave nĂ« kĂ«tĂ« drejtim operojnĂ« sipas tĂ« njĂ«jtave principe â 'thanĂ« tĂ« gĂ«rmoj, unĂ« po gĂ«rmoj' . KĂ«rkesa juaj mund tĂ« ngadalĂ«sojĂ« jo vetĂ«m proceset pĂ«rreth, duke zĂ«nĂ« vazhdimisht burimet e procesorit, por gjithashtu mund tĂ« 'ngrejĂ«' tĂ« gjithĂ« bazĂ«n,e cila 'ha' gjithĂ« memorien e disponueshme. Prandaj mbrojtja nga rekursioni i pafund Ă«shtĂ« detyrĂ« e zhvilluesit.
Në PostgreSQL, mundësia për të përdorur kërkesa rekursive përmes u shfaq që në kohët e vona të versionit 8.4, por ende mund të hasen rregullisht 'kërkesa të pambrojtura' potencialisht të cenueshme. Si të shpëtojmë nga probleme të tilla?
Mos shkruani kërkesa rekursive
Por shkruani të pa-rekursive. Me respekt, K.O. juaj.
Në të vërtetë, PostgreSQL ofron një numër të konsiderueshëm funksionaliteti, që mund të shfrytëzohet për të jo aplikuar rekursioni.
Përdorni një qasje krejt tjetër për problemin
NdonjĂ«herĂ« mund tĂ« shikoni thjesht problemin 'nga njĂ« kĂ«nd tjetĂ«r'. NjĂ« shembull i tillĂ« unĂ« e kam pĂ«rmendur nĂ« artikullin â shumĂ«zimi i njĂ« seti numrash pa pĂ«rdorur funksione tĂ« agregimit tĂ« personalizuara:
ME REKURSIVE src SI (
SELECT '{2,3,5,7,11,13,17,19}'::integer[] arr
)
, T(i, val) SI (
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 -- renditje e rezultatit final
i DESC
LIMIT 1;Kjo kërkesë mund të zëvendësohet me një variant nga njohësit e matematikës:
ME src SI (
SELECT unnest('{2,3,5,7,11,13,17,19}'::integer[]) prime
)
SELECT
exp(sum(ln(prime)))::integer val
FROM
src;Përdorni generate_series në vend të cikleve
Le të themi se na qëndron para një problemi për të gjeneruar të gjitha prefikset e mundshme për vargun 'abcdefgh':
ME REKURSIVE T SI (
SELECT 'abcdefgh' str
UNION ALL
SELECT
substr(str, 1, length(str) - 1)
FROM
T
WHERE
length(str) > 1
)
TABELA T; A është e nevojshme rikursioni këtu?.. Nëse shfrytëzojmë LATERAL dhe generate_series, atëherë as CTE nuk do të nevojiten:
SELECT
substr(str, 1, ln) str
FROM
(VALUES('abcdefgh')) T(str)
, LATERAL(
SELECT generate_series(length(str), 1, -1) ln
) X;Ndryshoni strukturën e BDs
Për shembull, keni një tabelë mesazhesh në forum me lidhjet se kush iu përgjigj kujt ose tema në :
KRIJONI TABELEN mesazh(
mesazh_id
uuid
KLAVĂ E VETME
, përgjigje_në
uuid
REFERENCà në mesazh
, trupi
tekst
);
KRIJONI INDEKSE Nà mesazh(përgjigje_në); 
Një pyetje tipike për të ngarkuar të gjitha mesazhet për një temë duket afërsisht kështu:
ME RECURSIVE T AS (
ZGJEDH
*
NGA
mesazh
KU
mesazh_id = $1
BASHKOHU TĂ GJITHA
ZGJEDH
m.*
NGA
T
BASHKOHUNI
mesazh m
Nà m.përgjigje_në = T.mesazh_id
)
TABELA T;Por, për shkak se gjithmonë na nevojitet e gjithë tema nga mesazhi rrënjor, pse të mos shtojmë identifikatorin e tij në çdo shënim automatikisht?
-- shtojmë një fushë me identifikatorin e përbashkët të temës dhe një indeks për të
MODIFIKOHEN TABELA mesazh
SHTO FUSHĂN theme_id uuid;
KRIJONI INDEKSE NĂ mesazh(theme_id);
-- inicializojmë identifikatorin e temës në trigger gjatë insertimit
KRIJONI OSE ZĂVENDĂSONI FUNKSIONIN ins() KTHEN TRIGGER AS $$
FILLIM
NEW.theme_id = RASTISHT
NĂSE NEW.pĂ«rgjigje_nĂ« ĂSHTĂ NULL ATĂHERĂ NEW.mesazh_id -- e marrim nga ngjarja fillestare
PĂRNDYSHĂ ( -- ose nga mesazhi qĂ« pĂ«rgjigjemi
ZGJEDH
theme_id
NGA
mesazh
KU
mesazh_id = NEW.përgjigje_në
)
MARRIN;
KTHEN NEW;
FUND;
$$ GJUHA plpgsql;
KRIJONI TRIGGER ins PARA INSERTIT
NĂ mesazh
PĂR ĂDO RRESHT
EKZEKUTO PROCEDURĂN ins(); 
Tani, gjithë pyetja jonë rekurzive mund të reduktohet vetëm në këtë:
ZGJEDH
*
NGA
mesazh
KU
theme_id = $1;Përdorni "kufizues" aplikativë
Nëse për ndonjë arsye nuk mund të ndryshojmë strukturën e bazës, le të shohim se çfarë mund të mbështetem, në mënyrë që ndonjë gabim në të dhëna të mos çojë në ekzekutimin e pafund të rekursivitetit.
Numëruesi "thellësisë" së rekursivitetit
Thjesht rrisim numëruesin me një në çdo hap të rekursivitetit deri në momentin që arrijmë kufirin, të cilin e konsiderojmë si të papranueshëm:
ME RECURSIVE T AS (
ZGJEDH
0 i
...
BASHKOHU TĂ GJITHA
ZGJEDH
i + 1
...
KU
T.i < 64 -- kufiri
) Pro: Në përpjekjen për t'u cikluar, ne gjithsesi do të bëjmë jo më shumë se kufiri i përcaktuar i iteracioneve "thellë".
Kundra: Nuk ka garanci se nuk do tĂ« pĂ«rpunojmĂ« pĂ«rsĂ«ri tĂ« njĂ«jtĂ«n shĂ«nim â pĂ«r shembull, nĂ« thellĂ«sinĂ« 15 dhe 25, dhe mĂ« pas çdo +10. Edhe pĂ«r "gjerĂ«" asnjĂ« gjĂ« nuk u premtua.
Formalisht, një kështu rekurzivitet nuk do të jetë i pafund, por nëse në çdo hap numri i shënimeve rritet eksponencialisht, ne të gjithë e dimë se çfarë ndodh...
Ruajtësi "rrugës"
RadhitshĂ«m shtojmĂ« tĂ« gjitha identifikatorĂ«t qĂ« na takojnĂ« nĂ« rrugĂ«n e rekursivitetit nĂ« njĂ« ĐŒĐ°ŃŃĐžĐČ qĂ« Ă«shtĂ« "rruga" unike deri te ai:
ME RECURSIVE T AS (
ZGJEDH
ARRAY[id] rruga
...
BASHKOHU TĂ GJITHA
ZGJEDH
rruga || id
...
KU
id T.rruga -- nuk përputhet me asnjë prej
) Pro: Nëse ka një cikël në të dhëna, ne me siguri nuk do ta përpunojmë sërish të njëjtën regjistër brenda një rruge.
Kundra: Por megjithatë mund të kalojmë përmes të gjitha regjistrimeve, pa u përsëritur.
Kufizimi i gjatë të rrugës
Për të shmangur situatën e "wanderings" në thellësi të paqartë recursioni, ne mund të kombinojmë dy metodat e mëparshme. Ose, nëse nuk duam të mbajmë fusha të panevojshme, ta plotësojmë kushtin për vazhdimin e recursioni me një vlerësim të gjatë të rrugës:
ME RECURSIVE T SI (
SELECT
ARRAY[id] path
...
UNION ALL
SELECT
path || id
...
KU
id ALL(T.path) DHE
array_length(T.path, 1) < 10
) Zgjidhni metodën sipas shijes tuaj!
Burimi: habr.com
