Come si aggiunge una chiave esterna a una tabella SQLite esistente?


128

Ho la seguente tabella:

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

Come si aggiunge un vincolo di chiave esterna parent_id? Supponiamo che le chiavi esterne siano abilitate.

La maggior parte degli esempi presume che tu stia creando la tabella: vorrei aggiungere il vincolo a uno esistente.


Il comando SQLite ALTER supporta solo "rinomina tabella" e "aggiungi colonna". Tuttavia, possiamo apportare altre modifiche arbitrarie al formato di una tabella utilizzando una semplice sequenza di operazioni. Controlla la mia risposta
situee

Risposte:


198

Non puoi.

Sebbene la sintassi SQL-92 per aggiungere una chiave esterna alla tabella sarebbe la seguente:

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

SQLite non supporta la ADD CONSTRAINTvariante del ALTER TABLEcomando ( sqlite.org: funzionalità SQL che SQLite non implementa ).

Pertanto, l'unico modo per aggiungere una chiave esterna in sqlite 3.6.1 è durante CREATE TABLEil seguente:

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

Sfortunatamente dovrai salvare i dati esistenti in una tabella temporanea, eliminare la vecchia tabella, creare la nuova tabella con il vincolo FK, quindi copiare nuovamente i dati dalla tabella temporanea. ( sqlite.org - FAQ: Q11 )


28
Penso che sia più facile rinominare la vecchia tabella, creare la nuova tabella e copiare nuovamente i dati. Quindi è possibile eliminare la vecchia tabella.
tuinstoel

Sì, è più facile. Stavo solo citando le FAQ di sqlite: sqlite.org/faq.html#q11 . In effetti, RENAME TOè una delle poche ALTER TABLEvarianti attualmente supportate in sqlite 3.
Daniel Vassallo

3
Non dovrebbe essere: CHIAVE ESTERA (parent_id) RIFERIMENTI parent (id) Vero, Jonathan non ha fornito il nome della "tabella genitore". In effetti la tabella dovrebbe essere nominata persona, ma ...
igorludi

3
Questo sembra essere un grosso problema per me. Di solito quando si esegue il dump di un database, si esportano prima i comandi CREATE TABLE. Quindi INSERT INTO e infine ADD CONSTRAINT. Se è presente una dipendenza circolare (valore di chiave esterna) nei dati, non è possibile inserire i dati mentre sono applicate le chiavi esterne. Ma se non puoi aggiungere i vincoli di chiave esterna in un secondo momento, sei bloccato. Naturalmente ci sono vincoli differiti, ma questo è molto goffo.
Nagylzs

9
NON rinominare la vecchia tabella come detto nel primo commento se altre tabelle hanno riferimenti a questa tabella! In questo caso dovrai ricreare anche tutte queste tabelle.
rocknow

57

È possibile aggiungere il vincolo se si modifica la tabella e si aggiunge la colonna che utilizza il vincolo.

Innanzitutto, crea una tabella senza parent_id:

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

Quindi, modifica la tabella:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);

2
È bello abituarsi a questa sequenza, ma questo non risponde alla domanda vera e propria: vorrei aggiungere il vincolo a uno esistente.
Wolf,

9

Si prega di controllare https://www.sqlite.org/lang_altertable.html#otheralter

Gli unici comandi che alterano lo schema direttamente supportati da SQLite sono i comandi "rinomina tabella" e "aggiungi colonna" mostrati sopra. Tuttavia, le applicazioni possono apportare altre modifiche arbitrarie al formato di una tabella utilizzando una semplice sequenza di operazioni. I passaggi per apportare modifiche arbitrarie alla progettazione dello schema di alcune tabelle X sono i seguenti:

  1. Se i vincoli di chiave esterna sono abilitati, disabilitarli utilizzando PRAGMA foreign_keys = OFF.
  2. Avvia una transazione.
  3. Ricorda il formato di tutti gli indici e i trigger associati alla tabella X. Queste informazioni saranno necessarie nel passaggio 8 di seguito. Un modo per farlo è eseguire una query come la seguente: SELECT type, sql FROM sqlite_master WHERE tbl_name = 'X'.
  4. Utilizzare CREATE TABLE per costruire una nuova tabella "new_X" che sia nel formato rivisto desiderato della tabella X. Assicurarsi che il nome "new_X" non entri in conflitto con il nome della tabella esistente, ovviamente.
  5. Trasferisci il contenuto da X a new_X usando un'istruzione come: INSERT INTO new_X SELECT ... FROM X.
  6. Elimina la vecchia tabella X: DROP TABLE X.
  7. Modificare il nome di new_X in X utilizzando: ALTER TABLE new_X RENAME TO X.
  8. Utilizzare CREATE INDEX e CREATE TRIGGER per ricostruire gli indici e i trigger associati alla tabella X. Forse utilizzare il vecchio formato dei trigger e degli indici salvati dal passaggio 3 sopra come guida, apportando le modifiche appropriate per l'alterazione.
  9. Se qualche vista fa riferimento alla tabella X in un modo che è influenzato dalla modifica dello schema, rilasciare quelle viste utilizzando DROP VIEW e ricrearle con le modifiche necessarie per accogliere la modifica dello schema utilizzando CREATE VIEW.
  10. Se i vincoli di chiave esterna erano originariamente abilitati, eseguire PRAGMA foreign_key_check per verificare che la modifica dello schema non abbia infranto alcun vincolo di chiave esterna.
  11. Eseguire il commit della transazione avviata nel passaggio 2.
  12. Se i vincoli delle chiavi esterne erano originariamente abilitati, riabilitali ora.

La procedura precedente è completamente generale e funzionerà anche se la modifica dello schema provoca la modifica delle informazioni memorizzate nella tabella. Pertanto, la procedura completa sopra è appropriata per eliminare una colonna, modificare l'ordine delle colonne, aggiungere o rimuovere un vincolo UNIQUE o PRIMARY KEY, aggiungere CHECK o FOREIGN KEY o vincoli NOT NULL o modificare il tipo di dati per una colonna, ad esempio.


4

Sì, puoi, senza aggiungere una nuova colonna. Devi stare attento a farlo correttamente per evitare di danneggiare il database, quindi dovresti eseguire il backup completo del database prima di provare questo.

per il tuo esempio specifico:

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

o più in generale:

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 ogni caso, probabilmente vorrai prima vedere qual è la definizione SQL prima di apportare modifiche:

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

Se usi l'approccio replace (), potresti trovare utile, prima di eseguire, testare prima il tuo comando replace () eseguendo:

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

3

Se stai usando il componente aggiuntivo per Firefox sqlite-manager puoi fare quanto segue:

Invece di rilasciare e creare di nuovo la tabella, è possibile modificarla in questo modo.

Nella casella di testo Colonne, fare clic con il pulsante destro del mouse sul nome dell'ultima colonna elencata per visualizzare il menu di scelta rapida e selezionare Modifica colonna. Si noti che se l'ultima colonna nella definizione TABELLA è la CHIAVE PRIMARIA, sarà necessario prima aggiungere una nuova colonna e quindi modificare il tipo di colonna della nuova colonna per aggiungere la definizione CHIAVE ESTERA. Nella casella Tipo di colonna, aggiungi una virgola e il

FOREIGN KEY (parent_id) REFERENCES parent(id)

definizione dopo il tipo di dati. Fare clic sul pulsante Modifica, quindi fare clic sul pulsante Sì nella finestra di dialogo Operazione pericolosa.

Riferimento: Sqlite Manager


2

Puoi provare questo:

ALTER TABLE [Child] ADD COLUMN column_name INTEGER REFERENCES parent_table_name(column_id);

-1

Fondamentalmente non puoi ma puoi aggirare la situazione.

Il modo corretto per aggiungere il vincolo di chiave esterna a una tabella esistente è il seguente comando.

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

Quindi copia i dati parent_Id in newCol e quindi elimina la colonna Parent_Id . Quindi, non c'è bisogno di una tabella temporanea.


Sembra che tu non abbia letto attentamente la domanda. Il problema era aggiungere solo un vincolo esterno, non aggiungere una colonna con un vincolo.
Wolf

No. Non risponde alla domanda posta.
MK

-4

Per prima cosa aggiungi una colonna nella tabella figlio Cidcome intpoi alter tablecon il codice sottostante. In questo modo puoi aggiungere la chiave esterna Cidcome chiave primaria della tabella genitore e usarla come chiave esterna nella tabella figlio ... spero che ti possa aiutare perché è buono per me:

ALTER TABLE [child] 
  ADD CONSTRAINT [CId] 
  FOREIGN KEY ([CId]) 
  REFERENCES [Parent]([CId]) 
  ON DELETE CASCADE ON UPDATE NO ACTION;
GO

1
Questo non è valido in SQLite. Anche questa è la sintassi MS SQL.
StilesCrisis
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.