@mateuszaは例を提供しなかったので、私は1つ作成しました。
次のテーブルがあるとします。
CREATE TABLE [Customer] (
[name] TEXT,
[street] TEXT,
[city] TEXT);
ここで、別のテーブルに移動street
したいので、2つのテーブルになります。city
Address
CREATE TABLE [Customer2] (
[name] TEXT,
[addr] INTEGER);
CREATE TABLE [Address] (
[rowid] INTEGER NOT NULL,
[street] TEXT,
[city] TEXT,
PRIMARY KEY ([rowid])
);
(この例では、同じデータベースで変換を行っています。SQLATTACHコマンドを使用して、おそらく2つのDBを使用し、一方を他方に変換します。)
次に、ビュー(新しいテーブルを使用して元のテーブルを模倣します)とトリガーを作成します。
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つの新しいテーブルに移動する単純化されたケースです。
より複雑な(そしてより一般的な)ケースは、あなたがしたい場合です...
- 元のテーブルの列をいくつかの外部テーブルに分割し、
- 外部テーブルに一意のエントリがあります(これが通常、テーブルをリファクタリングする理由です)。
これにより、いくつかの追加の課題が追加されます。
- 参照するROWIDを使用してそれらのROWIDをテーブルに挿入する前に、複数のテーブルに挿入することになります。これには、それぞれ
INSERT
のlast_insert_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;
この例には1つの潜在的な問題があることに注意してください。外部テーブルの制約がより複雑な場合は、SELECT rowid FROM
それに応じて既存のエントリの検索を更新する必要があります。理想的には、SQLiteは競合するROWIDを何らかの方法で特定する方法を提供する必要がありますが、残念ながらそうではありません(この関連する質問を参照してください)。