Comment ajouter des contraintes «on delete cascade»?

163

Dans PostgreSQL 8, est-il possible d'ajouter ON DELETE CASCADESles deux clés étrangères dans le tableau suivant sans supprimer cette dernière?

# \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)

Les deux tableaux référencés sont ci-dessous - ici:

# \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)

Et ici:

# \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)

Et aussi je me demande s'il est logique d'ajouter 2 index à l'ancienne table?

MISE À JOUR: Merci, et aussi j'ai eu le conseil à la liste de diffusion, que je pourrais le gérer en 1 déclaration et donc sans démarrer explicitement une transaction:

ALTER TABLE public.scores
DROP CONSTRAINT scores_gid_fkey,
ADD CONSTRAINT scores_gid_fkey
   FOREIGN KEY (gid)
   REFERENCES games(gid)
   ON DELETE CASCADE;
Alexander Farber
la source
1
Un peu OT, mais je remarque que vous n'avez pas créé d'index sur les colonnes de référencement (par exemple, pref_scores.gid). Les suppressions sur la table référencée prendront beaucoup de temps sans celles-ci, si vous obtenez de nombreuses lignes dans ces tables. Certaines bases de données créent automatiquement un index sur la ou les colonnes de référence; PostgreSQL vous laisse le soin, car il y a des cas où cela ne vaut pas la peine.
kgrittn
1
Je vous remercie! En fait, j'ai remarqué que la suppression prend du temps, mais je ne savais pas que c'était la raison
Alexander Farber
1
Dans quels cas s'agit-il, lorsque les index sur des clés étrangères ne valent pas la peine?
Alexander Farber
2
J'ai incorporé votre découverte dans ma réponse. (Cette déclaration unique est également une transaction unique.)
Mike Sherrill 'Cat Recall'
2
@AlexanderFarber: Quand souhaiteriez-vous omettre un index sur la ou les colonnes de référencement d'un FK? Quand il y a un autre index pas une correspondance exacte qui fonctionnera assez bien (par exemple, vous pourriez avoir un index de trigramme pour les recherches fréquentes de similarité qui sera également OK pour la suppression FK). Lorsque les suppressions sont peu fréquentes et peuvent être programmées en dehors des heures d'ouverture. Lorsqu'une table a des mises à jour fréquentes de la valeur de référence. Lorsque la table de référencement est très petite mais fréquemment mise à jour. Les exceptions se produisent assez souvent pour que la communauté PostgreSQL préfère en avoir le contrôle plutôt que de le rendre automatique.
kgrittn

Réponses:

218

Je suis presque sûr que vous ne pouvez pas simplement ajouter on delete cascadeà une contrainte de clé étrangère existante. Vous devez d'abord supprimer la contrainte, puis ajouter la version correcte. En SQL standard, je pense que le moyen le plus simple de le faire est de

  • démarrer une transaction,
  • déposer la clé étrangère,
  • ajouter une clé étrangère avec on delete cascade, et enfin
  • valider la transaction

Répétez pour chaque clé étrangère que vous souhaitez modifier.

Mais PostgreSQL a une extension non standard qui vous permet d'utiliser plusieurs clauses de contrainte dans une seule instruction SQL. Par exemple

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

Si vous ne connaissez pas le nom de la contrainte de clé étrangère que vous souhaitez supprimer, vous pouvez soit le rechercher dans pgAdminIII (cliquez simplement sur le nom de la table et regardez le DDL, soit développez la hiérarchie jusqu'à ce que vous voyiez «Contraintes»), ou vous pouvez interroger le schéma d'informations .

select *
from information_schema.key_column_usage
where position_in_unique_constraint is not null
Mike Sherrill 'Rappel de chat'
la source
Merci, c'est ce que je pensais aussi - mais que faire des FOREIGN KEYs? S'agit-il simplement de contraintes (similaires à NOT NULL) qui peuvent être supprimées et lues facilement?
Alexander Farber
2
@AlexanderFarber: Oui, ce sont des contraintes nommées que vous pouvez supprimer et ajouter facilement. Mais vous souhaitez probablement le faire dans le cadre d'une transaction. J'ai mis à jour ma réponse avec plus de détails.
Mike Sherrill 'Cat Recall' le
+1 pour chercher dans pgAdminIII. Il vous donne même les commandes DROP CONSTRAINT et ADD CONSTRAINT, vous pouvez donc simplement copier et coller dans une fenêtre de requête et modifier la commande comme vous le souhaitez.
Dave Pile
Après avoir rédigé la requête, j'ai remarqué que mon interface graphique Postgres (Navicat) me permettait de faire ce changement de manière triviale depuis l'interface graphique: dl.dropboxusercontent.com/spa/quq37nq1583x0lf/wwqne-lw.png
danneu
Pour les grandes tables, est-ce possible avec NOT VALIDet valider dans une transaction séparée? J'ai une question sans réponse à ce sujet.
TheCloudlessSky
11

Basé sur la réponse de @Mike Sherrill Cat Recall, voici ce qui a fonctionné pour moi:

ALTER TABLE "Children"
DROP CONSTRAINT "Children_parentId_fkey",
ADD CONSTRAINT "Children_parentId_fkey"
  FOREIGN KEY ("parentId")
  REFERENCES "Parent"(id)
  ON DELETE CASCADE;
Compagnon étranger
la source
5

Usage:

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

Fonction:

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;

Attention: cette fonction ne copiera pas les attributs de la clé étrangère initiale. Il prend uniquement le nom de la table / colonne étrangère, supprime la clé actuelle et la remplace par une nouvelle.

Daniel Garmoshka
la source