Comment ajouter une clé étrangère à une table SQLite existante?

128

J'ai le tableau suivant:

CREATE TABLE child( 
  id INTEGER PRIMARY KEY, 
  parent_id INTEGER, 
  description TEXT);

Comment ajouter une contrainte de clé étrangère sur parent_id? Supposons que les clés étrangères sont activées.

La plupart des exemples supposent que vous créez la table - j'aimerais ajouter la contrainte à une table existante.

Dane O'Connor
la source
La commande SQLite ALTER ne prend en charge que «renommer la table» et «ajouter une colonne». Cependant, nous pouvons apporter d'autres modifications arbitraires au format d'une table en utilisant une simple séquence d'opérations. Vérifier ma réponse
situee le

Réponses:

198

Vous ne pouvez pas.

Bien que la syntaxe SQL-92 pour ajouter une clé étrangère à votre table soit la suivante:

ALTER TABLE child ADD CONSTRAINT fk_child_parent
                  FOREIGN KEY (parent_id) 
                  REFERENCES parent(id);

SQLite ne prend pas en charge la ADD CONSTRAINTvariante de la ALTER TABLEcommande ( sqlite.org: fonctionnalités SQL que SQLite n'implémente pas ).

Par conséquent, la seule façon d'ajouter une clé étrangère dans sqlite 3.6.1 est la CREATE TABLEsuivante:

CREATE TABLE child ( 
    id           INTEGER PRIMARY KEY, 
    parent_id    INTEGER, 
    description  TEXT,
    FOREIGN KEY (parent_id) REFERENCES parent(id)
);

Malheureusement, vous devrez enregistrer les données existantes dans une table temporaire, supprimer l'ancienne table, créer la nouvelle table avec la contrainte FK, puis recopier les données à partir de la table temporaire. ( sqlite.org - FAQ: Q11 )

Daniel Vassallo
la source
28
Je pense qu'il est plus facile de renommer l'ancienne table, de créer la nouvelle table et de recopier les données. Ensuite, vous pouvez supprimer l'ancienne table.
tuinstoel
Oui, c'est plus facile. Je citais juste la FAQ sqlite: sqlite.org/faq.html#q11 . En fait, RENAME TOest l'une des rares ALTER TABLEvariantes actuellement prise en charge dans sqlite 3.
Daniel Vassallo
3
Ne devrait-il pas être: FOREIGN KEY (parent_id) REFERENCES parent (id) True, Jonathan n'a pas donné le nom de la "table parent". En fait, la table devrait être nommée personne, mais ...
igorludi
3
Cela me semble être un gros problème. Habituellement, lorsque vous videz une base de données, vous exportez d'abord les commandes CREATE TABLE. Puis les commandes INSERT INTO, et enfin les commandes ADD CONSTRAINT. S'il existe une dépendance circulaire (valeur de clé étrangère) dans vos données, vous ne pouvez pas insérer vos données pendant que les clés étrangères sont appliquées. Mais si vous ne pouvez pas ajouter les contraintes de clé étrangère ultérieurement, vous êtes bloqué. Bien sûr, il y a des contraintes différées, mais c'est très maladroit.
nagylzs
9
NE renommez PAS l'ancienne table comme indiqué dans le premier commentaire si d'autres tables ont des références à cette table! Dans ce cas, vous devrez également recréer toutes ces tables.
rocknow
57

Vous pouvez ajouter la contrainte si vous modifiez la table et ajoutez la colonne qui utilise la contrainte.

Tout d'abord, créez une table sans le parent_id:

CREATE TABLE child( 
  id INTEGER PRIMARY KEY,  
  description TEXT);

Ensuite, modifiez la table:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);
Jorge Novaes
la source
2
C'est bien de s'habituer à cette séquence, mais cela ne répond pas à la question réelle: j'aimerais ajouter la contrainte à une existante.
Wolf
9

Veuillez vérifier https://www.sqlite.org/lang_altertable.html#otheralter

Les seules commandes de modification de schéma directement prises en charge par SQLite sont les commandes «renommer la table» et «ajouter une colonne» présentées ci-dessus. Cependant, les applications peuvent apporter d'autres modifications arbitraires au format d'une table à l'aide d'une simple séquence d'opérations. Les étapes pour apporter des modifications arbitraires à la conception de schéma de certaines tables X sont les suivantes:

  1. Si les contraintes de clé étrangère sont activées, désactivez-les à l'aide de PRAGMA Foreign_keys = OFF.
  2. Démarrez une transaction.
  3. Souvenez-vous du format de tous les index et déclencheurs associés à la table X. Ces informations seront nécessaires à l'étape 8 ci-dessous. Une façon de faire est d'exécuter une requête comme celle-ci: SELECT type, sql FROM sqlite_master WHERE tbl_name = 'X'.
  4. Utilisez CREATE TABLE pour construire une nouvelle table "new_X" qui est dans le format révisé souhaité de la table X. Assurez-vous que le nom "new_X" n'entre pas en conflit avec un nom de table existant, bien sûr.
  5. Transférez le contenu de X vers new_X en utilisant une instruction comme: INSERT INTO new_X SELECT ... FROM X.
  6. Supprimez l'ancienne table X: DROP TABLE X.
  7. Changez le nom de new_X en X en utilisant: ALTER TABLE new_X RENAME TO X.
  8. Utilisez CREATE INDEX et CREATE TRIGGER pour reconstruire les index et les déclencheurs associés à la table X. Utilisez peut-être l'ancien format des déclencheurs et des index enregistrés à l'étape 3 ci-dessus comme guide, en apportant les modifications appropriées pour la modification.
  9. Si des vues font référence à la table X d'une manière qui est affectée par le changement de schéma, supprimez ces vues à l'aide de DROP VIEW et recréez-les avec les modifications nécessaires pour prendre en charge la modification de schéma à l'aide de CREATE VIEW.
  10. Si les contraintes de clé étrangère ont été activées à l'origine, exécutez PRAGMA Foreign_key_check pour vérifier que le changement de schéma n'a rompu aucune contrainte de clé étrangère.
  11. Validez la transaction commencée à l'étape 2.
  12. Si les contraintes de clés étrangères ont été activées à l'origine, réactivez-les maintenant.

La procédure ci-dessus est tout à fait générale et fonctionnera même si la modification de schéma entraîne la modification des informations stockées dans la table. Ainsi, la procédure complète ci-dessus est appropriée pour supprimer une colonne, changer l'ordre des colonnes, ajouter ou supprimer une contrainte UNIQUE ou PRIMARY KEY, ajouter des contraintes CHECK ou FOREIGN KEY ou NOT NULL, ou modifier le type de données d'une colonne, par exemple.

situé
la source
4

Oui, vous pouvez, sans ajouter de nouvelle colonne. Vous devez faire attention à le faire correctement afin d'éviter de corrompre la base de données, vous devez donc sauvegarder complètement votre base de données avant d'essayer cela.

pour votre exemple spécifique:

CREATE TABLE child(
  id INTEGER PRIMARY KEY,
  parent_id INTEGER,
  description TEXT
);

--- create the table we want to reference
create table parent(id integer not null primary key);

--- now we add the foreign key
pragma writable_schema=1;
update SQLITE_MASTER set sql = replace(sql, 'description TEXT)',
    'description TEXT, foreign key (parent_id) references parent(id))'
) where name = 'child' and type = 'table';

--- test the foreign key
pragma foreign_keys=on;
insert into parent values(1);
insert into child values(1, 1, 'hi'); --- works
insert into child values(2, 2, 'bye'); --- fails, foreign key violation

ou plus généralement:

pragma writable_schema=1;

// replace the entire table's SQL definition, where new_sql_definition contains the foreign key clause you want to add
UPDATE SQLITE_MASTER SET SQL = new_sql_definition where name = 'child' and type = 'table';

// alternatively, you might find it easier to use replace, if you can match the exact end of the sql definition
// for example, if the last column was my_last_column integer not null:
UPDATE SQLITE_MASTER SET SQL = replace(sql, 'my_last_column integer not null', 'my_last_column integer not null, foreign key (col1, col2) references other_table(col1, col2)') where name = 'child' and type = 'table';

pragma writable_schema=0;

Dans tous les cas, vous voudrez probablement d'abord voir quelle est la définition SQL avant d'apporter des modifications:

select sql from SQLITE_MASTER where name = 'child' and type = 'table';

Si vous utilisez l'approche replace (), vous trouverez peut-être utile, avant de l'exécuter, de tester d'abord votre commande replace () en exécutant:

select replace(sql, ...) from SQLITE_MASTER where name = 'child' and type = 'table';
mwag
la source
3

Si vous utilisez le module complémentaire de Firefox sqlite-manager, vous pouvez effectuer les opérations suivantes:

Au lieu de supprimer et de créer à nouveau la table, on peut simplement la modifier comme ceci.

Dans la zone de texte Colonnes, cliquez avec le bouton droit sur le nom de la dernière colonne répertoriée pour afficher le menu contextuel et sélectionnez Modifier la colonne. Notez que si la dernière colonne de la définition TABLE est la CLÉ PRIMAIRE, il sera alors nécessaire d'ajouter d'abord une nouvelle colonne, puis d'éditer le type de colonne de la nouvelle colonne afin d'ajouter la définition de CLÉ ÉTRANGÈRE. Dans la zone Type de colonne, ajoutez une virgule et le

FOREIGN KEY (parent_id) REFERENCES parent(id)

définition après le type de données. Cliquez sur le bouton Modifier, puis sur le bouton Oui dans la boîte de dialogue Opération dangereuse.

Référence: Sqlite Manager

Baso
la source
2

Vous pouvez essayer ceci:

ALTER TABLE [Child] ADD COLUMN column_name INTEGER REFERENCES parent_table_name(column_id);
Jamshy EK
la source
-1

En gros, vous ne pouvez pas mais vous pouvez contourner la situation.

La manière correcte d'ajouter la contrainte de clé étrangère à une table existante est la commande suivante.

db.execSQL("alter table child add column newCol integer REFERENCES parent(parent_Id)");

Copiez ensuite les données parent_Id dans le newCol , puis supprimez la colonne Parent_Id . Par conséquent, pas besoin de table temporaire.

saeed khalafinejad
la source
Il semble que vous n'ayez pas lu la question attentivement. Le problème était d'ajouter uniquement une contrainte étrangère, pas d'ajouter une colonne avec une contrainte.
Wolf
Nan. Il ne répond pas à la question posée.
MK
-4

Ajoutez d'abord une colonne dans la table enfant Cidcomme intpuis alter tableavec le code ci-dessous. De cette façon, vous pouvez ajouter la clé étrangère Cidcomme clé primaire de la table parent et l'utiliser comme clé étrangère dans la table enfant ... j'espère que cela vous aidera car c'est bon pour moi:

ALTER TABLE [child] 
  ADD CONSTRAINT [CId] 
  FOREIGN KEY ([CId]) 
  REFERENCES [Parent]([CId]) 
  ON DELETE CASCADE ON UPDATE NO ACTION;
GO
Tariq Nawaz Khan
la source
1
Ce n'est pas valide dans SQLite. C'est aussi la syntaxe MS SQL.
StilesCrisis