Jak dodać klucz obcy do istniejącej tabeli SQLite?

128

Mam następującą tabelę:

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

Jak dodać ograniczenie klucza obcego na parent_id? Załóżmy, że klucze obce są włączone.

Większość przykładów zakłada, że ​​tworzysz tabelę - chciałbym dodać ograniczenie do istniejącego.

Dane O'Connor
źródło
Polecenie SQLite ALTER obsługuje tylko „zmień nazwę tabeli” i „dodaj kolumnę”. Możemy jednak wprowadzić inne dowolne zmiany w formacie tabeli za pomocą prostej sekwencji operacji. Sprawdź moją odpowiedź
situee

Odpowiedzi:

198

Nie możesz.

Chociaż składnia SQL-92 dodawania klucza obcego do tabeli byłaby następująca:

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

SQLite nie obsługuje ten ADD CONSTRAINTwariant ALTER TABLEkomendy ( sqlite.org: SQL, SQLite nie implementuje ).

Dlatego jedynym sposobem dodania klucza obcego w sqlite 3.6.1 jest CREATE TABLEnastępujący sposób:

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

Niestety będziesz musiał zapisać istniejące dane w tabeli tymczasowej, usunąć starą tabelę, utworzyć nową tabelę z ograniczeniem FK, a następnie skopiować dane z powrotem z tabeli tymczasowej. ( sqlite.org - często zadawane pytania: Q11 )

Daniel Vassallo
źródło
28
Myślę, że łatwiej jest zmienić nazwę starej tabeli, utworzyć nową tabelę i skopiować dane z powrotem. Następnie możesz usunąć starą tabelę.
tuinstoel
Tak, to jest łatwiejsze. Właśnie cytuję sqlite FAQ: sqlite.org/faq.html#q11 . W rzeczywistości RENAME TOjest to jeden z niewielu ALTER TABLEwariantów, który jest obecnie obsługiwany w sqlite 3.
Daniel Vassallo,
3
Czy nie powinno być: FOREIGN KEY (parent_id) REFERENCES rodzic (id) Prawda, Jonathan nie podał nazwy „tabeli nadrzędnej”. Właściwie stół powinien być nazwany osobą, ale ...
igorludi
3
Wydaje mi się, że to duży problem. Zwykle podczas zrzucania bazy danych najpierw eksportuje się polecenia CREATE TABLE. Następnie polecenia INSERT INTO, a na końcu polecenia ADD CONSTRAINT. Jeśli istnieje zależność cykliczna (wartość klucza obcego) w danych, nie można wstawić danych, gdy klucze obce są wymuszane. Ale jeśli nie możesz później dodać ograniczeń klucza obcego, utkniesz. Oczywiście istnieją odroczone ograniczenia, ale jest to bardzo niezdarne.
nagylzs
9
NIE zmieniaj nazwy starej tabeli, jak wspomniano w pierwszym komentarzu, jeśli inne tabele mają odniesienia do tej tabeli! W takim przypadku będziesz musiał również odtworzyć wszystkie te tabele.
rocknow
57

Możesz dodać ograniczenie, jeśli zmienisz tabelę i dodasz kolumnę, która używa ograniczenia.

Najpierw utwórz tabelę bez parent_id:

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

Następnie zmień tabelę:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);
Jorge Novaes
źródło
2
Dobrze jest przyzwyczaić się do tej sekwencji, ale to nie odpowiada faktycznemu pytaniu: chciałbym dodać ograniczenie do istniejącego.
Wolf
9

Proszę sprawdzić https://www.sqlite.org/lang_altertable.html#otheralter

Jedynymi poleceniami zmieniającymi schemat bezpośrednio obsługiwanymi przez SQLite są polecenia „rename table” i „add column” pokazane powyżej. Jednak aplikacje mogą wprowadzać inne dowolne zmiany w formacie tabeli przy użyciu prostej sekwencji operacji. Kroki wprowadzania dowolnych zmian w projekcie schematu niektórych tabel X są następujące:

  1. Jeśli ograniczenia klucza obcego są włączone, wyłącz je za pomocą PRAGMA Foreign_keys = OFF.
  2. Rozpocznij transakcję.
  3. Zapamiętaj format wszystkich indeksów i wyzwalaczy powiązanych z tabelą X. Te informacje będą potrzebne w kroku 8 poniżej. Jednym ze sposobów jest uruchomienie kwerendy podobnej do następującej: typ SELECT, sql FROM sqlite_master WHERE tbl_name = 'X'.
  4. Użyj CREATE TABLE, aby skonstruować nową tabelę „nowa_X”, która jest w żądanym poprawionym formacie tabeli X. Upewnij się, że nazwa „nowy_X” nie koliduje z żadną istniejącą nazwą tabeli.
  5. Przenieś zawartość z X do nowy_X za pomocą instrukcji typu: INSERT INTO new_X SELECT ... FROM X.
  6. Usuń starą tabelę X: DROP TABLE X.
  7. Zmień nazwę nowego_X na X za pomocą: ALTER TABLE new_X RENAME TO X.
  8. Użyj CREATE INDEX i CREATE TRIGGER, aby zrekonstruować indeksy i wyzwalacze skojarzone z tabelą X. Być może użyj starego formatu wyzwalaczy i indeksów zapisanych z kroku 3 powyżej jako wskazówki, wprowadzając zmiany odpowiednie dla zmiany.
  9. Jeśli jakiekolwiek widoki odwołują się do tabeli X w sposób, na który wpływa zmiana schematu, usuń te widoki za pomocą DROP VIEW i utwórz je ponownie z wszelkimi zmianami niezbędnymi do uwzględnienia zmiany schematu za pomocą polecenia CREATE VIEW.
  10. Jeśli ograniczenia klucza obcego były pierwotnie włączone, uruchom PRAGMA Foreign_key_check, aby sprawdzić, czy zmiana schematu nie złamała żadnych ograniczeń klucza obcego.
  11. Zatwierdź transakcję rozpoczętą w kroku 2.
  12. Jeśli ograniczenia kluczy obcych były pierwotnie włączone, włącz je ponownie teraz.

Powyższa procedura jest całkowicie ogólna i będzie działać, nawet jeśli zmiana schematu spowoduje zmianę informacji przechowywanych w tabeli. Tak więc pełna procedura powyżej jest odpowiednia do usuwania kolumny, zmiany kolejności kolumn, dodawania lub usuwania ograniczenia UNIQUE lub klucza podstawowego, dodawania ograniczeń CHECK lub FOREIGN KEY lub NOT NULL lub zmiany typu danych dla kolumny.

situee
źródło
4

Tak, możesz, bez dodawania nowej kolumny. Musisz uważać, aby zrobić to poprawnie, aby uniknąć uszkodzenia bazy danych, dlatego przed wypróbowaniem należy wykonać pełną kopię zapasową bazy danych.

dla twojego konkretnego przykładu:

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

lub bardziej ogólnie:

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;

Tak czy inaczej, prawdopodobnie będziesz chciał najpierw zobaczyć, jaka jest definicja SQL, zanim wprowadzisz jakiekolwiek zmiany:

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

Jeśli używasz metody replace (), przed wykonaniem może okazać się pomocne, aby najpierw przetestować polecenie replace (), uruchamiając:

select replace(sql, ...) from SQLITE_MASTER where name = 'child' and type = 'table';
mwag
źródło
3

Jeśli używasz menedżera dodatku Firefox do programu sqlite-manager, możesz wykonać następujące czynności:

Zamiast upuszczać i ponownie tworzyć tabelę, można ją po prostu zmodyfikować w ten sposób.

W polu tekstowym Kolumny kliknij prawym przyciskiem myszy ostatnią wymienioną nazwę kolumny, aby wyświetlić menu kontekstowe, i wybierz opcję Edytuj kolumnę. Zwróć uwagę, że jeśli ostatnia kolumna w definicji TABELI jest KLUCZEM PODSTAWOWYM, wówczas konieczne będzie najpierw dodanie nowej kolumny, a następnie edycja typu nowej kolumny w celu dodania definicji KLUCZA OBCEGO. W polu Typ kolumny dołącz przecinek i rozszerzenie

FOREIGN KEY (parent_id) REFERENCES parent(id)

definicja po typie danych. Kliknij przycisk Zmień, a następnie kliknij przycisk Tak w oknie dialogowym Niebezpieczna operacja.

Odniesienie: Sqlite Manager

Baso
źródło
2

Możesz spróbować tego:

ALTER TABLE [Child] ADD COLUMN column_name INTEGER REFERENCES parent_table_name(column_id);
Jamshy EK
źródło
-1

Zasadniczo nie możesz, ale możesz ominąć tę sytuację.

Prawidłowym sposobem dodania ograniczenia klucza obcego do istniejącej tabeli jest następujące polecenie.

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

Następnie skopiuj dane Parent_Id do nowej Kolumny, a następnie usuń kolumnę Parent_Id . Dlatego nie ma potrzeby stosowania tabeli tymczasowej.

saeed khalafinejad
źródło
Wygląda na to, że nie przeczytałeś dokładnie pytania. Problem polegał na dodaniu tylko obcego ograniczenia, a nie dodaniu kolumny z ograniczeniem.
Wolf
Nie. Nie odpowiada na zadane pytanie.
MK
-4

Najpierw dodaj kolumnę w tabeli podrzędnej, Cidjak intwtedy alter tablez kodem poniżej. W ten sposób możesz dodać klucz obcy Cidjako klucz podstawowy tabeli nadrzędnej i używać go jako klucza obcego w tabeli podrzędnej ... mam nadzieję, że to ci pomoże, ponieważ jest dla mnie dobre:

ALTER TABLE [child] 
  ADD CONSTRAINT [CId] 
  FOREIGN KEY ([CId]) 
  REFERENCES [Parent]([CId]) 
  ON DELETE CASCADE ON UPDATE NO ACTION;
GO
Tariq Nawaz Khan
źródło
1
Nie dotyczy to SQLite. To jest również składnia MS SQL.
StilesCrisis