如何将外键添加到现有SQLite表?

Dan*_*nor 118 sql sqlite ddl foreign-keys

我有下表:

CREATE TABLE child( 
  id INTEGER PRIMARY KEY, 
  parent_id INTEGER, 
  description TEXT);
Run Code Online (Sandbox Code Playgroud)

如何添加外键约束parent_id?假设启用了外键.

大多数示例假设您正在创建表 - 我想将约束添加到现有表中.

Dan*_*llo 187

你不能.

虽然向表中添加外键的SQL-92语法如下:

ALTER TABLE child ADD CONSTRAINT fk_child_parent
                  FOREIGN KEY (parent_id) 
                  REFERENCES parent(id);
Run Code Online (Sandbox Code Playgroud)

SQLite不支持该命令的ADD CONSTRAINT变体ALTER TABLE(sqlite.org:SQLite未实现的SQL功能).

因此,在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)
);
Run Code Online (Sandbox Code Playgroud)

不幸的是,您必须将现有数据保存到临时表,删除旧表,使用FK约束创建新表,然后从临时表中复制数据.(sqlite.org - FAQ:Q11)

  • 我认为重命名旧表,创建新表并将数据复制回来更容易.然后你可以删除旧表. (23认同)
  • 如果其他表引用了这个表,请不要重命名旧表,如第一个注释中所述!在这种情况下,您还必须重新创建所有这些表. (5认同)
  • 不应该是:FOREIGN KEY(parent_id)REFERENCES parent(id)True,Jonathan没有给出"父表"的名称.事实上,表应该命名为person,但是...... (3认同)
  • 这对我来说似乎是个大问题.通常在转储数据库时,首先导出CREATE TABLE命令.然后INSERT INTO命令,最后是ADD CONSTRAINT命令.如果数据中存在循环(外键值)依赖关系,则在强制执行外键时无法插入数据.但是如果你以后不能添加外键约束,那么你就会陷入困境.当然有延迟约束,但这非常笨拙. (3认同)

Jor*_*aes 51

如果更改表并添加使用约束的列,则可以添加约束.

首先,创建没有parent_id的表:

CREATE TABLE child( 
  id INTEGER PRIMARY KEY,  
  description TEXT);
Run Code Online (Sandbox Code Playgroud)

然后,改变表格:

ALTER TABLE child ADD COLUMN parent_id INTEGER REFERENCES parent(id);
Run Code Online (Sandbox Code Playgroud)

  • 习惯了这一顺序很不错,但这并不能回答实际的问题:*我想将约束添加到现有约束中。 (2认同)
  • @wolf - Necro我知道,但是...添加新列,将值从旧列复制到新列,删除旧列。 (2认同)

sit*_*uee 8

请查看https://www.sqlite.org/lang_altertable.html#otheralter

SQLite直接支持的唯一模式更改命令是上面显示的"重命名表"和"添加列"命令.但是,应用程序可以使用简单的操作序列对表的格式进行其他任意更改.对某些表X的模式设计进行任意更改的步骤如下:

  1. 如果启用了外键约束,请使用PRAGMA foreign_keys = OFF禁用它们.
  2. 开始交易.
  3. 记住与表X关联的所有索引和触发器的格式.下面的步骤8中将需要此信息.一种方法是运行如下查询:SELECT类型,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约束,或更改列的数据类型等.


Ang*_*der 7

正如@Daniel Vassallo所说,你做不到。您必须使用的代码是这样的:

给定表格:

CREATE TABLE child( 
id INTEGER PRIMARY KEY, 
parent_id INTEGER, 
description TEXT);
Run Code Online (Sandbox Code Playgroud)

我假设您要添加以下外键:

FOREIGN KEY (parent_id) REFERENCES parent(id);
Run Code Online (Sandbox Code Playgroud)

因此,我将基于该表创建一个临时表,然后创建一个新表作为第一个表,但使用外键,最后将临时表的数据添加到其中:

CREATE TEMPORARY TABLE temp AS
SELECT 
    id,
    parent_id,
    description
FROM child;

DROP TABLE child;

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

INSERT INTO child
 (  id,
    parent_id,
    description)
SELECT
    id,
    parent_id,
    description
FROM temp;
Run Code Online (Sandbox Code Playgroud)


mwa*_*wag 5

是的,您可以,无需添加新列。您必须小心正确地执行此操作以避免损坏数据库,因此您应该在尝试此操作之前完全备份您的数据库。

对于您的具体示例:

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
Run Code Online (Sandbox Code Playgroud)

或更一般地说:

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;
Run Code Online (Sandbox Code Playgroud)

无论哪种方式,您都可能希望在进行任何更改之前先查看 SQL 定义是什么:

select sql from SQLITE_MASTER where name = 'child' and type = 'table';
Run Code Online (Sandbox Code Playgroud)

如果您使用 replace() 方法,您可能会发现在执行之前先通过运行以下命令来测试您的 replace() 命令会很有帮助:

select replace(sql, ...) from SQLITE_MASTER where name = 'child' and type = 'table';
Run Code Online (Sandbox Code Playgroud)

  • 这对我来说是更简单的解决方案,感谢分享!:) (2认同)

Jam*_* EK 5

你可以试试这个:

ALTER TABLE [Child] ADD COLUMN column_name INTEGER REFERENCES parent_table_name(column_id);
Run Code Online (Sandbox Code Playgroud)