Как добавить внешний ключ в существующую таблицу SQLite?

128

У меня есть следующая таблица:

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

Как добавить ограничение внешнего ключа parent_id? Предположим, что внешние ключи включены.

В большинстве примеров предполагается, что вы создаете таблицу - я бы хотел добавить ограничение к существующей.

Дэйн О'Коннор
источник
Команда SQLite ALTER поддерживает только «переименовать таблицу» и «добавить столбец». Однако мы можем внести другие произвольные изменения в формат таблицы, используя простую последовательность операций. Проверьте мой ответ
ситуация

Ответы:

198

Вы не можете.

Хотя синтаксис SQL-92 для добавления внешнего ключа в вашу таблицу будет следующим:

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

SQLite не поддерживает на ADD CONSTRAINTвариант ALTER TABLEкоманды ( sqlite.org: SQL функций , которые SQLite не реализует ).

Следовательно, единственный способ добавить внешний ключ в sqlite 3.6.1 - это выполнить CREATE TABLEследующие действия:

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

К сожалению, вам придется сохранить существующие данные во временную таблицу, удалить старую таблицу, создать новую таблицу с ограничением FK, а затем скопировать данные обратно из временной таблицы. ( sqlite.org - FAQ: Q11 )

Даниэль Вассалло
источник
28
Я думаю, что проще переименовать старую таблицу, создать новую и скопировать данные обратно. Затем вы можете отбросить старую таблицу.
Tuinstoel
Да, так проще. Я просто цитировал FAQ по sqlite: sqlite.org/faq.html#q11 . Фактически, RENAME TOэто один из немногих ALTER TABLEвариантов, который в настоящее время поддерживается в sqlite 3.
Даниэль Вассалло,
3
Разве это не должно быть: FOREIGN KEY (parent_id) REFERENCES parent (id) Верно, Джонатан не назвал имя «родительской таблицы». На самом деле, стол должен быть назван человеком, но ...
igorludi 07
3
Мне это кажется большой проблемой. Обычно, когда вы создаете дамп базы данных, вы сначала экспортируете команды CREATE TABLE. Затем команды INSERT INTO и, наконец, команды ADD CONSTRAINT. Если в ваших данных существует циклическая зависимость (значение внешнего ключа), вы не можете вставить свои данные, пока внешние ключи применяются. Но если вы не можете добавить ограничения внешнего ключа позже, вы застряли. Конечно, есть отложенные ограничения, но это очень коряво.
nagylzs
9
НЕ переименовывайте старую таблицу, как сказано в первом комментарии, если другие таблицы имеют ссылки на эту таблицу! В этом случае вам также придется воссоздавать все эти таблицы.
rocknow
57

Вы можете добавить ограничение, если измените таблицу и добавите столбец, который использует ограничение.

Сначала создайте таблицу без parent_id:

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

Затем измените таблицу:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);
Хорхе Новаес
источник
2
Хорошо привыкнуть к этой последовательности, но это не дает ответа на вопрос: я хотел бы добавить ограничение к существующему.
Wolf
9

Пожалуйста, проверьте https://www.sqlite.org/lang_altertable.html#otheralter

Единственные команды изменения схемы, напрямую поддерживаемые SQLite, - это команды «переименовать таблицу» и «добавить столбец», показанные выше. Однако приложения могут вносить другие произвольные изменения в формат таблицы, используя простую последовательность операций. Шаги по внесению произвольных изменений в схему некоторой таблицы X следующие:

  1. Если ограничения внешнего ключа включены, отключите их с помощью PRAGMA foreign_keys = OFF.
  2. Начать транзакцию.
  3. Запомните формат всех индексов и триггеров, связанных с таблицей X. Эта информация понадобится на шаге 8 ниже. Один из способов сделать это - выполнить следующий запрос: SELECT type, sql FROM sqlite_master WHERE tbl_name = 'X'.
  4. Используйте CREATE TABLE для создания новой таблицы «new_X», которая имеет желаемый измененный формат таблицы X. Конечно, убедитесь, что имя «new_X» не конфликтует с каким-либо существующим именем таблицы.
  5. Перенести содержимое из X в new_X с помощью оператора типа: INSERT INTO new_X SELECT ... FROM X.
  6. Отбросьте старую таблицу X: DROP TABLE X.
  7. Измените имя new_X на X, используя: ALTER TABLE new_X RENAME TO X.
  8. Используйте CREATE INDEX и CREATE TRIGGER для восстановления индексов и триггеров, связанных с таблицей X. Возможно, используйте старый формат триггеров и индексов, сохраненных на шаге 3 выше, в качестве руководства, внося соответствующие изменения для внесения изменений.
  9. Если какие-либо представления ссылаются на таблицу X способом, на который влияет изменение схемы, отбросьте эти представления с помощью DROP VIEW и воссоздайте их с любыми изменениями, необходимыми для адаптации к изменению схемы с помощью CREATE VIEW.
  10. Если ограничения внешнего ключа были изначально включены, запустите PRAGMA foreign_key_check, чтобы убедиться, что изменение схемы не нарушило никаких ограничений внешнего ключа.
  11. Зафиксируйте транзакцию, начатую на шаге 2.
  12. Если ограничения внешних ключей были изначально включены, включите их сейчас.

Вышеупомянутая процедура является полностью общей и будет работать, даже если изменение схемы приведет к изменению информации, хранящейся в таблице. Таким образом, описанная выше полная процедура подходит для удаления столбца, изменения порядка столбцов, добавления или удаления ограничения UNIQUE или PRIMARY KEY, добавления ограничений CHECK, FOREIGN KEY или NOT NULL или изменения типа данных для столбца, например.

situee
источник
4

Да, можно без добавления нового столбца. Вы должны быть осторожны, чтобы сделать это правильно, чтобы не повредить базу данных, поэтому вам следует полностью создать резервную копию своей базы данных, прежде чем пытаться это сделать.

для вашего конкретного примера:

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

или в более общем плане:

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;

В любом случае, вы, вероятно, захотите сначала увидеть, что такое определение SQL, прежде чем вносить какие-либо изменения:

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

Если вы используете подход replace (), вам может быть полезно перед выполнением сначала протестировать вашу команду replace (), запустив:

select replace(sql, ...) from SQLITE_MASTER where name = 'child' and type = 'table';
mwag
источник
3

Если вы используете надстройку sqlite-manager Firefox, вы можете сделать следующее:

Вместо того, чтобы отбрасывать и снова создавать таблицу, можно просто изменить ее вот так.

В текстовом поле «Столбцы» щелкните правой кнопкой мыши последнее имя столбца в списке, чтобы вызвать контекстное меню, и выберите «Изменить столбец». Обратите внимание, что если последний столбец в определении ТАБЛИЦЫ является ПЕРВИЧНЫМ КЛЮЧОМ, тогда необходимо будет сначала добавить новый столбец, а затем отредактировать тип столбца нового столбца, чтобы добавить определение FOREIGN KEY. В поле Тип столбца добавьте запятую и

FOREIGN KEY (parent_id) REFERENCES parent(id)

определение после типа данных. Нажмите кнопку «Изменить», а затем нажмите кнопку «Да» в диалоговом окне «Опасная операция».

Ссылка: Sqlite Manager

Baso
источник
2

Вы можете попробовать это:

ALTER TABLE [Child] ADD COLUMN column_name INTEGER REFERENCES parent_table_name(column_id);
Джамши Е.К.
источник
-1

В принципе, нельзя, но можно обойти ситуацию.

Правильный способ добавить ограничение внешнего ключа к существующей таблице - это следующая команда.

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

Затем скопируйте данные parent_Id в newCol и удалите столбец Parent_Id . Следовательно, нет необходимости во временной таблице.

Саид Халафинеджад
источник
Похоже, вы не внимательно прочитали вопрос. Проблема заключалась в том, чтобы добавить только внешнее ограничение, а не добавить столбец с ограничением.
Wolf
Нет. Он не отвечает на заданный вопрос.
MK
-4

Сначала добавьте столбец в дочернюю таблицу Cid , как intтогда alter tableс кодом ниже. Таким образом, вы можете добавить внешний ключ Cidв качестве первичного ключа родительской таблицы и использовать его в качестве внешнего ключа в дочерней таблице ... надеюсь, это поможет вам, поскольку это хорошо для меня:

ALTER TABLE [child] 
  ADD CONSTRAINT [CId] 
  FOREIGN KEY ([CId]) 
  REFERENCES [Parent]([CId]) 
  ON DELETE CASCADE ON UPDATE NO ACTION;
GO
Тарик Наваз Хан
источник
1
Это недопустимо в SQLite. Также это синтаксис MS SQL.
StilesCrisis