Prevent and/or detect cycles in postgres

后端 未结 4 792
刺人心
刺人心 2020-12-06 12:28

Assuming a schema like the following:

CREATE TABLE node (
  id       SERIAL PRIMARY KEY,
  name     VARCHAR,
  parentid INT REFERENCES node(id)
);

4条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-12-06 13:18

    Your trigger simplified and optimized, should be considerably faster:

    CREATE OR REPLACE FUNCTION detect_cycle()
      RETURNS TRIGGER
      LANGUAGE plpgsql AS
    $func$
    BEGIN
       IF EXISTS (
          WITH RECURSIVE search_graph(parentid, path, cycle) AS ( -- relevant columns
              -- check ahead, makes 1 step less
             SELECT g.parentid, ARRAY[g.id, g.parentid], (g.id = g.parentid)
             FROM   node g
             WHERE  g.id = NEW.id  -- only test starting from new row
             
             UNION ALL
             SELECT g.parentid, sg.path || g.parentid, g.parentid = ANY(sg.path)
             FROM   search_graph sg
             JOIN   node g ON g.id = sg.parentid
             WHERE  NOT sg.cycle
             )
          SELECT FROM search_graph
          WHERE  cycle
          LIMIT  1  -- stop evaluation at first find
          )
       THEN
          RAISE EXCEPTION 'Loop detected!';
       ELSE
         RETURN NEW;
       END IF;
    END
    $func$;
    

    You don't need dynamic SQL, you don't need to count, you don't need all the columns and you don't need to test the whole table for every single row.

    CREATE TRIGGER detect_cycle_after_update
    AFTER INSERT OR UPDATE ON node
    FOR EACH ROW EXECUTE PROCEDURE detect_cycle();

    An INSERT like this has to be prohibited, too:

    INSERT INTO node (id, name,parentid) VALUES (8,'D',9), (9,'E',8);
    

提交回复
热议问题