【问题标题】:How do I add a foreign key to an existing SQLite table?如何向现有 SQLite 表添加外键?
【发布时间】:2010-12-25 11:36:20
【问题描述】:

我有下表:

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

如何在parent_id 上添加外键约束?假设外键已启用。

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

【问题讨论】:

  • SQLite ALTER 命令只支持“重命名表”和“添加列”。但是,我们可以使用简单的操作序列对表格的格式进行其他任意更改。检查my answer

标签: sql sqlite foreign-keys ddl


【解决方案1】:

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

给定表格:

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

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

FOREIGN KEY (parent_id) REFERENCES parent(id);

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

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;

【讨论】:

  • 不知道这种方便的速记语法来复制表 (CREATE AS)。
【解决方案2】:

如果其他人需要有关 SQLiteStudio 的信息,您可以通过它的 GUI 轻松完成。

双击列并双击外键行,然后勾选外键并单击配置。您可以添加参考列,然后在每个窗口中单击“确定”。

最后点击绿色勾号提交结构更改。

请注意,这些步骤创建的 SQL 脚本会删除并重新创建表!

从数据库中备份您的数据。

【讨论】:

    【解决方案3】:

    为现有 SQLLite 表创建外键:

    对于 SQL LITE,没有直接的方法可以做到这一点。运行以下查询以使用外键重新创建 STUDENTS 表。 在创建初始 STUDENTS 表并向表中插入数据后运行查询。

    CREATE TABLE    STUDENTS    (       
        STUDENT_ID  INT NOT NULL,   
        FIRST_NAME  VARCHAR(50) NOT NULL,   
        LAST_NAME   VARCHAR(50) NOT NULL,   
        CITY    VARCHAR(50) DEFAULT NULL,   
        BADGE_NO    INT DEFAULT NULL
        PRIMARY KEY(STUDENT_ID) 
    );
    

    将数据插入 STUDENTS 表。

    然后添加 FOREIGN KEY :将 BADGE_NO 作为同一个 STUDENTS 表的外键

    BEGIN;
    CREATE TABLE STUDENTS_new (
        STUDENT_ID  INT NOT NULL,   
        FIRST_NAME  VARCHAR(50) NOT NULL,   
        LAST_NAME   VARCHAR(50) NOT NULL,   
        CITY    VARCHAR(50) DEFAULT NULL,   
        BADGE_NO    INT DEFAULT NULL,
        PRIMARY KEY(STUDENT_ID) ,
        FOREIGN KEY(BADGE_NO) REFERENCES STUDENTS(STUDENT_ID)   
    );
    INSERT INTO STUDENTS_new SELECT * FROM STUDENTS;
    DROP TABLE STUDENTS;
    ALTER TABLE STUDENTS_new RENAME TO STUDENTS;
    COMMIT;
    

    我们也可以从任何其他表中添加外键。

    【讨论】:

      【解决方案4】:

      如果您使用 DB Browser for sqlite ,那么您可以轻松修改表格。您可以在现有表中添加外键而无需编写查询。

      • 在 Db 浏览器中打开您的数据库,
      • 只需右键点击表格,点击修改,
      • 在那里滚动到外键列,
      • 双击要更改的字段,
      • 然后选择表和它的字段并单击确定。

      就是这样。您已成功在现有表中添加外键。

      【讨论】:

      • 我可以使用 DB Browser 版本 3.10.1 执行此操作,但不会保存信息。如果进行更改,单击确定,然后再次单击“修改”,更改已消失。这适用于哪个版本?
      【解决方案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
      

      或更笼统地说:

      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';
      

      【讨论】:

        【解决方案6】:

        基本上你不能,但你可以绕过这种情况。

        将外键约束添加到现有表的正确方法是以下命令。

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

        然后将 parent_Id 数据复制到 newCol 中,然后删除 Parent_Id 列。 因此,不需要临时表。

        【讨论】:

        • 您似乎没有仔细阅读问题。问题是只添加外部约束,而不是添加带有约束的列。
        • 不。它没有回答所提出的问题。
        【解决方案7】:

        你可以试试这个:

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

        【讨论】:

          【解决方案8】:

          首先在子表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
          

          【讨论】:

          • 这在 SQLite 中无效。这也是 MS SQL 语法。
          【解决方案9】:

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

          SQLite 直接支持的唯一模式更改命令是 上面显示的“重命名表”和“添加列”命令。然而, 应用程序可以对表格的格式进行其他任意更改 使用简单的操作序列。使任意的步骤 部分表X的schema设计变化如下:

          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 约束, 或更改列的数据类型,例如。

          【讨论】:

            【解决方案10】:

            如果您使用的是 Firefox 插件 sqlite-manager,您可以执行以下操作:

            可以像这样修改它,而不是再次删除和创建表。

            在列文本框中,右键单击列出的最后一个列名称以调出上下文菜单并选择编辑列。 请注意,如果 TABLE 定义中的最后一列是 PRIMARY KEY,则必须首先添加一个新列,然后编辑新列的列类型以添加​​ FOREIGN KEY 定义。 在列类型框中,附加一个逗号和

            FOREIGN KEY (parent_id) REFERENCES parent(id)
            

            数据类型后的定义。 单击“更改”按钮,然后单击“危险操作”对话框中的“是”按钮。

            参考: Sqlite Manager

            【讨论】:

              【解决方案11】:

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

              首先,创建没有 parent_id 的表:

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

              然后,修改表:

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

              【讨论】:

              • 习惯这个序列很好,但这并不能回答实际问题:我想将约束添加到现有的。
              • @wolf - Necro 我知道,但是... 添加新列,将值从旧列复制到新列,删除旧列。
              【解决方案12】:

              你不能。

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

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

              SQLite 不支持ALTER TABLE 命令 (sqlite.org: SQL Features That SQLite Does Not Implement) 的 ADD CONSTRAINT 变体。

              因此,在 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)

              【讨论】:

              • 我认为重命名旧表,创建新表并将数据复制回更容易。然后您可以删除旧表。
              • 是的,这更容易。我只是在引用 sqlite 常见问题解答:sqlite.org/faq.html#q11。事实上,RENAME TO 是目前在 sqlite 3 中支持的少数ALTER TABLE 变体之一。
              • 不应该是:FOREIGN KEY (parent_id) REFERENCES parent(id) 没错,Jonathan 没有给出“父表”的名称。其实表应该命名为person,但是……
              • 这对我来说似乎是个大问题。通常在转储数据库时,首先导出 CREATE TABLE 命令。然后是 INSERT INTO 命令,最后是 ADD CONSTRAINT 命令。如果您的数据中存在循环(外键值)依赖性,则在强制执行外键时您无法插入数据。但是如果你以后不能添加外键约束,那么你就卡住了。当然也有延迟约束,但这很笨拙。
              • 如果其他表引用了该表,请不要像第一条评论中所说的那样重命名旧表!在这种情况下,您也必须重新创建所有这些表。
              猜你喜欢
              • 1970-01-01
              • 2023-04-03
              • 2013-07-13
              • 1970-01-01
              • 2017-09-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多