Adding IDs to join table for many-to-many in SQLite - sqlite

I am using SQLite Studio 3.3 to work with a simple video database. These are three of the tables I am working with.
-- Table: Videos
DROP TABLE IF EXISTS Videos;
CREATE TABLE Videos (
id INTEGER PRIMARY KEY ASC AUTOINCREMENT
UNIQUE
NOT NULL,
vid_title TEXT NOT NULL
UNIQUE,
vid_rel_date DATE
);
-- Table: People
DROP TABLE IF EXISTS People;
CREATE TABLE People (
id INTEGER PRIMARY KEY AUTOINCREMENT
NOT NULL
UNIQUE,
person_name_first TEXT NOT NULL,
person_name_last TEXT
);
-- Table: _Videos_People
DROP TABLE IF EXISTS _Videos_People;
CREATE TABLE _Videos_People (
id INTEGER PRIMARY KEY AUTOINCREMENT
UNIQUE
NOT NULL,
vid_id INTEGER REFERENCES Videos (id),
person_id INTEGER REFERENCES People (id)
);
I have these statements to add a video and a person to the database and to get both IDs.
INSERT OR IGNORE INTO Videos (
vid_title,
vid_rel_date
)
VALUES (
'The Call Of The Wild',
'2020'
);
INSERT OR IGNORE INTO People (
person_name_first,
person_name_last
)
VALUES (
'Harrison',
'Ford'
);
SELECT id
FROM Videos
WHERE vid_title = 'The Call Of The Wild'
AND vid_rel_date = '2020';
SELECT id
FROM People
WHERE person_name_first = 'Harrison'
AND person_name_last = 'Ford';
This is where I am stuck. Once I have these two IDs, how do I add them to the _Videos_People join table so that the person is associated with the video?
I found this but I don't know how to use it for my situation (or if I should).
If I am going about this completely wrong, I am open to suggestions. I am new at this so I appreciate any help.

You can use this INSERT statement:
INSERT INTO _Videos_People (vid_id, person_id) VALUES (
(SELECT id FROM Videos WHERE vid_title = 'The Call Of The Wild' AND vid_rel_date = '2020'),
(SELECT id FROM People WHERE person_name_first = 'Harrison' AND person_name_last = 'Ford')
);
See the demo.

Related

Insert the id from a previous query inside a sqlite trigger

In a sqlite3 database I would like to create a trigger on a view so that I can insert data over the view. Inside the trigger I would like to insert something in the tblmedia table. The id of the inserted row should be now also inserted into the tblbook as id.
In sqlite there are no variables. Otherwise I would store the returning value in the variable and would use it in the second query.
Can this even be achieved in sqlite?
Following my sql schema:
CREATE TABLE tblmedia(
id INTEGER PRIMARY KEY NOT NULL,
title VARCHAR NOT NULL,
raiting INTEGER,
file_name VARCHAR NOT NULL,
media_type TEXT NOT NULL
);
CREATE TABLE tblbook(
id INTEGER PRIMARY KEY NOT NULL,
author VARCHAR,
FOREIGN KEY (id) REFERENCES tblmedia(id) ON DELETE CASCADE
);
CREATE VIEW book AS
SELECT
m.id as id,
m.title as title,
b.author as author,
m.raiting as raiting,
m.file_name as file_name
FROM tblbook b
LEFT JOIN tblmedia m ON m.id = b.id;
CREATE TRIGGER insert_book
INSTEAD OF INSERT ON book
BEGIN
INSERT INTO tblmedia(title, raiting, file_name)
VALUES(new.title, new.raiting, new.file_name);
INSERT INTO tblbook(id, author)
VALUES (xx, new.author); -- xx should be the id from the previous insert
END

Insert primary key of unique value into foreign key

If I have a two tables like this:
CREATE TABLE users
(
id INTEGER PRIMARY KEY,
username TEXT UNIQUE NOT NULL
);
CREATE TABLE games
(
id INTEGER PRIMARY KEY,
player1 INTEGER REFERENCES users,
player2 INTEGER REFERENCES users,
comment TEXT
);
How can I insert a new row into the games table given two usernames?
The primary keys of the users would need to be looked up by their names in the users table first and then inserted into the games table. What's the best way to do this?
So, instead of
INSERT INTO games (player1, player2)
VALUES (1, 2);
how can I combine this with looking up the id values from usernames?
You could use subqueries to lookup the two usernames based on their ids:
INSERT INTO games (player1, player2, comment)
SELECT
(SELECT username FROM users WHERE id = 1),
(SELECT username FROM users WHERE id = 2),
'good luck';

Except the name of table what property can use to differentiate these tables

I have a database which contains many tables and these tables can be added or removedd any time.So I give each of them a different name like Table1,Table2,...
but it's uncomfortable to use these table because sometime I forget what infomation was stored in Table1
So I want something to differentiate these all tables, some property that I can be specified when I create a table and I can use to access a specific table when I need to fetch informations from that table
As one comment says, you could create a table for notes on the other tables:
CREATE TABLE notes (
id INT AUTO_INCREMENT,
table_name VARCHAR(64),
note VARCHAR(255),
PRIMARY KEY (id)
);
By the way, MySQL (but not SQLite) allows comments on the table itself:
CREATE TABLE table1 (
id INT AUTO_INCREMENT,
val INT,
PRIMARY KEY (id)
) COMMENT = 'Table of stuff';
-- Show the comment
SHOW TABLE STATUS WHERE NAME='table1';
-- Just show names and comments
SELECT `TABLE_NAME`, `TABLE_COMMENT`
FROM information_schema.tables
WHERE table_schema = DATABASE();

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.

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