Ako na rekurziu v databázach

2008/08/07

Čísla od 1 po n

Vytvorte tabulku, ktora obsahuje v riadkoch cisla od 1 po N.

V Pascale by to bol for:

for i:=1 to N do begin
  writeln(i)
end

Lenze my nemame for. Mame len rekurziu. My vsak vieme kazdy prvok v postupnosti odvodit z predosleho prvku.

s_0 = 0
s_i = s_i-1 + 1

Napisane vseobecne

s(i) = 0, ak i = 0
s(i) = s(i-1) + 1
s(x) = 0, ak x = 0
s(x) = s(x-1) + 1

Toto vieme mechanicky previest na SELECT

WITH s(x) as
(
SELECT 0 FROM SYSIBM.SYSDUMMY1
  UNION ALL
SELECT x + 1 FROM s
WHERE x < 1000
)
SELECT * FROM s

Fibonacci

Fibonacciho postupnosť je definované rekurzívne:

F(0) = 0
F(1) = 1
F(x) = F(x - 2) + F(x - 1)
...
F(x + 1) = F(x - 1) + F(x)
F(x + 2) = F(x) + F(x + 1)
F(x + 3) = F(x + 1) + F(x + 2)

Ak sa na to pozrieme z iného uhla, tak:

Pamätajme si teda dva stĺpce, čo bude vyzerať takto:

Poradie Aktuálny prvok Nasledovný prvok
0 0 1
1 1 1
2 1 2
3 2 3
4 3 5
5 5 8

Toto vieme mechanicky previesť na dopyt:

WITH RECURSIVE FIBONACCI(`current`, `next`) AS
(
  SELECT 0, 1
    UNION ALL
  SELECT `next`, `current` + `next`
  FROM FIBONACCI
  WHERE `next` < 10
)
SELECT * FROM FIBONACCI

Podmienka next < 10 len obmedzuje hĺbku rekurzie, predsa len nemôžeme mať nekonečné tabuľky.

Hierarchie

Majme tabuľku súborového systému s hierarchiou:

CREATE TABLE filesystem (
  id INTEGER NOT NULL,
  name VARCHAR(255) NOT NULL,
  parent_id INTEGER
);

A hodnoty:

INSERT INTO filesystem VALUES
(0, '/', NULL),
(1, 'home', 0),
(2, 'novotnyr', 1),
(3, 'armstrong', 1),
(4, 'public_html', 2),
(5, 'tmp', 0);

Chceme vypísať pre každý adresár celú jeho cestu:

/home/
/home/armstrong/
/home/novotnyr/
/home/novotnyr/public_html/
/tmp/

Idea je nasledovná:

Pa-matematický zápis by vyzeral:

fullpath(0) = '/'
fullpath(x) = x.`name` + '/' + fullpath(resolvefile(x).parent_id)  

V databázovom prípade však musíme byť presnejší: funkcia musí brať viacero parametrov:

Funkcia resolvefile sa však v databázovom svete preloží na join-om tabuľky na budovanú hierarchiu.

WITH RECURSIVE fullpath (id, name, path) AS
(
  SELECT id, name, CAST('/' AS CHAR(200))
    FROM filesystem
    WHERE parent_id IS NULL
  UNION ALL
  SELECT filesystem.id, filesystem.name, CONCAT(fullpath.path, filesystem.name, '/')
    FROM filesystem
	JOIN fullpath ON fullpath.id = filesystem.parent_id
)
SELECT * FROM fullpath ORDER BY path;
>> Home