2010-10-18 18 views
20

Ho creato alcune tabelle in postgres, aggiunto una chiave esterna da una tabella a un'altra e impostato ON DELETE su CASCADE. Stranamente, ho alcuni campi che sembrano violare questo vincolo.Le chiavi esterne in postgresql possono essere violate dal trigger

È normale? E se è così, c'è un modo per ottenere il comportamento che voglio (nessuna violazione possibile)?

Edit:

ho orginaly creato la chiave esterna come parte di CREATE TABLE, usando solo

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

Il codice corrente pgAdmin3 dà è

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

Edit 2:

Per chiarire, ho un vago sospetto che i vincoli vengano controllati solo quando si aggiorna s/inserts capita ma non vengono mai più guardati. Sfortunatamente non conosco abbastanza su postgres per scoprire se questo è vero o come i campi potrebbero finire nel database senza che questi controlli vengano eseguiti.

Se questo è il caso, c'è un modo per controllare tutte le chiavi esterne e risolvere questi problemi?

Edit 3:

Una violazione di vincolo può essere causato da un trigger difettosa, vedi sotto

+0

solo per un controllo di integrità puoi pubblicare la dichiarazione che hai usato per creare l'FK? –

+0

Quale versione di PG sei? – Kuberchaun

+0

Sto usando la versione 8.3 –

risposta

5

Tutto quello che ho letto finora sembra suggerire che i vincoli vengono controllati solo quando è inserita i dati. (Oppure quando viene creato il vincolo) Ad esempio the manual on set constraints.

Questo ha senso e, se il database funziona correttamente, dovrebbe essere sufficiente. Sono ancora curioso di sapere come sono riuscito a evitarlo o se ho appena letto la situazione sbagliata e non c'è mai stata una vera e propria violazione ai vincoli.

In entrambi i casi, caso chiuso: -/

------- -------- UPDATE

C'era sicuramente una violazione di vincolo, causata da un trigger difettoso. Ecco uno script da replicare:

-- 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; 
+1

Non c'è mai stata una violazione reale dei vincoli. – Unreason

+0

Mi sono reso conto che ho gestito questa situazione un po 'stranamente, quindi ho fatto uno sforzo extra e ho replicato la situazione che ha causato i miei problemi iniziali. –

24

Ho cercato di creare un semplice esempio che mostri il vincolo di chiave esterna applicato. Con questo esempio provo che non sono autorizzato a inserire dati che violano il fk e provo che se il fk non è in posizione durante l'inserimento, e abilito il fk, il vincolo fk genera un errore che mi dice che i dati violano il fk. Quindi non vedo come hai dati nella tabella che viola un fk che è in atto. Sono su 9.0, ma questo non dovrebbe essere diverso su 8.3. Se è possibile mostrare un esempio funzionante che dimostra il problema che potrebbe aiutare.

--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; 
Problemi correlati