如何创建函数以避免递归 PSQL 上的无限循环

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

我有一个问题,我有一个员工与每个经理的自引用表,有时由于人为错误,它会创建一个无限循环。

CREATE TABLE employees (
    id serial PRIMARY KEY,
    name varchar(255) NOT NULL,
    job varchar(255),
    manager_id int,
    FOREIGN KEY (manager_id) REFERENCES employees (id) ON DELETE CASCADE);

下面是我用来测试无限循环的行。 ID 1 到> ID 5 到> ID 7 到> ID 1.....

INSERT INTO employees (id,name,manager_id,job)VALUES
    (1,  'Max', 5, 'CEO'),
    (2,  'Jeremy', 5, 'Junior Developer'),
    (3,  'Allen', 5, 'Intern'),
    (4,  'Lucas', 5, 'Developer'),
    (5,  'John', 7, 'Manager'),
    (6,  'Creed', 7, 'DevOps Engineer'),
    (7,  'Anna', 1, 'VP'),
    (8,  'Logan', 1, 'Manager'),
    (9,  'Ted', 8, 'Data Analyst'),
    (10, 'Jensen', 8, 'Developer');

我尝试在更新后创建触发器,但由于某种原因它不起作用。

create or replace function if_atu_employee() returns trigger as
$$      
DECLARE         
        hier RECORD;
begin   
        IF NEW.manager_id IS NULL THEN 
                return NEW;
        END IF; 

        WITH RECURSIVE managers AS (
                SELECT id, name, manager_id, job
                FROM employees
                WHERE id = NEW.id
        UNION ALL
                SELECT e.id, e.name, e.manager_id, e.job
                FROM employees e 
                JOIN managers ON e.manager_id = managers.id
        )
    cycle ID set is_cycle using path
        SELECT INTO hier id FROM managers WHERE is_cycle limit 1;

    IF hier IS NOT NULL THEN
        UPDATE employees SET manager_id = NULL WHERE id = NEW.id;
    END IF;
        
        return NEW;
end;
$$ language plpgsql;
create trigger if_tr_employee after update on employees for each row execute procedure if_atu_employee();

有人知道我在函数中做错了什么吗?

postgresql recursion triggers reference recursive-query
1个回答
0
投票

这个似乎适用于插入和更新。

CREATE OR REPLACE FUNCTION tr_check_loop()
    RETURNS TRIGGER
    LANGUAGE plpgsql
    AS
    $$
    DECLARE
        _id INT;
    BEGIN
        WITH RECURSIVE managers AS (
                SELECT id
                     , name
                     , ARRAY[manager_id] manager_ids
                     , job
                FROM employees
                WHERE id = NEW.id
        UNION ALL
                SELECT e.id
                     , e.name
                     , managers.manager_ids || e.manager_id,
                       e.job
                FROM employees e
                    JOIN managers ON e.manager_id = managers.id
        )
    SELECT NEW.id
    INTO _id
    FROM managers
    WHERE ARRAY(SELECT e FROM unnest(manager_ids) u(e) ORDER BY e )
              <>
          ARRAY(SELECT DISTINCT e FROM unnest(manager_ids) u(e) ORDER BY e )
    LIMIT 1;

    IF FOUND THEN
        RAISE EXCEPTION 'Loop detected for id %', _id;
    END IF;

    RETURN NEW;
END;
$$;


CREATE TRIGGER tr_loop AFTER INSERT OR UPDATE ON employees
    FOR EACH ROW EXECUTE FUNCTION tr_check_loop();

检查包含路径的数组是否存在非不同值应该可以检测到循环。而且它确实只需要一个循环,因此一旦发现问题就会停止。它还引发了一个异常,因为代码永远不会知道谁是正确的经理。我不建议设置默认 NULL,因为您可能需要几周甚至几个月的时间才能发现您的数据已被破坏。

© www.soinside.com 2019 - 2024. All rights reserved.