Wie füge ich einer vorhandenen SQLite-Tabelle einen Fremdschlüssel hinzu?

128

Ich habe folgende Tabelle:

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

Wie füge ich eine Fremdschlüsseleinschränkung hinzu parent_id? Angenommen, Fremdschlüssel sind aktiviert.

In den meisten Beispielen wird davon ausgegangen, dass Sie die Tabelle erstellen. Ich möchte die Einschränkung einer vorhandenen hinzufügen.

Däne O'Connor
quelle
Der SQLite ALTER-Befehl unterstützt nur "Tabelle umbenennen" und "Spalte hinzufügen". Mit einer einfachen Abfolge von Operationen können wir jedoch andere willkürliche Änderungen am Format einer Tabelle vornehmen. Überprüfen Sie meine Antwort
situee

Antworten:

198

Das kannst du nicht.

Die SQL-92-Syntax zum Hinzufügen eines Fremdschlüssels zu Ihrer Tabelle lautet zwar wie folgt:

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

SQLite unterstützt die ADD CONSTRAINTVariante des ALTER TABLEBefehls nicht ( sqlite.org: SQL-Funktionen, die SQLite nicht implementiert ).

Daher können Sie in sqlite 3.6.1 nur CREATE TABLEwie folgt einen Fremdschlüssel hinzufügen :

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

Leider müssen Sie die vorhandenen Daten in einer temporären Tabelle speichern, die alte Tabelle löschen, die neue Tabelle mit der FK-Einschränkung erstellen und die Daten dann wieder aus der temporären Tabelle kopieren. ( sqlite.org - FAQ: Q11 )

Daniel Vassallo
quelle
28
Ich denke, es ist einfacher, die alte Tabelle umzubenennen, die neue Tabelle zu erstellen und die Daten wieder zu kopieren. Dann können Sie die alte Tabelle löschen.
Tuinstoel
Ja das ist einfacher Ich habe gerade die SQLite-FAQ zitiert: sqlite.org/faq.html#q11 . In der Tat RENAME TOist eine der wenigen ALTER TABLEVarianten, die derzeit in SQLite 3 unterstützt wird.
Daniel Vassallo
3
Sollte es nicht sein: FOREIGN KEY (parent_id) REFERENCES parent (id) Richtig, Jonathan hat den Namen der "übergeordneten Tabelle" nicht angegeben. In der Tat sollte die Tabelle Person genannt werden, aber ...
igorludi
3
Dies scheint mir ein großes Problem zu sein. Wenn Sie eine Datenbank sichern, exportieren Sie normalerweise zuerst die Befehle CREATE TABLE. Dann INSERT INTO-Befehle und schließlich ADD CONSTRAINT-Befehle. Wenn Ihre Daten eine zirkuläre Abhängigkeit (Fremdschlüsselwert) aufweisen, können Sie Ihre Daten nicht einfügen, während Fremdschlüssel erzwungen werden. Wenn Sie die Fremdschlüsseleinschränkungen jedoch später nicht hinzufügen können, stecken Sie fest. Natürlich gibt es aufgeschobene Einschränkungen, aber das ist sehr ungeschickt.
Nagylzs
9
Benennen Sie die alte Tabelle NICHT wie im ersten Kommentar angegeben um, wenn andere Tabellen Verweise auf diese Tabelle enthalten! In diesem Fall müssen Sie auch alle diese Tabellen neu erstellen.
Rocknow
57

Sie können die Einschränkung hinzufügen, wenn Sie die Tabelle ändern und die Spalte hinzufügen, die die Einschränkung verwendet.

Erstellen Sie zunächst eine Tabelle ohne die parent_id:

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

Ändern Sie dann die Tabelle:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);
Jorge Novaes
quelle
2
Es ist gut, sich an diese Sequenz zu gewöhnen, aber dies beantwortet nicht die eigentliche Frage: Ich möchte die Einschränkung zu einer vorhandenen hinzufügen.
Wolf
9

Bitte überprüfen Sie https://www.sqlite.org/lang_altertable.html#otheralter

Die einzigen Befehle zur Schemaänderung, die direkt von SQLite unterstützt werden, sind die oben gezeigten Befehle "Tabelle umbenennen" und "Spalte hinzufügen". Anwendungen können jedoch mithilfe einer einfachen Abfolge von Operationen andere willkürliche Änderungen am Format einer Tabelle vornehmen. Die Schritte zum Vornehmen beliebiger Änderungen am Schemadesign einiger Tabellen X sind wie folgt:

  1. Wenn Fremdschlüsseleinschränkungen aktiviert sind, deaktivieren Sie sie mit PRAGMA Foreign_keys = OFF.
  2. Starten Sie eine Transaktion.
  3. Beachten Sie das Format aller mit Tabelle X verknüpften Indizes und Trigger. Diese Informationen werden in Schritt 8 unten benötigt. Eine Möglichkeit, dies zu tun, besteht darin, eine Abfrage wie die folgende auszuführen: SELECT-Typ, sql FROM sqlite_master WHERE tbl_name = 'X'.
  4. Verwenden Sie CREATE TABLE, um eine neue Tabelle "new_X" im gewünschten überarbeiteten Format von Tabelle X zu erstellen. Stellen Sie sicher, dass der Name "new_X" natürlich nicht mit einem vorhandenen Tabellennamen kollidiert.
  5. Übertragen Sie Inhalte von X in new_X mit einer Anweisung wie: INSERT INTO new_X SELECT ... FROM X.
  6. Löschen Sie die alte Tabelle X: DROP TABLE X.
  7. Ändern Sie den Namen von new_X in X mit: ALTER TABLE new_X RENAME TO X.
  8. Verwenden Sie CREATE INDEX und CREATE TRIGGER, um die mit Tabelle X verknüpften Indizes und Trigger zu rekonstruieren. Verwenden Sie möglicherweise das alte Format der in Schritt 3 oben gespeicherten Trigger und Indizes als Richtlinie, und nehmen Sie die für die Änderung geeigneten Änderungen vor.
  9. Wenn Ansichten in einer Weise auf Tabelle X verweisen, die von der Schemaänderung betroffen ist, löschen Sie diese Ansichten mit DROP VIEW und erstellen Sie sie mit den erforderlichen Änderungen neu, um die Schemaänderung mit CREATE VIEW zu berücksichtigen.
  10. Wenn ursprünglich Fremdschlüsseleinschränkungen aktiviert waren, führen Sie PRAGMA Foreign_key_check aus, um sicherzustellen, dass durch die Schemaänderung keine Fremdschlüsseleinschränkungen verletzt wurden.
  11. Übernehmen Sie die in Schritt 2 gestartete Transaktion.
  12. Wenn ursprünglich Fremdschlüsseleinschränkungen aktiviert waren, aktivieren Sie sie jetzt erneut.

Das obige Verfahren ist völlig allgemein gehalten und funktioniert auch dann, wenn die Schemaänderung dazu führt, dass sich die in der Tabelle gespeicherten Informationen ändern. Die obige vollständige Prozedur eignet sich daher zum Löschen einer Spalte, zum Ändern der Spaltenreihenfolge, zum Hinzufügen oder Entfernen einer EINZIGARTIGEN Einschränkung oder eines PRIMARY KEY, zum Hinzufügen von CHECK- oder FOREIGN KEY- oder NOT NULL-Einschränkungen oder zum Ändern des Datentyps für eine Spalte.

Situee
quelle
4

Ja, Sie können, ohne eine neue Spalte hinzuzufügen. Sie müssen vorsichtig sein, um eine korrekte Beschädigung der Datenbank zu vermeiden. Daher sollten Sie Ihre Datenbank vollständig sichern, bevor Sie dies versuchen.

für Ihr spezifisches Beispiel:

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

oder allgemeiner:

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;

In beiden Fällen möchten Sie wahrscheinlich zuerst die SQL-Definition überprüfen, bevor Sie Änderungen vornehmen:

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

Wenn Sie den replace () -Ansatz verwenden, ist es möglicherweise hilfreich, vor der Ausführung zunächst den Befehl replace () zu testen, indem Sie Folgendes ausführen:

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

Wenn Sie den Firefox-Add-On-SQLite-Manager verwenden, können Sie Folgendes tun:

Anstatt die Tabelle zu löschen und erneut zu erstellen, kann man sie einfach so ändern.

Klicken Sie im Textfeld Spalten mit der rechten Maustaste auf den zuletzt aufgelisteten Spaltennamen, um das Kontextmenü aufzurufen, und wählen Sie Spalte bearbeiten. Beachten Sie, dass, wenn die letzte Spalte in der TABLE-Definition der PRIMARY KEY ist, zuerst eine neue Spalte hinzugefügt und dann der Spaltentyp der neuen Spalte bearbeitet werden muss, um die FOREIGN KEY-Definition hinzuzufügen. Fügen Sie im Feld Spaltentyp ein Komma und das

FOREIGN KEY (parent_id) REFERENCES parent(id)

Definition nach Datentyp. Klicken Sie auf die Schaltfläche Ändern und dann im Dialogfeld Gefährlicher Betrieb auf die Schaltfläche Ja.

Referenz: Sqlite Manager

Baso
quelle
2

Sie können dies versuchen:

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

Grundsätzlich können Sie nicht, aber Sie können die Situation umgehen.

Der richtige Befehl zum Hinzufügen der Fremdschlüsseleinschränkung zu einer vorhandenen Tabelle ist der folgende Befehl.

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

Kopieren Sie dann die parent_Id- Daten in die newCol und löschen Sie die Parent_Id- Spalte. Daher ist keine temporäre Tabelle erforderlich.

saeed khalafinejad
quelle
Anscheinend haben Sie die Frage nicht sorgfältig gelesen. Das Problem bestand darin, nur eine fremde Einschränkung hinzuzufügen, keine Spalte mit einer Einschränkung.
Wolf
Nee. Die gestellte Frage wird nicht beantwortet.
MK
-4

Fügen Sie zuerst eine Spalte in die untergeordnete Tabelle ein Cid, intdann alter tablemit dem folgenden Code. Auf diese Weise können Sie den Fremdschlüssel Cidals Primärschlüssel der übergeordneten Tabelle hinzufügen und als Fremdschlüssel in der untergeordneten Tabelle verwenden. Ich hoffe, er hilft Ihnen, da er gut für mich ist:

ALTER TABLE [child] 
  ADD CONSTRAINT [CId] 
  FOREIGN KEY ([CId]) 
  REFERENCES [Parent]([CId]) 
  ON DELETE CASCADE ON UPDATE NO ACTION;
GO
Tariq Nawaz Khan
quelle
1
Dies ist in SQLite nicht gültig. Dies ist auch die MS SQL-Syntax.
StilesCrisis