Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sqlite/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
通过将SQLite表一分为二并使用外键链接来重构它_Sqlite_Foreign Keys_Temp Tables - Fatal编程技术网

通过将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

我正在处理一个SQLite数据库。数据库已经被填充,但我想重构它。以下是我需要做的一个示例:

我目前有一张桌子:

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的最终车辆表

因为@mateusza没有提供示例,所以我做了一个:

假设您有这个表:

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;

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

一个更复杂(更一般)的情况是你想

  • 将原始表的列分隔为多个外部表,然后
  • 在外部表中有唯一的条目(这通常是重构表的原因)
  • 这增加了一些额外的挑战:

  • 在将多个表的rowid插入到具有引用rowid的表之前,您将先插入多个表。这需要将每个
    INSERT
    的最后一个\u INSERT\u rowid()的结果存储到临时表中
  • 如果该值已存在于外部表中,则必须存储其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
    search。理想情况下,SQLite应该以某种方式提供一种确定冲突rowid的方法,但不幸的是()。

    只想提一下——尽管名称相似,但这个问题并没有得到回答,您可以省略VARCHAR(32),因为SQLite将忽略它。我会用文本来代替。我最终做了一些非常像这样的事情。对于尝试此操作的其他人,只有一个警告:如果您在DELETE CASCADE中将外键设置为
    ,则在删除车辆时,您将丢失CARS表的内容。所以在移除汽车识别栏时要小心。好的。。。如果SQlite有一个完整的ALTERTABLE语句,当然会更容易…没错,但是为了方便使用这种轻量级的基于单个文件的数据库解决方案,我愿意为ALTERTABLE语句等待几年!