重新创建父表后如何在 SQLite 中重新设置表 FOREIGN KEY

Posted

技术标签:

【中文标题】重新创建父表后如何在 SQLite 中重新设置表 FOREIGN KEY【英文标题】:How to re-parent a table FOREIGN KEY in SQLite after recreating the parent 【发布时间】:2021-07-04 10:50:19 【问题描述】:

考虑我的汽车租赁公司,有两种不同的租赁车辆:

CREATE TABLE busses (
    id INTEGER PRIMARY KEY NOT NULL,
    places INTEGER NOT NULL
);
CREATE TABLE cars (
    id INTEGER PRIMARY KEY NOT NULL,
    places INTEGER NOT NULL
);

CREATE TABLE vehicles (
    id INTEGER PRIMARY KEY NOT NULL,
    bus_id INTEGER,
    car_id INTEGER,

    FOREIGN KEY(bus_id) REFERENCES busses(id),
    FOREIGN KEY(car_id) REFERENCES cars(id),

    CHECK (NOT(bus_id IS NULL AND car_id IS NULL))
);

CREATE TABLE rentals(
    id INTEGER PRIMARY KEY NOT NULL,
    vehicle_id INTEGER NOT NULL,
    customer_id INTEGER NOT NULL,

    FOREIGN KEY(vehicle_id) REFERENCES vehicles(id)
);

我的汽车公司在过去几年做得很好。我们已经订购了几辆卡车,我们也计划将它们出租。我们想要得到这个架构:

CREATE TABLE busses (..);
CREATE TABLE cars (..);
CREATE TABLE trucks (
    id INTEGER PRIMARY KEY NOT NULL,
    tar_weight INTEGER NOT NULL,
    max_net_weight INTEGER NOT NULL
);

CREATE TABLE vehicles (
    id INTEGER PRIMARY KEY NOT NULL,
    bus_id INTEGER,
    car_id INTEGER,
    truck_id INTEGER,

    FOREIGN KEY(bus_id) REFERENCES busses(id),
    FOREIGN KEY(car_id) REFERENCES cars(id),
    FOREIGN KEY(truck_id) REFERENCES trucks(id),

    CHECK (NOT(bus_id IS NULL AND car_id IS NULL AND truck_id IS NULL))
);

CREATE TABLE rentals(...);

在普通 SQL 中,您可以使用 ALTER TABLE 在这两个模式之间移动。但是,SQLite 似乎不允许重写 CHECK 约束(例如 How do I DROP a constraint from a sqlite (3.6.21) table?),也不允许删除它并重新添加它。

解决办法是

    重命名表;那么 用新的CHECK约束重新创建新表;那么 重新插入旧表中的数据。

类似

ALTER TABLE vehicles RENAME TO vehicles_old;
CREATE TABLE vehicles (...);
INSERT INTO vehicles (id, bus_id, car_id) SELECT * FROM vehicles_old;
DROP TABLE vehicles_old;

但是,将vehicles 表重命名为vehicles_old 也会重命名bussescars 附带的FOREIGN KEY 约束:重命名后它们指向vehicles_old 表,如下所示:

sqlite> select * from sqlite_schema;
table|busses|busses|2|CREATE TABLE busses (
    id INTEGER PRIMARY KEY NOT NULL,
    places INTEGER NOT NULL
)
table|cars|cars|3|CREATE TABLE cars (
    id INTEGER PRIMARY KEY NOT NULL,
    places INTEGER NOT NULL
)
table|rentals|rentals|5|CREATE TABLE rentals(
    id INTEGER PRIMARY KEY NOT NULL,
    vehicle_id INTEGER NOT NULL,
    customer_id INTEGER NOT NULL,

    FOREIGN KEY(vehicle_id) REFERENCES "vehicles_old"(id)
)
table|trucks|trucks|6|CREATE TABLE trucks (
    id INTEGER PRIMARY KEY NOT NULL,
    tar_weight INTEGER NOT NULL,
    max_net_weight INTEGER NOT NULL
)
table|vehicles|vehicles|7|CREATE TABLE vehicles (
    id INTEGER PRIMARY KEY NOT NULL,
    bus_id INTEGER,
    car_id INTEGER,
    truck_id INTEGER,

    FOREIGN KEY(bus_id) REFERENCES busses(id),
    FOREIGN KEY(car_id) REFERENCES cars(id),
    FOREIGN KEY(truck_id) REFERENCES trucks(id),

    CHECK (NOT(bus_id IS NULL AND car_id IS NULL AND truck_id IS NULL))
)

我现在的问题是:如何重新设置 rentals 表的父级,使其指向 vehicles 表而不是不存在的 vehicles_old 表?

注意,这可能被认为是一个 x-y 问题,其中“y”是“rentals 的重新父母化?”,“x”是“我如何更新 vehicles 的检查约束?”。我认为后者是不可能的。

这不应该被认为是一个 x-y-z 问题,我应该介绍一个 vehicle_typesvehicle_attributes 表; in my real case 我们说的是 ISA 关系。

【问题讨论】:

【参考方案1】:

当然,在发布 20 分钟后,您会意识到过去一个小时里答案一直盯着您。

来自ALTER TABLE,“2. ALTER TABLE RENAME”部分:

在版本 3.26.0 (2018-12-01) 之前,仅当 PRAGMA foreign_keys=ON 或换句话说,如果强制执行外键约束时,才会编辑对重命名表的 FOREIGN KEY 引用。使用 PRAGMA foreign_keys=OFF,当外键引用的表(“父表”)被重命名时,FOREIGN KEY 约束不会改变。从 3.26.0 版开始,重命名表时始终会转换 FOREIGN KEY 约束,除非使用了 PRAGMA legacy_alter_table=ON 设置。

虽然我尝试了两种 PRAGMA 的多种组合,但我没有意识到 PRAGMA foreign_key=OFF; is a no-op during a transaction。当然,我在启用了 FK 检查的事务中运行我的迁移。如果我们不运行古老的 SQLite 3.15.2,这将不是问题,因为那里的 PRAGMA legacy_alter_table=ON; 就足够了。

【讨论】:

不确定我是否应该在这里接受我自己的答案;虽然它在技术上解决了这个问题,但它有点丑陋(legacy_alter_table=ON 不鼓励用于新应用程序)。很高兴找到一个更好的答案。

以上是关于重新创建父表后如何在 SQLite 中重新设置表 FOREIGN KEY的主要内容,如果未能解决你的问题,请参考以下文章

在 Postgres 中,如何在更改表后重新验证(“类型检查”)函数和过程?

UILabel 的背景颜色在重新加载表后变得清晰

更新子表后数据库触发器更新父表上的行

从数据库中删除所有表后,如何将 prisma 重新部署到数据库

python测试开发django-72.删除表后如何重新生成表

CakePHP - 如何在更改语言和重新加载路由表后翻译当前 URL