Написано мной по мотивам известной статьи Joe Celko 1996 г
с максимальной адаптацией под самый простой SQL на примере SQLite
Данный код не требует хранимых процедур и триггеров, и может быть легко
адаптирован практически для любой современной БД.
Для запуска и проверки достаточно: cat file.sql | sqlite3
/* tree-sets */
CREATE TABLE test (
id INTEGER PRIMARY KEY,
name VARCHAR(50),
salary FLOAT,
left INTEGER,
right INTEGER,
CHECK(left < right)
);
/*
Jerry
Bert
Chuck
Eddie
Mary
Donna
Fred
Max
*/
-- new Jerry
UPDATE test SET
left = CASE WHEN left > (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='' LIMIT 1)
THEN left + 2 ELSE left END,
right = CASE WHEN right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='' LIMIT 1)
THEN right + 2 ELSE right END
WHERE right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right) FROM test),1) FROM test WHERE name='' LIMIT 1);
INSERT INTO test (name, salary, left, right)
SELECT 'Jerry', 100,
(SELECT COALESCE(MAX(right)-2,(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='' LIMIT 1),
(SELECT COALESCE(MAX(right)-1,(SELECT MAX(right)+2 FROM test),2) FROM test WHERE name='' LIMIT 1)
FROM sqlite_master LIMIT 1;
-- new Bert
UPDATE test SET
left = CASE WHEN left > (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Jerry' LIMIT 1)
THEN left + 2 ELSE left END,
right = CASE WHEN right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Jerry' LIMIT 1)
THEN right + 2 ELSE right END
WHERE right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right) FROM test),1) FROM test WHERE name='Jerry' LIMIT 1);
INSERT INTO test (name, salary, left, right)
SELECT 'Bert', 90,
(SELECT COALESCE(MAX(right)-2,(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Jerry' LIMIT 1),
(SELECT COALESCE(MAX(right)-1,(SELECT MAX(right)+2 FROM test),2) FROM test WHERE name='Jerry' LIMIT 1)
FROM sqlite_master LIMIT 1;
-- new Chunk
UPDATE test SET
left = CASE WHEN left > (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Bert' LIMIT 1)
THEN left + 2 ELSE left END,
right = CASE WHEN right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Bert' LIMIT 1)
THEN right + 2 ELSE right END
WHERE right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right) FROM test),1) FROM test WHERE name='Bert' LIMIT 1);
INSERT INTO test (name, salary, left, right)
SELECT 'Chuck', 80,
(SELECT COALESCE(MAX(right)-2,(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Bert' LIMIT 1),
(SELECT COALESCE(MAX(right)-1,(SELECT MAX(right)+2 FROM test),2) FROM test WHERE name='Bert' LIMIT 1)
FROM sqlite_master LIMIT 1;
-- new Donna
UPDATE test SET
left = CASE WHEN left > (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Bert' LIMIT 1)
THEN left + 2 ELSE left END,
right = CASE WHEN right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Bert' LIMIT 1)
THEN right + 2 ELSE right END
WHERE right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right) FROM test),1) FROM test WHERE name='Bert' LIMIT 1);
INSERT INTO test (name, salary, left, right)
SELECT 'Donna', 80,
(SELECT COALESCE(MAX(right)-2,(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Bert' LIMIT 1),
(SELECT COALESCE(MAX(right)-1,(SELECT MAX(right)+2 FROM test),2) FROM test WHERE name='Bert' LIMIT 1)
FROM sqlite_master LIMIT 1;
-- new Fred
UPDATE test SET
left = CASE WHEN left > (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='' LIMIT 1)
THEN left + 2 ELSE left END,
right = CASE WHEN right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='' LIMIT 1)
THEN right + 2 ELSE right END
WHERE right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right) FROM test),1) FROM test WHERE name='' LIMIT 1);
INSERT INTO test (name, salary, left, right)
SELECT 'Fred', 100,
(SELECT COALESCE(MAX(right)-2,(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='' LIMIT 1),
(SELECT COALESCE(MAX(right)-1,(SELECT MAX(right)+2 FROM test),2) FROM test WHERE name='' LIMIT 1)
FROM sqlite_master LIMIT 1;
-- new Max
UPDATE test SET
left = CASE WHEN left > (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Fred' LIMIT 1)
THEN left + 2 ELSE left END,
right = CASE WHEN right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Fred' LIMIT 1)
THEN right + 2 ELSE right END
WHERE right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right) FROM test),1) FROM test WHERE name='Fred' LIMIT 1);
INSERT INTO test (name, salary, left, right)
SELECT 'Max', 90,
(SELECT COALESCE(MAX(right)-2,(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Fred' LIMIT 1),
(SELECT COALESCE(MAX(right)-1,(SELECT MAX(right)+2 FROM test),2) FROM test WHERE name='Fred' LIMIT 1)
FROM sqlite_master LIMIT 1;
-- new Eddie
UPDATE test SET
left = CASE WHEN left > (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Chuck' LIMIT 1)
THEN left + 2 ELSE left END,
right = CASE WHEN right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Chuck' LIMIT 1)
THEN right + 2 ELSE right END
WHERE right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right) FROM test),1) FROM test WHERE name='Chuck' LIMIT 1);
INSERT INTO test (name, salary, left, right)
SELECT 'Eddie', 90,
(SELECT COALESCE(MAX(right)-2,(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Chuck' LIMIT 1),
(SELECT COALESCE(MAX(right)-1,(SELECT MAX(right)+2 FROM test),2) FROM test WHERE name='Chuck' LIMIT 1)
FROM sqlite_master LIMIT 1;
-- new Mary
UPDATE test SET
left = CASE WHEN left > (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Chuck' LIMIT 1)
THEN left + 2 ELSE left END,
right = CASE WHEN right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Chuck' LIMIT 1)
THEN right + 2 ELSE right END
WHERE right >= (SELECT COALESCE(MAX(right),(SELECT MAX(right) FROM test),1) FROM test WHERE name='Chuck' LIMIT 1);
INSERT INTO test (name, salary, left, right)
SELECT 'Mary', 20,
(SELECT COALESCE(MAX(right)-2,(SELECT MAX(right)+1 FROM test),1) FROM test WHERE name='Chuck' LIMIT 1),
(SELECT COALESCE(MAX(right)-1,(SELECT MAX(right)+2 FROM test),2) FROM test WHERE name='Chuck' LIMIT 1)
FROM sqlite_master LIMIT 1;
.mode column
.headers on
.print
.print "=============="
.print "=== SOURCE ==="
.print "=============="
.print
SELECT test.*, right-left as size FROM test ORDER BY id;
.print
.print "======================"
.print "=== RECURSIVE VIEW ==="
.print "======================"
.print
WITH RECURSIVE V (lv, id, name, salary, left, right) AS (
SELECT 1,A.id,A.name,A.salary,A.left,A.right FROM test A
WHERE NOT EXISTS (
SELECT id FROM test B WHERE A.left BETWEEN B.left+1 AND B.right-1
)
UNION
SELECT V.lv+1, T.id, T.name, T.salary, T.left, T.right FROM test T
INNER JOIN V ON (T.left BETWEEN V.left+1 AND V.right-1)
LEFT JOIN test P
ON (P.left BETWEEN V.left+1 AND V.right-1)
AND (T.left BETWEEN P.left+1 AND P.right-1)
WHERE P.id IS NULL
ORDER BY 1 DESC
)
SELECT lv, REPLACE(HEX(ZEROBLOB(lv*2)),0x00,' ') || name FROM V;
.print
.print "===================="
.print "=== ALL CHILDREN ==="
.print "===================="
.print
SELECT * FROM test WHERE left = (right - 1);
.print
.print "========================================"
.print "=== ALL PARENTS OF 'EDDIE' FROM ROOT ==="
.print "========================================"
.print
SELECT T2.name, T1.name AS parent, (T1.right - T1.left) AS size
FROM test AS T1, test AS T2
WHERE (T2.left BETWEEN T1.left AND T1.right)
AND (T2.right BETWEEN T1.left AND T1.right)
AND (T2.name = 'Eddie')
ORDER BY 3 DESC;
.print
.print "========================================="
.print "=== LEVEL (1..N) OF 'EDDIE' FROM ROOT ==="
.print "========================================="
.print
SELECT T2.name, T1.name as parent, COUNT(*) AS level
FROM test AS T1, test AS T2
WHERE (T2.left BETWEEN T1.left AND T1.right)
AND (T2.right BETWEEN T1.left AND T1.right)
AND T2.name = 'Eddie'
ORDER BY 3 DESC;
.print
.print "============================"
.print "=== LEVELS FOR ALL NODES ==="
.print "============================"
.print
SELECT T2.name, COUNT(*) AS level
FROM test AS T1, test AS T2
WHERE (T2.left BETWEEN T1.left AND T1.right)
GROUP BY 1 ORDER BY 2;
.print
.print "==========================="
.print "=== CALC SALARY FOR ALL ==="
.print "==========================="
.print
SELECT T1.name, SUM(T2.salary) AS salary
FROM test AS T1, test AS T2
WHERE T2.left BETWEEN T1.left AND T1.right
GROUP BY 1;
.print
.print "=============================="
.print "=== DELETE SUBTREE 'CHUCK' ==="
.print "=============================="
.print
DELETE FROM test WHERE left BETWEEN 3 and 8;
UPDATE test SET
left = CASE
WHEN left > 3 THEN left - (8 - 3 + 1)
ELSE left
END,
right = CASE
WHEN right > 3 THEN right - (8 - 3 + 1)
ELSE right
END;
SELECT test.*, right-left as size FROM test ORDER BY id;
.print
.print "==========================="
.print "=== DELETE NODE 'CHUCK' ==="
.print "==========================="
.print
DELETE FROM test WHERE name='Chuck';
UPDATE test SET
left = CASE
WHEN (left BETWEEN 3 AND 8) THEN left - 1
WHEN (left > 8) THEN left - 2 ELSE left
END,
right = CASE
WHEN (right BETWEEN 3 AND 8) THEN right - 1
WHEN (right > 8) THEN right - 2 ELSE right
END
WHERE left >= 3 or right >= 3;
SELECT test.*, right-left as size FROM test ORDER BY id;
-- eof
SQL Справочник v0.05 © 2007-2026 Igor Salnikov aka SunDoctor