Sqlite INSERTION optimization - sqlite

Good day SO,
I'm working on a program in PowerShell to manipulate an SQLite DB I created. I've never written a serious applications to utilize a DB so right now I'm super interested in optimizing my querys, so I'm really interested in feed back. My primary issue is I have a lot of data that I want to include in a separate table that may or not exist already. All my research really seemed to lead to perform an INSERT and let the UNIQUE constraints sort it out, than do a select on the new record which seemed like two table scans and inefficient. So my solution was Create a temp table, insert into the temp table FROM the table with data I want and perform an INSERT if the data was not in the temporary table. I'm a few drinks in tonight and haven't tested the code so please don't critique small typos, I just want to know if my methodology is out to lunch, and if so please provide better direction.
My table is as shown:
CREATE TABLE Processes (
pk INTEGER PRIMARY KEY AUTOINCREMENT
UNIQUE,
hostname INTEGER NOT NULL,
artifacttype INTEGER REFERENCES ArtifactType (pk),
processname INTEGER REFERENCES ProcessesName (pk),
filelocation INTEGER NOT NULL
REFERENCES files (pk),
pid INTEGER,
ppid INTEGER,
starttime INTEGER,
stoptime INTEGER,
token STRING,
logonid INTEGER,
exitstatus INTEGER,
threadcount INTEGER,
commandline INTEGER REFERENCES ProcessesCommandline (pk),
user INTEGER REFERENCES users (pk),
PeakVirtualSize INTEGER,
VirtualSize INTEGER,
PeakWorkingSetSize INTEGER,
suspicious BOOLEAN,
malicious BOOLEAN
);
Transaction:
#"
CREATE TEMPORARY TABLE IF NOT EXISTS Results(pk INTEGER, data TEXT);
INSERT INTO Results(pk, data) VALUES ((SELECT pk, name FROM ProcessesName WHERE name = #processname));
INSERT INTO ProcessesName(name) VALUES (SELECT #processname WHERE NOT EXISTS (SELECT pk FROM Results WHERE data = #processname));
INSERT INTO Results (pk, data) VALUES ((SELECT last_insert_row_id, #processname WHERE NOT EXISTS (SELECT pk FROM Results WHERE data =#processname)));
INSERT INTO Results(pk, data) VALUES ((SELECT pk, file FROM Files WHERE file = #filelocation));
INSERT INTO Files(file) VALUES (SELECT #filelocation WHERE NOT EXISTS (SELECT pk FROM Results WHERE data = #filelocation));
INSERT INTO Results (pk, data) VALUES ((SELECT last_insert_row_id, #filelocation WHERE NOT EXISTS (SELECT pk FROM Results WHERE data =#filelocation)));
INSERT INTO Results(pk, data) VALUES ((SELECT pk, commandline FROM ProcessesCommandline WHERE commandline = #commandline));
INSERT INTO ProcesseCommandline(commandline) VALUES (SELECT #commandline WHERE NOT EXISTS (SELECT pk FROM Results WHERE data = #commandline));
INSERT INTO Results (pk, data) VALUES ((SELECT last_insert_row_id, #filelocation WHERE NOT EXISTS (SELECT pk FROM Results WHERE data =#commandline)));
INSERT INTO Results(pk, data) VALUES ((SELECT pk, SID FROM Users WHERE SID = #SID));
INSERT INTO Users(SID) VALUES (SELECT #SID WHERE NOT EXISTS (SELECT pk FROM Results WHERE data = #filelocation));
INSERT INTO Results (pk, data) VALUES ((SELECT last_insert_row_id, #SID WHERE NOT EXISTS (SELECT pk FROM Results WHERE data =#SID)));
INSERT INTO processes(hostname, artifacttype, processname, filelocation, pid, ppid, starttime, threadcount, commandline, user, PeakVirtualSize, VirtualSize, PeakWorkingSetSize)
VALUES (#hostname, #artifacttype, (SELECT pk FROM Results WHERE data = #processname), (SELECT pk FROM Results WHERE data #filelocation), #pid, #ppid, #starttime, #threadcount, (SELECT pk FROM Results where data = #commandline), SELECT pk FROM Results WHERE data = #SID, #PeakVirtualSize, #VirtualSize, #PeakWorkingSetSize);
DROP TABLE Results;
"#
*there are a few foreign keys where the data is being tracked application side so no complex queries are required.
So my core question is, is there a more efficient way to do this?
Thanks guys!

Related

MariaDB Check value of an attribute w/ another table attribute

I want to assure at inserting a manager that department manager start date [DEPARTMENT.mgr_start_date] is coming after his birthdate [EMPLOYEE.bdate],
how can I do that?
CREATE TABLE IF NOT EXISTS EMPLOYEE
(
ssn INT(16) unsigned NOT NULL,
fname VARCHAR(16),
lname VARCHAR(16),
bdate DATE,
address VARCHAR(32),
gender enum('m','f'),
salary decimal(16,2),
Dno VARCHAR(8),
PRIMARY KEY (ssn)
);
CREATE TABLE IF NOT EXISTS DEPARTMENT
(
mgr_ssn INT(16) unsigned,
Dname VARCHAR(32),
mgr_start_date DATE,
Dnumber VARCHAR(8),
PRIMARY KEY (Dnumber),
FOREIGN KEY (mgr_ssn) REFERENCES EMPLOYEE(ssn)
);
You would have to do this with a trigger.
CHECK constraints can reference only columns in the table where the constraint is defined.
The full SQL standard includes a type of constraint called an ASSERTION, which allows multi-table constraints, but MariaDB does not implement this feature of SQL (very few brands of SQL databases do implement it).
CREATE TRIGGER t BEFORE INSERT ON DEPARTMENT
FOR EACH ROW BEGIN
IF NEW.mgr_start_date < (SELECT bdate FROM EMPLOYEE WHERE ssn = NEW.mgr_ssn) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'manager is way too young';
END IF;
END
Test:
insert into EMPLOYEE set ssn=123, bdate='2021-01-01';
insert into DEPARTMENT set mgr_ssn=123, dnumber='1', mgr_start_date='2010-01-01';
ERROR 1644 (45000): manager is way too young

How to get a list of tables that have one-on-one relationship to a given table in SQLite3?

Is there a way to get a list of tables that have one-on-one relationship to a given table in SQLite3?
For example, here table ab has a one-on-one relationship with both table abc and abd. Is there a query or queries to return abc and abd for the given table name ab?
-- By default foreign key is diabled in SQLite3
PRAGMA foreign_keys = ON;
CREATE TABLE a (
aid INTEGER PRIMARY KEY
);
CREATE TABLE b (
bid INTEGER PRIMARY KEY
);
CREATE TABLE ab (
aid INTEGER,
bid INTEGER,
PRIMARY KEY (aid, bid)
FOREIGN KEY (aid) REFERENCES a(aid)
FOREIGN KEY (bid) REFERENCES b(bid)
);
-- tables 'ab' and 'abc' have a one-on-one relationship
CREATE TABLE abc (
aid INTEGER,
bid INTEGER,
name TEXT NOT NULL,
PRIMARY KEY (aid, bid) FOREIGN KEY (aid, bid) REFERENCES ab(aid, bid)
);
-- tables 'ab' and 'abd' have a one-on-one relationship
CREATE TABLE abd (
aid INTEGER,
bid INTEGER,
value INTEGER CHECK( value > 0 ),
PRIMARY KEY (aid, bid) FOREIGN KEY (aid, bid) REFERENCES ab(aid, bid)
);
CREATE TABLE w (
id INTEGER PRIMARY KEY
);
The following tedious precedure may get me the list of tables I want:
Get primary keys for table ab:
SELECT l.name FROM pragma_table_info('ab') as l WHERE l.pk > 0;
get foreign keys for other tables (this case is for table abd):
SELECT * from pragma_foreign_key_list('abd');
Do parsing to get what the list of tables of one-on-one relationships.
However, there must exist a more elegant way, I hope.
For SQL Server, there are sys.foreign_keys and referenced_object_id avaible (see post). Maybe there is something similar to that in SQLite?
Edit: adding two more tables for test
-- tables 'ab' and 'abe' have a one-on-one relationship
CREATE TABLE abe (
aid INTEGER,
bid INTEGER,
value INTEGER CHECK( value < 0 ),
PRIMARY KEY (aid, bid) FOREIGN KEY (aid, bid) REFERENCES ab
);
-- tables 'ab' and 'abf' have a one-on-one relationship
CREATE TABLE abf (
aidQ INTEGER,
bidQ INTEGER,
value INTEGER,
PRIMARY KEY (aidQ, bidQ) FOREIGN KEY (aidQ, bidQ) REFERENCES ab(aid, bid)
);
Edit: verify FK for table abe
sqlite> PRAGMA foreign_keys;
1
sqlite> .schema abe
CREATE TABLE abe (
aid INTEGER,
bid INTEGER,
value INTEGER CHECK( value < 0 ),
PRIMARY KEY (aid, bid) FOREIGN KEY (aid, bid) REFERENCES ab
);
sqlite> DELETE FROM abe;
sqlite> INSERT INTO abe (aid, bid, value) VALUES (2, 1, -21);
sqlite> INSERT INTO abe (aid, bid, value) VALUES (-2, 1, -21);
Error: FOREIGN KEY constraint failed
sqlite> SELECT * FROM ab;
1|1
1|2
2|1
Alternative
Although not a single query solution the following only requires submission/execution of a series of queries and is therefore platform independent.
It revolves around using two tables:-
a working copy of sqlite_master
a working table to store the the output of SELECT pragma_foreign_key_list(?)
Both tables are created via a CREATE-SELECT, although neither has any rows copied, so the tables are empty.
A trigger is applied to the working copy of sqlite_master to insert into the table that stores the result of SELECT pragma_foreign_key_list(table_name_from_insert);
The relevant rows are copied from sqlite_master via a SELECT INSERT and thus the triggering populates the store table.
The following is the testing code :-
DROP TABLE IF EXISTS fklist;
DROP TABLE IF EXISTS master_copy;
DROP TRIGGER IF EXISTS load_fklist;
/* Working version of foreign_key_list to store ALL results of SELECT pragma_foreign_key_list invocation */
CREATE TABLE IF NOT EXISTS fklist AS SELECT '' AS child,*
FROM pragma_foreign_key_list((SELECT name FROM sqlite_master WHERE type = 'not a type' LIMIT 1));
/* Working version of sqlite master */
CREATE TABLE IF NOT EXISTS master_copy AS SELECT * FROM sqlite_master WHERE type = 'not a type';
/* Add an after insert trigger for master copy to add to fklist */
CREATE TRIGGER IF NOT EXISTS load_fklist
AFTER INSERT ON master_copy
BEGIN
INSERT INTO fklist SELECT new.name,* FROM pragma_foreign_key_list(new.name);
END
;
/* Populate master_copy from sqlite_master (relevant rows)
and thus build the fklist
*/
INSERT INTO master_copy SELECT *
FROM sqlite_master
WHERE type = 'table'
AND instr(sql,' REFERENCES ') > 0
;
SELECT * FROM fklist;
DROP TABLE IF EXISTS fklist;
DROP TABLE IF EXISTS master_copy;
DROP TRIGGER IF EXISTS load_fklist;
Using a similar test base as per the previous answer the above results in :-
Is there a way to get a list of tables that have one-on-one relationship to a given table in SQLite3?
Not with certainty as coding a Foreign Key constraint does not define a relationship (rather it supports a relationship), that is relationships can exists without a FK constraint.
A Foreign Key constraint defines:-
a) a rule that enforces referential integrity
b) optionally maintains/alters referential integrity when the referred to column is changed (ON DELETE and ON UPDATE )
As such looking at the Foreign Key List only tells you where/if a FK constraint has been coded.
Saying that the following will get the tables with the constraint and the referenced tables.
More elegant is a matter of opinion, so it's up to you :-
WITH cte_part(name,reqd,rest) AS (
SELECT name,'',substr(sql,instr(sql,' REFERENCES ') + 12)||' REFERENCES '
FROM sqlite_master
WHERE sql LIKE '% REFERENCES %(%'
UNION ALL
SELECT
name,
substr(rest,0,instr(rest,' REFERENCES ')),
substr(rest,instr(rest,' REFERENCES ') + 12)
FROM cte_part
WHERE length(rest) > 12
)
SELECT DISTINCT
CASE
WHEN length(reqd) < 1 THEN name
ELSE
CASE substr(reqd,1,1)
WHEN '''' THEN substr(replace(reqd,substr(reqd,1,1),''),1,instr(reqd,'(')-3)
WHEN '[' THEN substr(replace(replace(reqd,'[',''),']',''),1,instr(reqd,'(')-3)
WHEN '`' THEN substr(replace(reqd,substr(reqd,1,1),''),1,instr(reqd,'(')-3)
ELSE substr(reqd,1,instr(reqd,'(')-1)
END
END AS tablename
FROM cte_part
;
As an example of it's use/results :-
screenshot from Navicat
Here's an adaptation of the above that includes, where appropriate, the child table that references the parent :-
WITH cte_part(name,reqd,rest) AS (
SELECT name,'',substr(sql,instr(sql,' REFERENCES ') + 12)||' REFERENCES '
FROM sqlite_master
WHERE sql LIKE '% REFERENCES %(%'
UNION ALL
SELECT
name,
substr(rest,0,instr(rest,' REFERENCES ')),
substr(rest,instr(rest,' REFERENCES ') + 12)
FROM cte_part
WHERE length(rest) > 12
)
SELECT DISTINCT
CASE
WHEN length(reqd) < 1 THEN name
ELSE
CASE substr(reqd,1,1)
WHEN '''' THEN substr(replace(reqd,substr(reqd,1,1),''),1,instr(reqd,'(')-3)
WHEN '[' THEN substr(replace(replace(reqd,'[',''),']',''),1,instr(reqd,'(')-3)
WHEN '`' THEN substr(replace(reqd,substr(reqd,1,1),''),1,instr(reqd,'(')-3)
ELSE substr(reqd,1,instr(reqd,'(')-1)
END
END AS tablename,
CASE WHEN length(reqd) < 1 THEN '' ELSE name END AS referrer
FROM cte_part
;
Example of the Result :-
the artists table is referenced by albums as the SQL used to create the albums table is CREATE TABLE 'albums'([AlbumId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,[Title] TEXT NOT NULL ,[ArtistId] INTEGER NOT NULL , FOREIGN KEY ([ArtistId]) REFERENCES 'artists'([ArtistId]))
i.e. FOREIGN KEY ([ArtistId]) REFERENCES 'artists'([ArtistId]))
the employees table is self-referencing as per CREATE TABLE 'employees'(.... REFERENCES 'employees'([EmployeeId]))
Additional re comment:-
(I am still trying to understand your code...)
The code is based upon selecting rows from sqlite_master where the row is for a table (type = 'table'), as opposed to an index, trigger or view and where the sql column contains the word REFERENCES with a space before and after and there is a following left parenthesis.
The last condition used to weed out the likes of CREATE TABLE oops (`REFERENCES` TEXT, `x REFERENCES Y`);
For each selected row 3 columns are output:-
name which is the name of the table as extracted from the name column of sqlite_master,
reqd is initially an empty string (i.e. initial)
rest the rest of sql that follows the referred to table name with suffixed with REFERENCES.
The UNION ALL adds rows that are built upon what is newly added to the CTE, i.e. the three columns are extracted as per :-
name is the name
reqd is the sql from the rest column up until the first REFERENCES term (i.e. the table and referenced column(s))
rest is the sql from after the REFERENCES term
As with any recursion the end needs to be detected, this is when the entire sql statement has been reduced to being less than 12 (i.e the length of " REFERENCES ", the term used for splitting the sql statement).
This is what is termed as a RECURSIVE CTE
Finally the resultant CTE is then queried. If the reqd field is empty then the tablename column is the name column otherwise (i.e. the reqd column contains data(part of the sql)) the table name is extracted (part up to left parenthesis if not enclosed (`,' or [ with ])) or extracted from between the enclosure.
The following is what the final query results in if all the CTE columns are included (some data has been truncated):-
As can clearly be seen the extracted sql progressively reduces
The answer is intended as in-principle and has not been extensively tested to consider all scenarios, it may well need tailoring.

SQLite Multi-column Insert/Replace with Multiple Join

Sorry for the poor title. I have a query (below) that executes properly and creates an insertion just as I would desire. However, I want to make it smarter by only inserting when the exact combination of three columns. Essentially, the three column tuple is a primary key, but I'm working with the limitation of sqlite's single primary key.
Basic Context
I have 4 tables: Permissions, Roles, Users, Actions
Permissions connects Roles and Users to Actions. The Actions table has a list of available tasks that a User or a user with a Role can perform. So for example, if user_id = 1 can perform a list_folder action (action_id = 1), then the permissions table would have an entry: (id=1, action_id=1, user_id=1, role_id=NULL). Likewise, suppose an owner_role (role_id=1) might have permissions to perform a list_folder action (action_id=1), then the permissions entry might be: (id=2, action_id=1, user_id=NULL, role_id=1).
When I do an insert, I want to make sure that I do not already have that exact combination (e.g. action_id=1, user_id=NULL, role_id=1). And I'm not entirely sure how to write the sql so that I have this setup properly.
Here's my basic insert statement. I need to come up with an insert and a replace statement:
INSERT INTO permissions (
action_id
,role_id
)
SELECT DISTINCT
a.id as "action_id"
,r.id as "role_id"
FROM tmp_permissions tmp
LEFT OUTER JOIN actions a
ON tmp.action_name = a.name
LEFT OUTER JOIN roles r
ON tmp.roles_name = r.name
LEFT OUTER JOIN permissions p
ON p.role_id
Here are some creation sql statements for the tables:
CREATE TABLE permissions (
id INTEGER NOT NULL,
enabled INTEGER,
action_id INTEGER,
user_id INTEGER,
role_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES users (id),
FOREIGN KEY(action_id) REFERENCES actions (id),
FOREIGN KEY(role_id) REFERENCES roles (id)
);
CREATE TABLE actions (
id INTEGER NOT NULL,
enabled INTEGER,
name VARCHAR(50),
permission_ids INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(permission_ids) REFERENCES permissions (id)
);
CREATE TABLE roles (
id INTEGER NOT NULL,
enabled INTEGER,
name VARCHAR(50),
permission_ids INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(permission_ids) REFERENCES permissions (id)
);
CREATE TABLE users (
id INTEGER NOT NULL,
enabled INTEGER,
name VARCHAR(50),
permission_ids INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(permission_ids) REFERENCES permissions (id)
);
Here's a temp table I'm using to store the data in the table while I work with it:
CREATE TABLE tmp_permissions(
roles_name VARCHAR(50),
action_name VARCHAR(50)
);
Here's some data:
#role|action
admin|setup
admin|debug
admin|login
admin|view_user
manager|view_employee
manager|enroll_employee
manager|login
employee|schedule
employee|login
customer|guest_login
customer|change_credentials
guest|guest_login
Thanks in advance!
Add a UNIQUE constraint to the table:
CREATE TABLE permissions(
... ,
UNIQUE (action_id, user_id, role_id)
)
You can then use any of the conflict resolution algorithms to handle duplicates.

Read last Inserted row in Sql according to its Time stamp

sql has a Table called emp.
emp(emp_id int IDENTITY primary key, EmployeeName varchar(50),.......)
I want to Insert a record to above table. Here is my code in asp.net.
DBconnection dbcon = new DBconnection();
string query = "insert into emp values('" + TextBox_EmpName.Text + "','" + ....);
int no1 = dbcon.insertQuery(query);
I have another table called emp-relation
emp-relation(emp_id int primary key, count int, ....)
-- foreign key (emp_id)references emp(emp_id)
My problem is when I inserting the emp row ,I dont know what is the emp_id since it created by auto. And when I am going to insert to emp-relation , I want to get emp-id since it is the foreign key.
How can I do this? Is there any way to read last Insert row in Sql according to Time stamp or some thing? I believe that records are not sorted according to inserted timestamp in nature. please help me.
There's bascally two ways. The first way is to return the new ID from the first insert query:
insert into emp values(...)
select scope_identity() as NewID
The second way is to lookup the first row when you insert into the relation table:
insert emp-relation
(emp_idm, ...)
select emp_id
, ...
from emp
where emp_name = #EmpName
You have to pass in enough columns to make the reference unique.

Refactor SQLite Table by splitting it in two and link with foreign keys

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.
Since #mateusza did not provide an example, I've made one:
Suppose you have this table:
CREATE TABLE [Customer] (
[name] TEXT,
[street] TEXT,
[city] TEXT);
Now you want to move street and city into a separate table Address, so you'll end up with two tables:
CREATE TABLE [Customer2] (
[name] TEXT,
[addr] INTEGER);
CREATE TABLE [Address] (
[rowid] INTEGER NOT NULL,
[street] TEXT,
[city] TEXT,
PRIMARY KEY ([rowid])
);
(For this example, I'm doing the conversion in the same database. You'd probably use two DBs, converting one into the other, with an SQL ATTACH command.)
Now we create a view (which imitates our original table using the new tables) and the trigger:
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;
Now you can copy the table rows:
INSERT INTO Customer1 (name, street, city) SELECT name, street, city FROM Customer;
The above is a simplified case where you'd only move some data into a single new table.
A more complex (and more general) case is where you want to...
Separate your original table's columns into several foreign tables, and
Have unique entries in the foreign tables (that's usually the reason why you'd refactor your table).
This adds some additional challenges:
You'll end up inserting into multiple tables before you can insert their rowids into the table with the referencing rowids. This requires storing the results of each INSERT's last_insert_rowid() into a temporary table.
If the value already exists in the foreign table, its rowid must be stored instead of the one from the (non-executed) insertion operation.
Here's a complete solution for this. It manages a database of music records, constisting of a song's name, album title and artist name.
-- 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;
Note that this example has one potential issue: If the constraints on the foreign table are more complex, the SELECT rowid FROM search for the existing entry needs to be updated accordingly. Ideally, SQLite should provide a way to determine the conflicting rowid somehow, but it doesn't, unfortunately (see this related question).
Simple solution without triggers:
create VEHICLES_TEMP table including the CAR_ID
create your new CARS table without the VEHICLES columns you don't want
update CARS with VEHICLE_ID taken from VEHICLES_TEMP (identified by the CAR_ID)
create final VEHICLES table without the CAR_ID
Create a table New_Cars and a INSTEAD OF INSERT trigger, which will insert data to both tables Vehicles and Cars. When inserting to Cars, you can use last_insert_rowid() function to refer to inserted row in Vehicles table.
This can be temporary solution, or you can leave it in your database for further modifications.

Resources