我有一个包含属性的表(ID int,SourceID int,TargetID int,TargetType int)
ID SourceID TargetID --------------------- 1 123 456 2 456 789 3 1 123 4 456 1 5 2 1
我想找出所有循环引用。我想为此编写PL / pgsql函数。
此处ID 4 = 456 1 123 456
的循环参考我想找到这样的例子。任何人都可以建议我如何进行此操作。
答案 0 :(得分:1)
这可以通过下面的递归函数来完成。该函数使用intarray extension.
create extension intarray;
int数组arr
的第一个元素是id
。数组的其余部分包含连续的引用source -> target.
如果数组的第二个和最后一个元素相等,则找到循环引用。 (1)
我们必须寻找内部循环引用并消除它们(或者我们将完成堆栈溢出)。 (2)
create or replace function find_cref(arr int[])
returns setof int[] language plpgsql
as $$
declare
vlen int = #arr;
vtarget int;
begin
if arr[2] = arr[vlen] then -- (1)
return query select arr;
else
if #uniq(sort(subarray(arr, 2)))+ 1 = vlen then -- (2)
for vtarget in
select target from the_table where source = arr[vlen]
loop
return query select find_cref (arr+ vtarget);
end loop;
end if;
end if;
end $$;
select c[1] id, subarray(c, 2) cref
from (
select find_cref(array[id, source, target]) c
from the_table) x
答案 1 :(得分:0)
这类似于How to prevent a self-referencing table from becoming circular(SQL-Server版本),它使用了员工/经理表的didatic示例提出了问题。
除了检测循环引用的函数之外,我感兴趣的是有一个触发器来阻止在数据库中插入或更新这些数据。
以下是我根据mellamokb回答的代码PLpgSQL / PostgreSQL:
-- example of table structure
CREATE TABLE employee (
ID INTEGER NOT NULL,
ManagerID INTEGER,
CONSTRAINT a PRIMARY KEY (ID),
CONSTRAINT b FOREIGN KEY (ManagerID) REFERENCES employee (ID))
-- function to be used inside trigger
CREATE OR REPLACE FUNCTION CircularReference()
RETURNS TRIGGER
AS
$$
DECLARE _CircularReferenceExists BIT(1) := 0;
BEGIN
WITH RECURSIVE cte AS (
SELECT E.* FROM employee E WHERE E.ID = new.ManagerID
UNION -- union instead of union all to prevent infinite looping.
SELECT E.* FROM employee E JOIN cte C ON C.ManagerID = E.ID AND E.ID <> new.ManagerID
)
SELECT count(*) INTO _CircularReferenceExists FROM cte C WHERE C.ManagerID = new.ManagerID;
IF (SELECT _CircularReferenceExists <> B'0')
THEN
RAISE EXCEPTION 'Circular Reference found: ManagerID = %', new.ManagerID;
END IF;
RETURN NEW;
END
$$ LANGUAGE plpgsql;
-- trigger
CREATE TRIGGER CircularReference
AFTER INSERT OR UPDATE
ON employee
FOR EACH ROW
EXECUTE PROCEDURE CircularReference();
如果数据库中已有数据,则只需创建触发器并使用UPDATE语句使其开始检测循环引用。
UPDATE employee SET ID = ID;