在 PostgreSQL 表中查找循环引用?

问题描述 投票:0回答:3

我有一张带有属性的表(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

我想找到这样的例子。谁能建议我如何进行此操作。

sql postgresql plpgsql postgresql-9.1 circular-reference
3个回答
4
投票

这类似于如何防止自引用表变成循环(SQL-Server 版本),它使用员工/经理表的说教示例提出了问题。

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

这是我改编自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; 

1
投票

这可以通过下面的递归函数来完成。该函数使用 intarray 扩展。

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

0
投票
CREATE TABLE members (
  id serial PRIMARY KEY,
  reports_to_id int REFERENCES members(id) ON DELETE RESTRICT
);

CREATE OR REPLACE FUNCTION circular_reference() RETURNS trigger AS $$
  DECLARE circular_ref_exists boolean := false;
    BEGIN
    IF (NEW.reports_to_id = NEW.id)
    THEN
      RAISE EXCEPTION 'A self reference has been detected';
    END IF;

    WITH RECURSIVE cte AS (
      SELECT * FROM members
      WHERE members.id = NEW.reports_to_id
      UNION
      SELECT members.* FROM cte
      INNER JOIN members ON
        members.id = cte.reports_to_id AND
        members.id <> NEW.id
    )
    SELECT true INTO circular_ref_exists FROM cte WHERE reports_to_id = NEW.id;

    IF (circular_ref_exists)
    THEN
      RAISE EXCEPTION 'A circular reference has been detected';
    END IF;

    RETURN NEW;
    END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER circular_reference
    BEFORE UPDATE ON members
    FOR EACH ROW
    EXECUTE FUNCTION circular_reference();
© www.soinside.com 2019 - 2024. All rights reserved.