2013-05-01 15 views
9

ho funzione di controllo partecipazione obbligatoria come segue:vincolo di controllo Deferrable in PostgreSQL

CREATE FUNCTION member_in_has_address() 
RETURNS BOOLEAN AS $$ 
BEGIN 
RETURN EXISTS (SELECT * 
     FROM address a, member_details b 
     WHERE b.member_id = a.member_id); 
END; 
$$ LANGUAGE plpgsql; 

poi chiamato dal vincolo CHECK

ALTER TABLE member_details 
ADD CONSTRAINT member_in_has_address_check 
    CHECK (member_in_has_address()); 

Per creare vincolo deferable in standard SQL sarebbe:

ALTER TABLE member_details 
ADD CONSTRAINT member_in_has_address_check 
    INITIALLY DEFERRED 
    CHECK (member_in_has_address()); 

Come posso fare lo stesso in PostgreSQL?

+0

Il tuo corrente 'member_in_has_address()' restituirà 'true' quando ** qualsiasi ** dei membri ha indirizzo. Non controllerà se un determinato membro ha un indirizzo. –

+0

Grazie, Igor, ma la mia domanda principale è come differire tale vincolo finché il bambino ('indirizzo') non viene aggiornato. L'inserto è il seguente: inserire in 'member_details' che è padre e poi a' address' che ha la chiave di prua. 'member_details' ha la partecipazione obbligatoria con' address'. –

+0

Vedere la mia risposta qui sotto. La risposta: crea una chiave esterna defferrata. –

risposta

9

È possibile definire i vincoli in Postgresql allo stesso modo di altri RDBMS, ma per la versione corrente (9.2) è possibile solo definire UNICO, PRIMARIO, ESCLUSIONE e RIFERIMENTI. Estrarre dal this page del manuale:

DEFERRABLE
NOT DEFERRABLE

This controls whether the constraint can be deferred. A constraint that is not deferrable will be checked immediately after every command. Checking of constraints that are deferrable can be postponed until the end of the transaction (using the SET CONSTRAINTS command). NOT DEFERRABLE is the default. Currently, only UNIQUE, PRIMARY KEY, EXCLUDE, and REFERENCES (foreign key) constraints accept this clause. NOT NULL and CHECK constraints are not deferrable.

INITIALLY IMMEDIATE
INITIALLY DEFERRED

If a constraint is deferrable, this clause specifies the default time to check the constraint. If the constraint is INITIALLY IMMEDIATE, it is checked after each statement. This is the default. If the constraint is INITIALLY DEFERRED, it is checked only at the end of the transaction. The constraint check time can be altered with the SET CONSTRAINTS command.

È possibile creare una semplice chiave esterna differita da member_details a address al posto del tuo vincolo corrente per verificare, se ogni membro ha un indirizzo.

AGGIORNAMENTO: è necessario creare 2 chiave esterna. Uno normale da address(member_id) a member_details(member_id). L'altro - defferrato da member_details(member_id) a address(member_id).

Con queste due chiavi esterne si sarà in grado di:

  1. crea un membro in member_details.
  2. Creare un indirizzo in address per membro dal passo 1
  3. commit (senza errori)

O

  1. Creare un membro member_details.
  2. Conferma (e ottiene l'errore dalla chiave esterna defferrata).
+0

Grazie. Significa che posso defferare una chiave esterna in 'address' e quindi inserire prima in' address' e poi in 'member_details'. –

+0

Ma c'è un altro problema, 'member_details' ha la chiave primaria autoincremned quindi prima inserisco in' member_details', quindi recupero la sua chiave primaria e poi la inserisco in 'address'. –

+0

@RadovanLuptak Basta creare 2 chiavi esterne, un 'membro_dettagli (membro_id)' -> 'indirizzo (membro_id)', e il secondo 'indirizzo (membro_id)' -> 'membro_dettagli (membro_id)'. –

1

Avvolgete le vostre domande in una transazione, e quindi utilizzare una chiave esterna differita e vincolo differita attiva se è necessario almeno un indirizzo:

CREATE CONSTRAINT TRIGGER member_details_address_check_ins 
    AFTER INSERT ON member_details 
DEFERRABLE INITIALLY DEFERRED 
FOR EACH ROW 
EXECUTE PROCEDURE member_details_address_check_ins(); 

ALTER TABLE address 
ADD CONSTRAINT address_member_details_member_id_fkey 
FOREIGN KEY (member_id) REFERENCES member_details(member_id) 
ON UPDATE NO ACTION ON DELETE NO ACTION 
DEFERRABLE INITIALLY DEFERRED; 

CREATE CONSTRAINT TRIGGER address_member_details_check_del 
    AFTER DELETE ON address 
DEFERRABLE INITIALLY DEFERRED 
FOR EACH ROW 
EXECUTE PROCEDURE address_member_details_check_del(); 

-- also consider the update cases for the inevitable merge of duplicate members. 

In una nota separata, normalizzato e carina, ma mettendo indirizzi e dettagli di contatto come le e-mail in una tabella di indirizzi separata presentano occasionalmente problemi UI/UX molto colorati. Per esempio. un segretario inesperto che cambia la compagnia e l'indirizzo di tutti i contatti del suo capo presso la società A quando uno di loro è passato alla compagnia B. Sì, visto accadere per davvero quando l'interfaccia utente si è comportata diversamente da Outlook ...

In ogni caso, e fwow, ho scoperto che di solito è più comodo archiviare questa roba nella stessa tabella del contatto, ad esempio indirizzo1, indirizzo2, email1, email2, ecc. Rende le altre cose più semplici per una serie di altri motivi, vale a dire l'esecuzione assegni come quello che stai esaminando. Il caso estremamente raro in cui si desidera archiviare più di due di tali informazioni è, in pratica, semplicemente non vale la pena.

+0

Grazie, ho completamente dimenticato di inserire ed eliminare il trigger per imporre la partecipazione obbligatoria. L'ho fatto in Sybase. –

+0

Btw, c'era una domanda correlata oggi, dove ho finito per espandermi sul mio punto conclusivo: http://dba.stackexchange.com/a/41430/1860 –

+0

non posso aggrapparmi alla tua vista di "unirsi" al 1: n informazioni di tipo di contatto più in generale (come indirizzo, mail, tel, ...) con il contatto stesso. in applicazioni che non si concentrano su questo, può essere ok, ma non appena è più importante gestire i contatti spesso o per un lungo periodo di tempo in modo affidabile e flessibile, gestendo questo con relazioni 1: n e tabelle separate possono essere più facilmente progettato e mantenuto nella mia esperienza. Outlook IMO non offre un approccio alle migliori pratiche in questo caso, dal momento che ha solo un modello di dati piuttosto semplice che non si adatta bene a una gestione dei contatti più complessa. –

0

Questo è quello che mi viene in mente.

ALTER TABLE address 
ADD CONSTRAINT address_member_in_has_address 
FOREIGN KEY (member_id) REFERENCES member_details(member_id) 
ON DELETE CASCADE 
DEFERRABLE INITIALLY DEFERRED; 

CREATE FUNCTION member_in_has_address() RETURNS trigger AS $BODY$ 
    BEGIN 
    IF NOT EXISTS(SELECT * 
        FROM member_details 
        WHERE member_id IN (SELECT member_id 
             FROM address)) 
    THEN 
      RAISE EXCEPTION 'Error: member does not have address'; 
     END IF; 
    RETURN NEW; 
    END; 
$BODY$ LANGUAGE plpgsql; 

CREATE CONSTRAINT TRIGGER manatory_participation_member_details_ins 
AFTER INSERT ON member_details 
DEFERRABLE INITIALLY DEFERRED 
FOR EACH ROW 
EXECUTE PROCEDURE member_in_has_address(); 

CREATE CONSTRAINT TRIGGER manatory_participation_member_details_del 
AFTER INSERT ON member_details 
DEFERRABLE INITIALLY DEFERRED 
FOR EACH ROW 
EXECUTE PROCEDURE member_in_has_address(); 

Ho provato la versione di Igor utilizzando chiavi esterne in entrambe le tabelle senza i trigger. In questo caso questo vincolo non è deffered.

ALTER TABLE member_details 
ADD CONSTRAINT member_details_in_has_address 
FOREIGN KEY (address_id) REFERENCES address 
ON UPDATE NO ACTION ON DELETE CASCADE 
DEFERRABLE INITIALLY DEFERRED; 

ottengo questo: ERRORE: valore nullo nella colonna "address_id" viola vincolo non nullo

Inserendo utilizza questo blocco annonymous:

DO $$ 
DECLARE 
mem BIGINT; 
BEGIN 
INSERT INTO member_details (member_first_name, member_last_name, member_dob, member_phone_no, 
member_email, member_gender, industry_position, account_type, music_interests) 
VALUES ('Rado','Luptak','07/09/80','07540962233','[email protected]','M','DJ','basic','hard core'); 

SELECT member_id 
INTO mem 
FROM member_details 
WHERE member_first_name = 'Rado' AND member_last_name = 'Luptak' 
AND member_dob = '07/09/76'; 

INSERT INTO address (address_id, house_name_no, post_code, street_name, town, country, member_id) 
VALUES (mem, '243', 'E17 3TT','Wood Road','London', 'UK', mem); 

UPDATE member_details 
SET address_id = mem WHERE member_id = mem; 
END 
$$; 

altro problema di far rispettare partecipazione obbligatoria member_details using address_id della tabella indirizzi (versione di Igor) è che questo mi permette di inserire una riga in member_details e fare riferimento a una riga di indirizzo esistente, ma la riga di indirizzo esistente fa riferimento a una riga member_details diversa. Quando viene eliminata la seconda riga member_details, esegue la cascata ed elimina la riga dell'indirizzo, che può o non può eliminare (dipende dalle impostazioni) la nuova riga member_details inserita. Restituirebbe anche diversi dettagli quando si unisse su member_id e su address_id. Pertanto, richiede un altro vincolo, quindi sono rimasto con il trigger e lo abbiamo rilasciato prima dell'inserto e lo ho ricreato dopo l'inserimento, poiché il trigger non è differito.

+0

Come ho detto prima, la funzione 'member_in_has_address()' restituirà true quando ** qualsiasi ** dei membri ha un indirizzo. Non controllerà se un determinato membro ha un indirizzo. –

+0

Sì, sono d'accordo e ho aggiornato il corpo della procedura. –

Problemi correlati