Comment ajouter une colonne avec une contrainte de clé étrangère à une table qui existe déjà?

11

J'ai les tableaux suivants,

CREATE TABLE users (id int PRIMARY KEY);

-- already exists with data
CREATE TABLE message ();

Comment puis-je modifier la messagestable de telle sorte que,

  1. une nouvelle colonne appelée y senderest ajoutée
  2. senderest une clé étrangère référençant la userstable

Ça n'a pas marché

# ALTER TABLE message ADD FOREIGN KEY (sender) REFERENCES users;
ERROR:  column "sender" referenced in foreign key constraint does not exist

Cette instruction ne crée-t-elle pas également la colonne?

Hassan Baig
la source
3
Vous devez créer la colonne avant de la référencer. J'essaierais également de lire la documentation d' ALTER TABLE ici, et je ferais très attention aux exemples.
Kassandry
Hassan, j'ai nettoyé cette question pour utiliser DDL et j'ai supprimé les choses qui ne fonctionnaient pas. Vérifiez si cela répond à la question: dba.stackexchange.com/a/202564/2639 . N'hésitez pas à rejeter l'une de ces modifications, je voulais juste nettoyer cela pour la postérité.
Evan Carroll
@Kassandry dba.stackexchange.com/a/202564/2639
Evan Carroll

Réponses:

18

Ce qui est relativement facile - il vous suffit d'ajouter une autre étape.

La FOREIGN KEYcolonne doit exister pour en faire un FK. J'ai fait ce qui suit (à partir d' ici et de la documentation ):

CREATE TABLE x(t INT PRIMARY KEY);

CREATE TABLE y(s INT);

ALTER TABLE y ADD COLUMN z INT;    

ALTER TABLE y
  ADD CONSTRAINT y_x_fkey FOREIGN KEY (z)
      REFERENCES x (t)
      ON UPDATE CASCADE ON DELETE CASCADE;

Quelques points à noter:

TOUJOURS donner à vos clés étrangères des noms significatifs. Le fait que la clé "SYS_C00308108" soit violée n'est pas très utile. Voir le violon ici pour le comportement d'Oracle dans ces circonstances, le nom de la clé variera d'un violon à un violon, mais est une chaîne arbitraire commençant par SYS _...)

Compte tenu de votre déclaration:

ALTER TABLE message ADD FOREIGN KEY (sender) REFERENCES users;

Ce serait "agréable à avoir" si le SGBDR pouvait créer automatiquement le champ souhaité avec le type de données correspondant au champ référencé. Tout ce que je dirais, c'est que changer DDL est (ou du moins devrait être) une opération rarement utilisée et non quelque chose que vous voudriez faire régulièrement. Cela risque également de s'ajouter à une documentation déjà assez importante.

Au moins, PostgreSQL essaie de faire quelque chose de raisonnable - il concatène le nom de la table, le nom du FOREIGN KEYchamp _fkeyet ajoute même DETAIL: Key (sender_id)=(56) is not present in table "user_".pour donner quelque chose qui pourrait avoir du sens pour un être humain - voir violon ici .

Vérace
la source
2
Je ne nomme jamais mes clés étrangères. Ils sont nommés automatiquement et ils sont généralement très utiles. Par exemple, le nom par défaut dans ce contexte est "y_z_fkey". Je dirais que c'est un meilleur nom que y_x_fkeyparce que votre violation ne vous indique pas la colonne dans laquelle vous insérez à l'origine de l'erreur. Je me soucie moins de l'endroit où ça pointe. En règle générale, vous ne devez JAMAIS nommer vos fkeys et laisser la valeur par défaut de PostgreSQL le gérer.
Evan Carroll
En outre, vous ne souhaiterez peut-être pas non plus remplacer les valeurs par défaut d' ON UPDATE CASCADE ON DELETE CASCADE;un exemple, en particulier sans raison. Cela rend l'exemple plus complexe et vous n'avez pas la peine d'expliquer ce que c'est. Pour ma part, je ne veux normalement pas que les suppressions se mettent en cascade.
Evan Carroll
1
Je nomme toujours les FK, selon la convention décidée par l'entreprise / le projet. Peu importe que ce soit y_x_fkeyou y_z_fkeyou x__y_FK, tant qu'il est cohérent.
ypercubeᵀᴹ
Je serais tout à fait d'accord avec cela si vous contractez - choisissez une convention et respectez-la et / ou assurez-vous que vous vous conformez aux conventions qui ont été / ont été utilisées avec le système précédemment.
Vérace
@EvanCarroll - si la convention de PostgreSQL est celle du projet ou celle qui a été précédemment décidée sur des systèmes qui pourraient ne pas être PostgreSQL - un système pourrait bien avoir démarré, disons, Oracle ou un autre système qui pourrait ne pas avoir la ou les conventions de PostgreSQL. Vous pourriez faire valoir que x_y_z_fk pourrait donner le maximum d'informations possible en cas d'erreur! Choisissez quelque chose et respectez- le, c'est ma devise, mais ne laissez pas un SGBDR (aussi bon soit-il) décider des conventions pour vous!
Vérace
8

Je ne sais pas pourquoi tout le monde vous dit que vous devez le faire en deux étapes. En fait, non . Vous avez essayé d'ajouter un FOREIGN KEYqui suppose, par conception, que la colonne est là et renvoie cette erreur si la colonne n'est pas là. Si vous ajoutez le COLUMN, vous pouvez explicitement en faire un FOREIGN KEYlors de la création avec REFERENCES,

ALTER TABLE message
  ADD COLUMN sender INT
  REFERENCES users;  -- or REFERENCES table(unique_column)

Fonctionnera bien. Vous pouvez voir la syntaxe d' ALTER TABLEici,

ALTER TABLE [ IF EXISTS ] [ ONLY ] name [ * ]
action [, ... ]

Avec "action" comme,

ADD [ COLUMN ] [ IF NOT EXISTS ] column_name data_type [ COLLATE collation ] [ column_constraint [ ... ] ]

Ces exemples sont même dans les documents,

ALTER TABLE distributors
  ADD CONSTRAINT distfk
  FOREIGN KEY (address)
  REFERENCES addresses (address);

ALTER TABLE distributors
  ADD CONSTRAINT distfk
  FOREIGN KEY (address)
  REFERENCES addresses (address)
  NOT VALID;

Mais tout cela n'est pas nécessaire car nous pouvons compter sur le nom automatique et la résolution de la clé primaire (si seul le nom de la table est spécifié, vous faites référence à la clé primaire).

Evan Carroll
la source
0

CAS1: Si vous devez créer une clé étrangère lors de la création d'une nouvelle table

CREATE TABLE table1(
id SERIAL PRIMARY KEY,
column1 varchar(n) NOT NULL,
table2_id SMALLINT REFERENCES table2(id)
); 

Les commandes ci-dessus créeront une table avec le nom 'table1' et trois colonnes nommées 'id' (clé primaire), 'colonne1', 'table2_id' (clé étrangère de table1 qui fait référence à la colonne id de table2).

DATATYPE 'serial' créera la colonne qui utilise ce type de données comme colonne générée automatiquement, lorsque vous insérez des valeurs dans le tableau, vous n'avez pas besoin de mentionner cette colonne du tout, ou vous pouvez donner 'default' sans guillemets à la place de la valeur.

Une colonne de clé primaire est toujours ajoutée à l'index de la table avec la valeur 'tablename_pkey'.

Si une clé étrangère est ajoutée au moment de la création de la table, une CONTRAINTE est ajoutée avec le modèle '(present_table_name) _ (foreign_key_id_name) _fkey'.

Lors de l'ajout d'une clé étrangère, nous devons saisir le mot-clé 'REFERENCES' à côté du nom de la colonne car nous voulons dire aux postgres que cette colonne fait référence à une table puis à côté des références, nous devons donner la table pour référence et entre parenthèses donner la nom de colonne de la table référencée, généralement les clés étrangères sont données comme colonnes de clé primaire.

CAS 2: Si vous voulez une clé étrangère vers une table existante sur une colonne existante

ALTER TABLE table1
ADD CONSTRAINT table1_table2_id_id_fkey
FOREIGN KEY (table2_id) REFERENCES table2(id);

REMARQUE: les crochets '()' après FOREIGN KEY et REFERENCES tabel2 sont obligatoires, sinon postgres générera une erreur.

Ashok Allu
la source
0

Je connais le problème. Les noms des colonnes sont différents. Peut-être que dans une colonne, il y a un espace ajouté après le nom de votre colonne, alors assurez-vous soigneusement que les noms de vos colonnes ont été nommés exactement de la même manière.

XIN WANG
la source
1
OP a demandé: Cette déclaration ne crée-t-elle pas également la colonne? Il est donc évident qu'il s'attendait à ce que cela se produise.
Laurenz Albe