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.
I have encountered this situation where i plan to insert a generated column which the expression (the expression to design quoted in (???..I need this suggestion expression too..???) schema table below. the output is a calculation depend on the value of broker_id and place to generated column name brokage.
Your sharing and guidance on the scenario is much appreciated.
Thank you and warm regards
new sqlite learner.
sqlite> .schema buy
CREATE TABLE buy(
buy_id INTEGER,
stock_id INTEGER,
investor_id INTEGER,
broker_id INTEGER,
unit INTEGER,
price REAL,
date TEXT,
cost REAL GENERATED ALWAYS AS (unit*price),
brokage REAL GENERATED ALWAYS AS (??? depend on broker_id for expression ???),
PRIMARY KEY (buy_id, stock_id, investor_id, broker_id),
FOREIGN KEY (stock_id) REFERENCES stock(stock_id) ON DELETE CASCADE ON UPDATE NO ACTION,
FOREIGN KEY (investor_id) REFERENCES stock(investor_id) ON DELETE CASCADE ON UPDATE NO ACTION
FOREIGN KEY (broker_id) REFERENCES stock(broker_id) ON DELETE CASCADE ON UPDATE NO ACTION
);
sqlite> .mode column
sqlite> .header on
sqlite> SELECT * FROM buy;
buy_id stock_id investor_id broker_id unit price date cost brokage
------ -------- ----------- --------- ----- ----- ---------- ------ --------
1 7 1 1 2000 0.68 2020-06-24 1360.0
2 25 1 3 2000 0.88 2020-10-22 1760.0
You can do it with a CASE expression:
CREATE TABLE buy(
buy_id INTEGER,
stock_id INTEGER,
investor_id INTEGER,
broker_id INTEGER,
unit INTEGER,
price REAL,
date TEXT,
cost REAL GENERATED ALWAYS AS (unit*price),
brokage REAL GENERATED ALWAYS AS (CASE broker_id WHEN 1 THEN 9 WHEN 3 THEN 28 END),
PRIMARY KEY (buy_id, stock_id, investor_id, broker_id),
FOREIGN KEY (stock_id) REFERENCES stock(stock_id) ON DELETE CASCADE ON UPDATE NO ACTION,
FOREIGN KEY (investor_id) REFERENCES stock(investor_id) ON DELETE CASCADE ON UPDATE NO ACTION
FOREIGN KEY (broker_id) REFERENCES stock(broker_id) ON DELETE CASCADE ON UPDATE NO ACTION
);
SqlServer
Suppose I have 2 tables:
Table 1 - having column A
Table 2 - having column B [Bit] Not Null
Is it possible to have a Check Constraint, such that value of Column B can be "0", only when Column A is NOT NULL.
OR put it other way, value of Column B can be "1", only when Column A is NULL.
Thanks in advance.
Assuming that these tables are already related by a suitable foreign key, we can implement this check using a computed column and a new foreign key.
Make sure you read to the end
So if we have:
CREATE TABLE Table1 (
Table1ID char(5) not null,
ColumnA int null,
constraint PK_Table1 PRIMARY KEY (Table1ID)
)
CREATE TABLE Table2 (
Table2ID char(7) not null,
Table1ID char(5) not null,
ColumnB bit not null,
constraint PK_Table2 PRIMARY KEY (Table2ID),
constraint FK_Table2_Table1 FOREIGN KEY (Table1ID) references Table1 (Table1ID)
)
We can run this script:
alter table Table1 add
ColumnBPrime as CAST(CASE WHEN ColumnA is NULL THEN 1 ELSE 0 END as bit) PERSISTED
go
alter table Table1 add constraint UQ_Table1_WithColumnBPrime UNIQUE (Table1ID, ColumnBPrime)
go
alter table Table2 add constraint FK_Table2_Table1_CheckColumnB FOREIGN KEY (Table1ID, ColumnB) references Table1 (Table1ID,ColumnBPrime)
Hopefully you can see how this enforces the relationship between the two tables1.
However, there's an issue. In T-SQL, any DML statement may only make changes to one table. So there's no way to issue an update that both changes whether ColumnA is null or not and changes Column B to suit it.
This is another good reason not to have Column B in the database at all - it's derived information, and in our quest to ensure it always matches its definition, we'd have to always delete from Table 2, update Table 1 and re-insert in Table 2.
1It's now a matter of personal taste whether you remove the previous foreign key or leave it in place as the "real" one.
Did anyone work on react native expo's sqlite database with foreign key constraints? Can we use structure similar to sql?
I'm trying to work on it building multiple tables with foreign key condition.
Example: If we have 2 tables Persons and Orders where personID is referred as foreign key in orders table. How would it be done using sqlite?
You would have two tables, perhaps with a column as the alias of the rowid column, this
e.g.
CREATE TABLE persons (
personid INTEGER PRIMARY KEY,
personname TEXT
);
CREATE TABLE orders (
orderid INTEGER PRIMARY KEY,
ordername TEXT,
person_reference INTEGER REFERENCES persons(personid)
);
Note that you have to turn foreign key handling on e.g. by executing PRAGMA foreign_keys = ON; (or true). See PRAGMA foreign_keys
in SQLite coding column_name INTEGER PRIMARY KEY defines that column as an alias of the rowid column, and if a value is not provided for the column when inserting then an integer value will be assigned. The initial value for the first row will be 1, subsequent values will typically be 1 greater than the highest rowid value (read the link above in regards why the word typically has been used).
If you then try to insert an Order for a non-existent personid you will then get a Foreign Key conflict.
An alternative to the column level definition would be to define the foreign key(s) at the table level e.g.
CREATE TABLE orders (
orderid INTEGER PRIMARY KEY,
ordername TEXT,
person_reference INTEGER,
FOREIGN KEY (person_reference) REFERENCES persons(personid)
);
As an example, consider the following :-
INSERT INTO persons (personname) VALUES
('Fred'),
('Mary'),
('Sue'),
('Tom')
;
INSERT INTO orders (ordername, person_reference) VALUES
('Order 1 for Fred',1),
('Order 2 for Sue',3),
('Order 3 for Fred',1),
('Order 4 for Mary',2)
;
INSERT into orders (ordername, person_reference) VALUES
('Order 5 for nobody',100);
The result would be :-
INSERT INTO persons (personname) VALUES ('Fred'),('Mary'),('Sue'),('Tom')
> Affected rows: 4
> Time: 0.453s
INSERT INTO orders (ordername, person_reference) VALUES
('Order 1 for Fred',1),('Order 2 for Sue',3),('Order 3 for Fred',1),('Order 4 for Mary',2)
> Affected rows: 4
> Time: 0.084s
INSERT into orders (ordername, person_reference) VALUES
('Order 5 for nobody',100)
> FOREIGN KEY constraint failed
> Time: 0s
i.e. the last as there is no row in the persons table with a personid of 100, then the last insert (on it's own doe demonstration) fails.
You may wish to refer to SQLite Foreign Key Support
I've a Rate table with the following structre:
CREATE TABLE Rate (
id INTEGER PRIMARY KEY AUTOINCREMENT,
book_id INTEGER,
value INTEGER,
user_id INTEGER,
FOREIGN KEY(user_id) REFERENCES User(ID),
FOREIGN KEY(book_id) REFERENCES book(ID) ON DELETE RESTRICT
)
And a book table with the following structure:
DROP TABLE IF EXISTS Book;
CREATE TABLE Book (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
ISBN VARCHAR,
author_id INTEGER,
editor_id INTEGER,
translator_id INTEGER,
publisher_id INTEGER,
"type" VARCHAR,
language VARCHAR,
"date" VARCHAR,
format VARCHAR,
summary TEXT,
FOREIGN KEY(author_id) REFERENCES Author(ID) ON DELETE SET NULL ON UPDATE CASCADE,
FOREIGN KEY(editor_id) REFERENCES Editor(ID) ON DELETE SET NULL ON UPDATE CASCADE,
FOREIGN KEY(translator_id) REFERENCES Translator(ID) ON DELETE SET NULL ON UPDATE CASCADE,
FOREIGN KEY(publisher_id) REFERENCES Publisher(ID) ON DELETE SET NULL ON UPDATE CASCADE
);
So, if I have a Rate entry like this:
1 1 4 3(ID, BOOK_ID, VALUE, USER_ID)
I shouldn't be able to delete the Book with the ID 1, right?
This is exactly what happens when I try to delete the Book with ID 1 on SQLITE Manager. It gives me
FOREIGN KEY constraint failed
However, when I call my delete from the code, it totally ignores the restriction and deletes the book and there's no change in the Rate entry it is still:
1 1 4 3
And my delete method is like the following:
public void delete() {
try {
String query = "DELETE FROM book WHERE id = ? ";
PreparedStatement statement = db.prepareStatement(query);
statement.setInt(1, (Integer) this.id);
statement.executeUpdate();
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
When i run PRAGMA foreign_keys on SQLITE Manager it returns 1. So, I assume that the database is created correctly.
The documentation says:
... foreign key constraints ... must still be enabled by the application at runtime, using the PRAGMA foreign_keys command. For example:
PRAGMA foreign_keys = ON;
Foreign key constraints are disabled by default (for backwards compatibility), so must be enabled separately for each database connection.