重新创建父表后如何在 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
也会重命名busses
和cars
附带的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_types
和 vehicle_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 中,如何在更改表后重新验证(“类型检查”)函数和过程?
从数据库中删除所有表后,如何将 prisma 重新部署到数据库