Come aggiungere vincoli "on delete cascade"?


163

In PostgreSQL 8 è possibile aggiungere ON DELETE CASCADESentrambe le chiavi esterne nella seguente tabella senza far cadere quest'ultima?

# \d scores
        Table "public.scores"
 Column  |         Type          | Modifiers
---------+-----------------------+-----------
 id      | character varying(32) |
 gid     | integer               |
 money   | integer               | not null
 quit    | boolean               |
 last_ip | inet                  |
Foreign-key constraints:
   "scores_gid_fkey" FOREIGN KEY (gid) REFERENCES games(gid)
   "scores_id_fkey" FOREIGN KEY (id) REFERENCES users(id)

Di seguito sono riportate entrambe le tabelle di riferimento: qui:

# \d games
                                     Table "public.games"
  Column  |            Type             |                        Modifiers
----------+-----------------------------+----------------------------------------------------------
 gid      | integer                     | not null default nextval('games_gid_seq'::regclass)
 rounds   | integer                     | not null
 finished | timestamp without time zone | default now()
Indexes:
    "games_pkey" PRIMARY KEY, btree (gid)
Referenced by:
    TABLE "scores" CONSTRAINT "scores_gid_fkey" FOREIGN KEY (gid) REFERENCES games(gid)

E qui:

# \d users
                Table "public.users"
   Column   |            Type             |   Modifiers
------------+-----------------------------+---------------
 id         | character varying(32)       | not null
 first_name | character varying(64)       |
 last_name  | character varying(64)       |
 female     | boolean                     |
 avatar     | character varying(128)      |
 city       | character varying(64)       |
 login      | timestamp without time zone | default now()
 last_ip    | inet                        |
 logout     | timestamp without time zone |
 vip        | timestamp without time zone |
 mail       | character varying(254)      |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "cards" CONSTRAINT "cards_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "catch" CONSTRAINT "catch_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "chat" CONSTRAINT "chat_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "game" CONSTRAINT "game_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "hand" CONSTRAINT "hand_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "luck" CONSTRAINT "luck_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "match" CONSTRAINT "match_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "misere" CONSTRAINT "misere_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "money" CONSTRAINT "money_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "pass" CONSTRAINT "pass_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "payment" CONSTRAINT "payment_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "rep" CONSTRAINT "rep_author_fkey" FOREIGN KEY (author) REFERENCES users(id)
    TABLE "rep" CONSTRAINT "rep_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "scores" CONSTRAINT "scores_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "status" CONSTRAINT "status_id_fkey" FOREIGN KEY (id) REFERENCES users(id)

E mi chiedo anche se abbia senso aggiungere 2 indici alla precedente tabella?

AGGIORNAMENTO: Grazie, e ho anche il consiglio della mailing list, che potrei gestirlo in 1 estratto conto e quindi senza iniziare esplicitamente una transazione:

ALTER TABLE public.scores
DROP CONSTRAINT scores_gid_fkey,
ADD CONSTRAINT scores_gid_fkey
   FOREIGN KEY (gid)
   REFERENCES games(gid)
   ON DELETE CASCADE;

1
Un po 'OT, ma noto che non hai creato indici su colonne di riferimento (ad esempio pref_scores.gid). Le eliminazioni nella tabella di riferimento richiederanno molto tempo senza quelle, se si ottengono molte righe in tali tabelle. Alcuni database creano automaticamente un indice sulle colonne di riferimento; PostgreSQL lo lascia a te, poiché ci sono alcuni casi in cui non vale la pena.
kgrittn,

1
Grazie! In realtà ho notato che l'eliminazione richiede molto tempo, ma non sapevo che fosse la ragione
Alexander Farber,

1
Quali casi si verificherebbero quando gli indici su chiavi esterne non valgono la pena?
Alexander Farber,

2
Ho incorporato la tua scoperta nella mia risposta. (Quella singola dichiarazione è anche una singola transazione.)
Mike Sherrill 'Cat Recall',

2
@AlexanderFarber: quando potresti voler omettere un indice sulle colonne di riferimento di un FK? Quando c'è un altro indice non una corrispondenza esatta che funzionerà abbastanza bene (ad esempio, potresti avere un indice trigramma per ricerche di somiglianza frequenti che sarà OK anche per l'eliminazione dell'FK). Quando le cancellazioni sono poco frequenti e possono essere programmate fuori orario. Quando una tabella presenta aggiornamenti frequenti del valore di riferimento. Quando la tabella di riferimento è molto piccola ma aggiornata frequentemente. Le eccezioni si verificano abbastanza spesso che la comunità PostgreSQL preferisce avere il controllo su di essa piuttosto che renderla automatica.
kgrittn,

Risposte:


218

Sono abbastanza sicuro che non puoi semplicemente aggiungere on delete cascadeun vincolo di chiave esterna esistente. Devi prima eliminare il vincolo, quindi aggiungere la versione corretta. In SQL standard, credo che il modo più semplice per farlo sia

  • avviare una transazione,
  • rilascia la chiave esterna,
  • aggiungi una chiave esterna con on delete cascade, e infine
  • impegnare la transazione

Ripetere l'operazione per ciascuna chiave esterna che si desidera modificare.

Ma PostgreSQL ha un'estensione non standard che consente di utilizzare più clausole di vincolo in una singola istruzione SQL. Per esempio

alter table public.scores
drop constraint scores_gid_fkey,
add constraint scores_gid_fkey
   foreign key (gid)
   references games(gid)
   on delete cascade;

Se non conosci il nome del vincolo di chiave esterna che desideri eliminare, puoi cercarlo in pgAdminIII (fai clic sul nome della tabella e osserva il DDL oppure espandi la gerarchia fino a visualizzare "Vincoli"), oppure è possibile eseguire una query sullo schema delle informazioni .

select *
from information_schema.key_column_usage
where position_in_unique_constraint is not null

Grazie, è quello che ho pensato anch'io, ma cosa fare con i TASTI STRANIERI? Sono solo vincoli (simili a NOT NULL) che possono essere eliminati e letti facilmente?
Alexander Farber,

2
@AlexanderFarber: Sì, sono denominati vincoli che puoi eliminare e aggiungere facilmente. Ma probabilmente vorrai farlo all'interno di una transazione. Aggiornato la mia risposta con maggiori dettagli.
Mike Sherrill "Cat Recall",

+1 per cercare in pgAdminIII. Ti dà anche i comandi DROP CONSTRAINT e ADD CONSTRAINT, quindi puoi semplicemente copiare e incollare in una finestra di query e modificare il comando come desideri.
Dave Pile il

Dopo aver scritto la query, ho notato che la mia GUI di Postgres (Navicat) mi fa banalmente fare questa modifica dall'interno della GUI: dl.dropboxusercontent.com/spa/quq37nq1583x0lf/wwqne-lw.png
danneu

Per le tabelle di grandi dimensioni, è possibile NOT VALIDe convalidare in una transazione separata? Ho una domanda senza risposta al riguardo.
TheCloudlessSky,

11

Basato sulla risposta di @Mike Sherrill Cat Recall, questo è ciò che ha funzionato per me:

ALTER TABLE "Children"
DROP CONSTRAINT "Children_parentId_fkey",
ADD CONSTRAINT "Children_parentId_fkey"
  FOREIGN KEY ("parentId")
  REFERENCES "Parent"(id)
  ON DELETE CASCADE;

5

Uso:

select replace_foreign_key('user_rates_posts', 'post_id', 'ON DELETE CASCADE');

Funzione:

CREATE OR REPLACE FUNCTION 
    replace_foreign_key(f_table VARCHAR, f_column VARCHAR, new_options VARCHAR) 
RETURNS VARCHAR
AS $$
DECLARE constraint_name varchar;
DECLARE reftable varchar;
DECLARE refcolumn varchar;
BEGIN

SELECT tc.constraint_name, ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name 
FROM 
    information_schema.table_constraints AS tc 
    JOIN information_schema.key_column_usage AS kcu
      ON tc.constraint_name = kcu.constraint_name
    JOIN information_schema.constraint_column_usage AS ccu
      ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY' 
   AND tc.table_name= f_table AND kcu.column_name= f_column
INTO constraint_name, reftable, refcolumn;

EXECUTE 'alter table ' || f_table || ' drop constraint ' || constraint_name || 
', ADD CONSTRAINT ' || constraint_name || ' FOREIGN KEY (' || f_column || ') ' ||
' REFERENCES ' || reftable || '(' || refcolumn || ') ' || new_options || ';';

RETURN 'Constraint replaced: ' || constraint_name || ' (' || f_table || '.' || f_column ||
 ' -> ' || reftable || '.' || refcolumn || '); New options: ' || new_options;

END;
$$ LANGUAGE plpgsql;

Attenzione: questa funzione non copia gli attributi della chiave esterna iniziale. Richiede solo il nome di tabella / colonna esterno, rilascia la chiave corrente e sostituisce con una nuova.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.