代码之家  ›  专栏  ›  技术社区  ›  Michael Clerx

触发器可能违反postgresql中的外键

  •  20
  • Michael Clerx  · 技术社区  · 15 年前

    我在postgres中创建了一些表,从一个表向另一个表添加了外键,并将DELETE设置为CASCADE。奇怪的是,我有一些字段似乎违反了这个约束。

    这是正常的行为吗?如果是这样,有没有办法得到我想要的行为(不可能违反)?

    编辑:

    ... REFERENCES product (id) ON UPDATE CASCADE ON DELETE CASCADE
    

    pgAdmin3给出的当前代码是

    ALTER TABLE cultivar
      ADD CONSTRAINT cultivar_id_fkey FOREIGN KEY (id)
          REFERENCES product (id) MATCH SIMPLE
          ON UPDATE CASCADE ON DELETE CASCADE;
    

    编辑2:

    为了澄清这一点,我有一种潜在的怀疑,即只有在更新/插入发生时才检查约束,但之后再也不会查看约束。不幸的是,我对postgres的了解还不够多,不知道这是否正确,也不知道在没有运行这些检查的情况下字段是如何在数据库中结束的。

    如果是这样,有没有办法检查所有外键并解决这些问题?

    编辑3:

    约束冲突可能是由错误的触发器引起的,请参见下文

    2 回复  |  直到 10 年前
        1
  •  24
  •   Kuberchaun    12 年前

    我试图创建一个简单的示例,显示外键约束被强制执行。通过这个例子,我证明了我不允许输入违反fk的数据,并且证明了如果在插入期间fk不在适当的位置,并且我启用了fk,fk约束将抛出一个错误,告诉我数据违反了fk。所以我没有看到表中的数据是如何违反fk的。我是9.0版的,但8.3版应该没什么不同。如果你能展示一个能证明你的问题的有效例子,也许会有帮助。

    --CREATE TABLES--
    CREATE TABLE parent
    (
      parent_id integer NOT NULL,
      first_name character varying(50) NOT NULL,
      CONSTRAINT pk_parent PRIMARY KEY (parent_id)
    )
    WITH (
      OIDS=FALSE
    );
    ALTER TABLE parent OWNER TO postgres;
    
    CREATE TABLE child
    (
      child_id integer NOT NULL,
      parent_id integer NOT NULL,
      first_name character varying(50) NOT NULL,
      CONSTRAINT pk_child PRIMARY KEY (child_id),
      CONSTRAINT fk1_child FOREIGN KEY (parent_id)
          REFERENCES parent (parent_id) MATCH SIMPLE
          ON UPDATE CASCADE ON DELETE CASCADE
    )
    WITH (
      OIDS=FALSE
    );
    ALTER TABLE child OWNER TO postgres;
    --CREATE TABLES--
    
    --INSERT TEST DATA--
    INSERT INTO parent(parent_id,first_name)
    SELECT 1,'Daddy'
    UNION 
    SELECT 2,'Mommy';
    
    INSERT INTO child(child_id,parent_id,first_name)
    SELECT 1,1,'Billy'
    UNION 
    SELECT 2,1,'Jenny'
    UNION 
    SELECT 3,1,'Kimmy'
    UNION 
    SELECT 4,2,'Billy'
    UNION 
    SELECT 5,2,'Jenny'
    UNION 
    SELECT 6,2,'Kimmy';
    --INSERT TEST DATA--
    
    --SHOW THE DATA WE HAVE--
    select parent.first_name,
           child.first_name
    from parent
    inner join child
            on child.parent_id = parent.parent_id
    order by parent.first_name, child.first_name asc;
    --SHOW THE DATA WE HAVE--
    
    --DELETE PARENT WHO HAS CHILDREN--
    BEGIN TRANSACTION;
    delete from parent
    where parent_id = 1;
    
    --Check to see if any children that were linked to Daddy are still there?
    --None there so the cascade delete worked.
    select parent.first_name,
           child.first_name
    from parent
    right outer join child
            on child.parent_id = parent.parent_id
    order by parent.first_name, child.first_name asc;
    ROLLBACK TRANSACTION;
    
    
    --TRY ALLOW NO REFERENTIAL DATA IN--
    BEGIN TRANSACTION;
    
    --Get rid of fk constraint so we can insert red headed step child
    ALTER TABLE child DROP CONSTRAINT fk1_child;
    
    INSERT INTO child(child_id,parent_id,first_name)
    SELECT 7,99999,'Red Headed Step Child';
    
    select parent.first_name,
           child.first_name
    from parent
    right outer join child
            on child.parent_id = parent.parent_id
    order by parent.first_name, child.first_name asc;
    
    --Will throw FK check violation because parent 99999 doesn't exist in parent table
    ALTER TABLE child
      ADD CONSTRAINT fk1_child FOREIGN KEY (parent_id)
          REFERENCES parent (parent_id) MATCH SIMPLE
          ON UPDATE CASCADE ON DELETE CASCADE;
    
    ROLLBACK TRANSACTION;
    --TRY ALLOW NO REFERENTIAL DATA IN--
    
    --DROP TABLE parent;
    --DROP TABLE child;
    
        2
  •  5
  •   Michael Clerx    11 年前

    到目前为止,我读到的所有内容似乎都表明,只有在插入数据时才检查约束。(或在创建约束时)例如 the manual on set constraints

    这是有意义的-如果数据库工作正常-应该足够好。

    不管怎样,结案:-/

    肯定有一个约束冲突,是由一个错误的触发器引起的。下面是要复制的脚本:

    -- Create master table
    CREATE TABLE product
    (
      id INT NOT NULL PRIMARY KEY
    );
    
    -- Create second table, referencing the first
    CREATE TABLE example
    (
      id int PRIMARY KEY REFERENCES product (id) ON DELETE CASCADE
    );
    
    -- Create a (broken) trigger function
    --CREATE LANGUAGE plpgsql;
    CREATE OR REPLACE FUNCTION delete_product()
      RETURNS trigger AS
    $BODY$
        BEGIN
          DELETE FROM product WHERE product.id = OLD.id;
          -- This is an error!
          RETURN null;
        END;
    $BODY$
      LANGUAGE plpgsql;
    
    -- Add it to the second table
    CREATE TRIGGER example_delete
      BEFORE DELETE
      ON example
      FOR EACH ROW
      EXECUTE PROCEDURE delete_product();
    
    -- Now lets add a row
    INSERT INTO product (id) VALUES (1);
    INSERT INTO example (id) VALUES (1);
    
    -- And now lets delete the row
    DELETE FROM example WHERE id = 1;
    
    /*
    Now if everything is working, this should return two columns:
    (pid,eid)=(1,1). However, it returns only the example id, so
    (pid,eid)=(0,1). This means the foreign key constraint on the
    example table is violated.
    */
    SELECT product.id AS pid, example.id AS eid FROM product FULL JOIN example ON product.id = example.id;