Внешние ключи в postgresql могут быть нарушены с помощью триггера
Я создал несколько таблиц в postgres, добавил внешний ключ из одной таблицы в другую и установил ON DELETE в CASCADE. Как ни странно, у меня есть некоторые поля, которые, по-видимому, нарушают это ограничение.
Это нормальное поведение? И если да, есть ли способ получить поведение, которое я хочу (возможны нарушения)?
Edit:
I orginaly создал внешний ключ как часть 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:
Нарушение ограничения может быть вызвано неисправным триггером, см. ниже
Ответы
Ответ 1
Все, что я читал до сих пор, похоже, предполагает, что ограничения проверяются только при вставке данных. (Или когда создается ограничение) Например руководство по установленным ограничениям.
Это имеет смысл и - если база данных работает правильно - должна быть достаточно хорошей. Мне все еще любопытно, как мне удалось обойти это, или если я просто прочитал ситуацию неправильно, и с самого начала никогда не было реального нарушения ограничений.
В любом случае закрытый случай: -/
------- UPDATE --------
Было определенно нарушение ограничений, вызванное неисправным триггером. Здесь script для репликации:
-- 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;
Ответ 2
Я попытался создать простой пример, который показывает принудительное ограничение внешнего ключа. В этом примере я докажу, что мне не разрешено вводить данные, которые нарушают 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;