通过将SQLite表一分为二并使用外键链接来重构它
我正在处理一个SQLite数据库。数据库已经被填充,但我想重构它。以下是我需要做的一个示例: 我目前有一张桌子:通过将SQLite表一分为二并使用外键链接来重构它,sqlite,foreign-keys,temp-tables,Sqlite,Foreign Keys,Temp Tables,我正在处理一个SQLite数据库。数据库已经被填充,但我想重构它。以下是我需要做的一个示例: 我目前有一张桌子: CREATE TABLE Cars (ID INTEGER PRIMARY KEY, Name VARCHAR(32), TopSpeed FLOAT, EngineCap FLOAT); 我想将其分为两个表: CREATE TAB
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT,
EngineCap FLOAT);
我想将其分为两个表:
CREATE TABLE Vehicles (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT);
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
VehicleID INTEGER CONSTRAINT FK_Cars REFERENCES [Vehicles](ID),
EngineCap FLOAT);
CREATE TABLE [Customer2] (
[name] TEXT,
[addr] INTEGER);
CREATE TABLE [Address] (
[rowid] INTEGER NOT NULL,
[street] TEXT,
[city] TEXT,
PRIMARY KEY ([rowid])
);
我已经想出了用Cars
表内容创建一个临时表的办法,我可以用Cars
表的内容填充Vehicles
表:
CREATE TEMPORARY TABLE Cars_temp AS SELECT * FROM Cars;
INSERT INTO Vehicles (Name, TopSpeed)
SELECT Name, TopSpeed FROM Cars_temp;
但是我仍然在寻找一种方法来检查相同的选择,同时将EngineCap
字段放入新的Cars
表中,并以某种方式从Vehicles
表中提取相应的ID值,放入Cars
表中的VehicleID
外键字段中。
我对变通方法或其他方法持开放态度
谢谢。创建一个表
新车
和一个而不是INSERT
触发器,它将向两个表车辆
和车辆
插入数据。插入到车辆
时,您可以使用该功能参考车辆
表中插入的行
这可以是临时解决方案,也可以将其保留在数据库中以供进一步修改。不带触发器的简单解决方案:
- 创建车辆临时表,包括车辆ID
- 创建新的CARS表,但不包含不需要的车辆列
- 使用从车辆临时获取的车辆ID更新车辆(由车辆ID标识)
- 创建没有车辆ID的最终车辆表
CREATE TABLE [Customer] (
[name] TEXT,
[street] TEXT,
[city] TEXT);
现在,您要将街道
和城市
移动到一个单独的表地址
,因此您将得到两个表:
CREATE TABLE Vehicles (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT);
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
VehicleID INTEGER CONSTRAINT FK_Cars REFERENCES [Vehicles](ID),
EngineCap FLOAT);
CREATE TABLE [Customer2] (
[name] TEXT,
[addr] INTEGER);
CREATE TABLE [Address] (
[rowid] INTEGER NOT NULL,
[street] TEXT,
[city] TEXT,
PRIMARY KEY ([rowid])
);
(在本例中,我在同一个数据库中进行转换。您可能会使用两个数据库,通过SQL ATTACH命令将一个数据库转换为另一个数据库。)
现在我们创建一个视图(使用新表模拟原始表)和触发器:
CREATE VIEW Customer1 (name, street, city) AS
SELECT C.name, A.street, A.city FROM Customer2 AS C
JOIN Address as A ON (C.addr == A.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Customer1 FOR EACH ROW BEGIN
INSERT INTO Address (street, city) SELECT NEW.street, NEW.city;
INSERT INTO Customer2 (addr, name) SELECT last_insert_rowid(), NEW.name;
END;
现在,您可以复制表行:
INSERT INTO Customer1 (name, street, city) SELECT name, street, city FROM Customer;
上面是一个简化的情况,您只需将一些数据移动到一个新表中 一个更复杂(更一般)的情况是你想
INSERT
的最后一个\u INSERT\u rowid()的结果存储到临时表中-- Original table
CREATE TABLE [Song] (
[title] TEXT,
[album] TEXT,
[artist] TEXT
);
-- Refactored tables
CREATE TABLE [Song2] (
[title] TEXT,
[album_rowid] INTEGER,
[artist_rowid] INTEGER
);
CREATE TABLE [Album] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[title] TEXT UNIQUE
);
CREATE TABLE [Artist] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[name] TEXT UNIQUE
);
-- Fill with sample data
INSERT INTO Song VALUES ("Hunting Girl", "Songs From The Wood", "Jethro Tull");
INSERT INTO Song VALUES ("Acres Wild", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Broadford Bazar", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Statue of Liberty", "White Music", "XTC");
INSERT INTO Song VALUES ("Standing In For Joe", "Wasp Star", "XTC");
INSERT INTO Song VALUES ("Velvet Green", "Songs From The Wood", "Jethro Tull");
-- Conversion starts here
CREATE TEMP TABLE [TempRowIDs] (
[album_id] INTEGER,
[artist_id] INTEGER
);
CREATE VIEW Song1 (title, album, artist) AS
SELECT Song2.title, Album.title, Artist.name
FROM Song2
JOIN Album ON (Song2.album_rowid == Album.rowid)
JOIN Artist ON (Song2.artist_rowid == Artist.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Song1 FOR EACH ROW BEGIN
INSERT OR IGNORE INTO Album (title) SELECT NEW.album;
UPDATE TempRowIDs SET album_id = (SELECT COALESCE (
(SELECT rowid FROM Album WHERE changes()==0 AND title==NEW.album), last_insert_rowid()
) ) WHERE rowid==1;
INSERT OR IGNORE INTO Artist (name) SELECT NEW.artist;
UPDATE TempRowIDs SET artist_id = (SELECT COALESCE (
(SELECT rowid FROM Artist WHERE changes()==0 AND name==NEW.artist), last_insert_rowid()
) ) WHERE rowid==1;
INSERT INTO Song2 (title, album_rowid, artist_rowid) SELECT
NEW.title, (SELECT album_id FROM TempRowIDs), (SELECT artist_id FROM TempRowIDs);
END;
INSERT INTO TempRowIDs DEFAULT VALUES;
INSERT INTO Song1 (title, album, artist) SELECT title, album, artist FROM Song;
DROP TRIGGER TempTrig;
DROP TABLE TempRowIDs;
-- Conversion ends here
-- Print results
SELECT * FROM Song;
SELECT * FROM Song1;
-- Check if original and copy are identical (https://stackoverflow.com/a/13865679/43615)
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT * FROM Song UNION SELECT * FROM Song1)) == (SELECT COUNT() FROM Song) THEN 'Success' ELSE 'Failure' END;
请注意,此示例有一个潜在问题:如果外部表上的约束更复杂,则需要相应地更新现有条目的
SELECT rowid FROM
search。理想情况下,SQLite应该以某种方式提供一种确定冲突rowid的方法,但不幸的是()。只想提一下——尽管名称相似,但这个问题并没有得到回答,您可以省略VARCHAR(32),因为SQLite将忽略它。我会用文本来代替。我最终做了一些非常像这样的事情。对于尝试此操作的其他人,只有一个警告:如果您在DELETE CASCADE中将外键设置为,则在删除车辆时,您将丢失CARS表的内容。所以在移除汽车识别栏时要小心。好的。。。如果SQlite有一个完整的ALTERTABLE语句,当然会更容易…没错,但是为了方便使用这种轻量级的基于单个文件的数据库解决方案,我愿意为ALTERTABLE语句等待几年!