在PostgreSQL表中查找循环引用?

在PostgreSQL表中查找循环引用?,sql,postgresql,plpgsql,postgresql-9.1,circular-reference,Sql,Postgresql,Plpgsql,Postgresql 9.1,Circular Reference,我有一个带有属性(ID int、SourceID int、TargetID int、TargetType int)的表 ID源ID目标ID --------------------- 1 123 456 2 456 789 3 1 123 4 456 1 5 2 1 我想找出所有的循环引用。我想为此编写PL/pgsql函数 此处ID 4=456 1 123 456的循环参考 我想找

我有一个带有属性(ID int、SourceID int、TargetID int、TargetType int)的表

ID源ID目标ID --------------------- 1 123 456 2 456 789 3 1 123 4 456 1 5 2 1 我想找出所有的循环引用。我想为此编写PL/pgsql函数

此处ID 4=456 1 123 456的循环参考


我想找到这样的例子。有谁能建议我怎么做

这可以通过下面的递归函数完成。函数使用

int数组
arr
的第一个元素是
id
。数组的其余部分包含连续引用
source->target.

如果数组的第二个和最后一个元素相等,则找到循环引用。(一)

我们必须寻找内部循环引用并消除它们(否则将导致堆栈溢出)。(二)

这与(SQLServer版本)类似,它使用employee/manager表的DITATIC示例提出了问题

除了检测循环引用的函数之外,我还对有一个触发器来防止在数据库中插入或更新此类数据感兴趣

以下是我根据的答案改编的代码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();

我假设这适用于任何程度的分离,而不是像您的示例中那样仅适用于1度。@xQbert..是的..请您对此提出建议听起来像是一个图遍历问题-可能是通过递归CTE完成的。
create extension intarray;
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
-- 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 employee SET ID = ID;