Ниже приведены различные варианты работы с "рекурсивными" таблицами через хранимые процедуры. На входе: таблица PARTS (PART_ID, PART_PID...) /* Получить все субэлементы */ CREATE PROCEDURE PARTS_INSIDE ( A_PART_ID INTEGER NOT NULL ) RETURNS ( B_PART_ID INTEGER ) AS DECLARE VARIABLE X INTEGER; BEGIN SELECT PART_ID FROM PARTS WHERE PART_ID=:A_PART_ID INTO :B_PART_ID; IF (NOT B_PART_ID IS NULL) THEN SUSPEND; ELSE EXCEPTION E_INVALID_VALUE 'Part not found: '||A_PART_ID; FOR SELECT PART_ID FROM PARTS WHERE PART_PID=:A_PART_ID INTO :X DO BEGIN FOR SELECT B_PART_ID FROM PARTS_INSIDE(:X) INTO :B_PART_ID DO SUSPEND; END END На входе: таблица TOPICS (TOPIC_ID, TOPIC_PID, TOPIC_CTYPE...) /* Получить корневой элемент для узла дерева */ CREATE PROCEDURE PART_ROOT ( TOPIC_ID INTEGER NOT NULL ) RETURNS ( CTYPE INTEGER ) AS DECLARE VARIABLE PID INTEGER; BEGIN CTYPE=0; SELECT TOPIC_PID, TOPIC_CTYPE FROM TOPICS WHERE TOPIC_ID=:TOPIC_ID INTO :PID, :CTYPE; IF (NOT PID IS NULL) THEN EXECUTE PROCEDURE TOPIC_ROOTTYPE(:PID) RETURNING_VALUES :CTYPE; SUSPEND; END На входе: таблица USAGES (USAGE_ID, USAGE_PID, USAGE_NAME...) /* Получить полный путь для заданного ID */ CREATE PROCEDURE USG_PATH ( USAGE_ID INTEGER ) RETURNS ( STRPATH VARCHAR(1024) ) AS DECLARE VARIABLE USAGE_NAME VARCHAR(250); DECLARE VARIABLE USAGE_PID INTEGER; BEGIN IF (NOT USAGE_ID IS NULL) THEN BEGIN SELECT USAGE_PID, USAGE_NAME FROM USAGES WHERE USAGE_ID=:USAGE_ID INTO :USAGE_PID, :USAGE_NAME; SELECT STRPATH || '/' || :USAGE_NAME FROM USG_PATH(:USAGE_PID) INTO :STRPATH; SUSPEND; END ELSE BEGIN STRPATH=''; SUSPEND; END END /* Получить все пути единым списком */ CREATE PROCEDURE USAGE_LIST RETURNS ( USAGE_ID INTEGER, USAGE_PID INTEGER, USAGE_RW XBOOL, USAGE_CODE XCODE, USAGE_PATH VARCHAR(1024) ) AS BEGIN FOR SELECT USAGE_ID, USAGE_PID, USAGE_RW, USAGE_CODE FROM USAGES INTO :USAGE_ID, :USAGE_PID, :USAGE_RW, :USAGE_CODE DO BEGIN SELECT STRPATH FROM USG_PATH(:USAGE_ID) INTO :USAGE_PATH; SUSPEND; END END /* Получить все конечные точки группы */ CREATE PROCEDURE USG_INSIDE ( A_USAGE_ID INTEGER NOT NULL ) RETURNS ( B_USAGE_ID INTEGER ) AS DECLARE VARIABLE X_USAGE_ID INTEGER; BEGIN SELECT USAGE_ID FROM USAGES WHERE USAGE_ID=:A_USAGE_ID INTO :B_USAGE_ID; IF (NOT B_USAGE_ID IS NULL) THEN SUSPEND; ELSE EXCEPTION E_INVALID_VALUE 'Usage not found: '||A_USAGE_ID; FOR SELECT USAGE_ID FROM USAGES WHERE USAGE_PID=:A_USAGE_ID INTO :X_USAGE_ID DO BEGIN FOR SELECT B_USAGE_ID FROM USG_INSIDE(:X_USAGE_ID) INTO :B_USAGE_ID DO SUSPEND; END END /* Получить все родительские группы для точки */ CREATE PROCEDURE USG_OUTSIDE ( A_USAGE_ID INTEGER NOT NULL ) RETURNS ( B_USAGE_ID INTEGER ) AS DECLARE VARIABLE X_USAGE_ID INTEGER; BEGIN SELECT USAGE_ID FROM USAGES WHERE USAGE_ID=:A_USAGE_ID INTO :B_USAGE_ID; IF (NOT B_USAGE_ID IS NULL) THEN SUSPEND; ELSE EXCEPTION E_INVALID_VALUE 'Usage not found: '||A_USAGE_ID; FOR SELECT USAGE_PID FROM USAGES WHERE USAGE_ID=:A_USAGE_ID AND USAGE_PID IS NOT NULL INTO :X_USAGE_ID DO BEGIN FOR SELECT B_USAGE_ID FROM USG_OUTSIDE(:X_USAGE_ID) INTO :B_USAGE_ID DO SUSPEND; END END /* Получить все точки в цепочке (можно сделать одним запросом WITH) */ CREATE PROCEDURE USG_FULLSIDE ( /* B_USAGE_ID:A_USAGE_ID */ A_USAGE_ID INTEGER NOT NULL ) RETURNS ( B_USAGE_ID INTEGER ) AS BEGIN FOR SELECT B_USAGE_ID FROM USG_INSIDE(:A_USAGE_ID) UNION SELECT B_USAGE_ID FROM USG_OUTSIDE(:A_USAGE_ID) INTO :B_USAGE_ID DO SUSPEND; END /* Получить разницу между двуми деревьями */ CREATE PROCEDURE USG_DIFF ( USAGE_ID1 INTEGER NOT NULL, USAGE_ID2 INTEGER NOT NULL ) RETURNS ( USAGE_ID INTEGER ) AS DECLARE VARIABLE Q INTEGER; BEGIN FOR SELECT X.B_USAGE_ID, 1 AS K FROM USG_INSIDE(:USAGE_ID1) X LEFT JOIN (SELECT B_USAGE_ID, 2 AS K FROM USG_INSIDE(:USAGE_ID2)) Y ON X.B_USAGE_ID=Y.B_USAGE_ID WHERE K IS NULL INTO :USAGE_ID, :Q DO SUSPEND; END
SQL Справочник v0.05 © 2007-2025 Igor Salnikov aka SunDoctor