【问题标题】:Finding circular references within PostgreSQL table?在 PostgreSQL 表中查找循环引用?
【发布时间】:2014-05-06 12:46:55
【问题描述】:

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

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

【问题讨论】:

  • 我认为这是针对任何程度的分离,而不仅仅是您示例中的 1 度。
  • @xQbert ..是的..你能建议一下吗
  • 听起来像是一个图遍历问题——可能是通过递归 CTE 完成的。

标签: sql postgresql plpgsql postgresql-9.1 circular-reference


【解决方案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

【讨论】:

    【解决方案2】:

    这类似于How to prevent a self-referencing table from becoming circular(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; 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-16
      • 1970-01-01
      • 2018-03-11
      • 1970-01-01
      • 2012-05-03
      相关资源
      最近更新 更多