2

I'm working on a SQLite Database. The database is already filled, but I want to refactor it. Here is a sample of what I need to do:

I currently have one table:

CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
                   Name VARCHAR(32),
                   TopSpeed FLOAT,                   
                   EngineCap FLOAT);

I want to split this into two tables:

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

I have figured out to create a temporary table with the Cars table contents, and I can fill up the Vehicles table with the contents of the Cars table:

CREATE TEMPORARY TABLE Cars_temp AS SELECT * FROM Cars;

INSERT INTO Vehicles (Name, TopSpeed)
SELECT Name, TopSpeed FROM Cars_temp;

But I am still looking for a way to go over that same selection, while putting the EngineCap field into the new Cars table and somehow extracting the corresponding ID value from the Vehicles table to put into the VehicleID foreign key field on the Cars table.

I'm open for workaround or alternative approaches.

Thanks.

4

3 回答 3

2

由于@mateusza 没有提供示例,因此我做了一个示例:

假设你有这张表:

CREATE TABLE [Customer] (
  [name] TEXT,
  [street] TEXT,
  [city] TEXT);

现在你想移动streetcity进入一个单独的表Address,所以你最终会得到两个表:

CREATE TABLE [Customer2] (
  [name] TEXT,
  [addr] INTEGER);

CREATE TABLE [Address] (
  [rowid] INTEGER NOT NULL,
  [street] TEXT,
  [city] TEXT,
  PRIMARY KEY ([rowid])
);

(对于这个例子,我在同一个数据库中进行转换。您可能会使用两个 DB,将一个转换为另一个,使用 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;

以上是一个简化的情况,您只需将一些数据移动到一个新表中。

一个更复杂(和更一般)的情况是您想要...

  1. 将原始表的列分成几个外部表,并且
  2. 在外部表中有唯一的条目(这通常是您重构表的原因)。

这增加了一些额外的挑战:

  1. 您最终将插入多个表,然后才能将它们的 rowid 插入到具有引用 rowid 的表中。这需要将每个INSERT's last_insert_rowid() 的结果存储到临时表中。
  2. 如果外部表中已经存在该值,则必须存储其 rowid 而不是(未执行的)插入操作中的 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需要相应地更新对现有条目的搜索。理想情况下,SQLite 应该提供一种方法来以某种方式确定冲突的 rowid,但不幸的是,它没有(参见这个相关问题)。

于 2018-11-24T18:32:12.990 回答
1

没有触发器的简单解决方案:

  • 创建包含 CAR_ID 的 VEHICLES_TEMP 表
  • 创建没有您不想要的 VEHICLES 列的新 CARS 表
  • 使用取自 VEHICLES_TEMP 的 VEHICLE_ID 更新 CARS(由 CAR_ID 标识)
  • 创建没有 CAR_ID 的最终 VEHICLES 表
于 2012-06-01T15:49:31.437 回答
0

创建一个表New_Cars和一个INSTEAD OF INSERT触发器,它将向表VehiclesCars. 插入时Cars,可以使用last_insert_rowid()函数来引用Vehicles表中插入的行。

这可以是临时解决方案,或者您可以将其保留在数据库中以进行进一步修改。

于 2012-06-01T12:39:32.447 回答