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

时间:2010-10-18 17:46:04

标签: postgresql foreign-keys

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

这是正常行为吗?如果是这样,有没有办法获得我想要的行为(没有违规行为)?

编辑:

我只是使用

创建外键作为CREATE TABLE的一部分
... 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 个答案:

答案 0 :(得分:24)

我尝试创建一个显示强制执行外键约束的简单示例。在这个例子中,我证明我不允许输入违反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;

答案 1 :(得分:5)

到目前为止,我所阅读的所有内容似乎都表明仅在插入数据时才会检查约束。 (或者在创建约束时)例如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;