web-dev-qa-db-fra.com

Les clés étrangères dans postgresql peuvent être violées par le déclencheur

J'ai créé des tables en postgres, ajouté une clé étrangère d'une table à une autre et défini ON DELETE sur CASCADE. Curieusement, certains champs semblent violer cette contrainte.

Est-ce un comportement normal? Et si oui, existe-t-il un moyen d'obtenir le comportement que je veux (aucune violation possible)?

Éditer:

J'ai créé la clé étrangère dans le cadre de CREATE TABLE, en utilisant simplement

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

Le code actuel que pgAdmin3 donne est

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

Modifier 2:

Pour clarifier, j'ai un soupçon sournois que les contraintes ne sont vérifiées que lorsque des mises à jour/insertions se produisent mais ne sont plus jamais examinées à nouveau. Malheureusement, je ne connais pas suffisamment les postgres pour savoir si cela est vrai ou comment les champs pourraient se retrouver dans la base de données sans que ces vérifications soient exécutées.

Si tel est le cas, existe-t-il un moyen de vérifier toutes les clés étrangères et de résoudre ces problèmes?

Modifier 3:

Une violation de contrainte peut être provoquée par un déclencheur défectueux, voir ci-dessous

21
Michael Clerx

Tout ce que j'ai lu jusqu'à présent semble suggérer que les contraintes ne sont vérifiées que lorsque les données sont insérées. (Ou lorsque la contrainte est créée) Par exemple le manuel sur les contraintes définies .

Cela a du sens et - si la base de données fonctionne correctement - devrait être suffisant. Je suis toujours curieux de savoir comment j'ai réussi à contourner cela ou si je viens de mal lire la situation et qu'il n'y a jamais eu de véritable violation de contrainte pour commencer.

Quoi qu'il en soit, le dossier est clos: - /

------- METTRE À JOUR --------

Il y avait certainement une violation de contrainte, causée par un déclencheur défectueux. Voici un script à répliquer:

-- 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;
5
Michael Clerx

J'ai essayé de créer un exemple simple qui montre que la contrainte de clé étrangère est appliquée. Avec cet exemple, je prouve que je ne suis pas autorisé à entrer des données qui violent le fk et je prouve que si le fk n'est pas en place pendant l'insertion, et j'active le fk, la contrainte fk génère une erreur en me disant que les données violent le fk. Je ne vois donc pas comment vous avez des données dans le tableau qui violent un fk en place. Je suis sur 9.0, mais cela ne devrait pas être différent sur 8.3. Si vous pouvez montrer un exemple de travail qui prouve votre problème, cela pourrait vous aider.

--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;
24
Kuberchaun