sqlite - How to get INSERT OR IGNORE to work - sqlite

I'm trying to insert data into a table. I would like to insert the row if the column doesn't have the data already - regardless of the other columns.
CREATE TABLE t (
id INTEGER PRIMARY KEY,
name VARCHAR,
other INT
);
INSERT OR IGNORE INTO t (name) VALUES ('a');
INSERT OR IGNORE INTO t (name) VALUES ('a');
INSERT OR IGNORE INTO t (name) VALUES ('a');
With the above snippet I end up with 3 rows, not 1 as I would have thought. If it matters the actual sql is happening inside of a INSTEAD OF INSERT trigger, this is just a simple test case.

Replace
CREATE TABLE t (
id INTEGER PRIMARY KEY,
name VARCHAR,
other INT
);
with
CREATE TABLE t (
id INTEGER PRIMARY KEY,
name VARCHAR UNIQUE,
other INT
);
Then you will get
sqlite> CREATE TABLE t (
...> id INTEGER PRIMARY KEY,
...> name VARCHAR UNIQUE,
...> other INT
...> );
sqlite> INSERT OR IGNORE INTO t (name) VALUES ('a');
sqlite> INSERT OR IGNORE INTO t (name) VALUES ('a');
sqlite> INSERT OR IGNORE INTO t (name) VALUES ('a');
sqlite> select * from t ;
1|a|

That would only work for the primary key field or unique constraints:
The optional conflict-clause allows the specification of an
alternative constraint conflict resolution algorithm to use during
this one INSERT command.
Further:
The ON CONFLICT clause applies to UNIQUE and NOT NULL constraints
(and to PRIMARY KEY constraints which for the purposes of this section
are the same thing as UNIQUE constraints). The ON CONFLICT algorithm
does not apply to FOREIGN KEY constraints. There are five conflict
resolution algorithm choices: ROLLBACK, ABORT, FAIL, IGNORE, and
REPLACE. The default conflict resolution algorithm is ABORT.

Related

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.

Behavior of SqlLite rowid in case of a INTEGER primary key

If I have a non-integer primary-key the rowid is an auto-increment starting at 1.
sqlite> create table t1 (name text, documentid integer, primary key (name));
sqlite> insert into t1 (name, documentid) values ('max', 123);
sqlite> insert into t1 (name, documentid) values ('duf', 321);
sqlite> select rowid,* from t1;
1|max|123
2|duf|321
But if I have a INTEGER primary-key it seems the rowid is equal to it.
sqlite> create table t2 (name text, xid integer, primary key (xid));
sqlite> insert into t2 (name, xid) values ('max', 123);
sqlite> insert into t2 (name, xid) values ('duf', 321);
sqlite> select rowid,* from t2;
123|max|123
321|duf|321
Thats unexpected for me. I would expect rowid to behave like in the 1st sample.
Is that normal behaviour? Can I make it work like expected?
I am using SqlLite3 3.27
The problem is not the value as long it is uniqua (must be by definition of primary). But in JDBC I can not address ResultSet.getInt ("rowid") anymore - need to use getInt ("xid") instead" to make it work. Thats abnormal to a table with a non-integer primar-key.
An INTEGER PRIMARY KEY column is just an alias for the rowid. It acts the same (Having a value automatically assigned if left out when inserting a row), and doesn't even take up any extra space in the database. You can reference the column via its name, rowid, or any of the other standard aliases for rowid like oid.
From the documentation:
With one exception noted below, if a rowid table has a primary key that consists of a single column and the declared type of that column is "INTEGER" in any mixture of upper and lower case, then the column becomes an alias for the rowid. Such a column is usually referred to as an "integer primary key". A PRIMARY KEY column only becomes an integer primary key if the declared type name is exactly "INTEGER". Other integer type names like "INT" or "BIGINT" or "SHORT INTEGER" or "UNSIGNED INTEGER" causes the primary key column to behave as an ordinary table column with integer affinity and a unique index, not as an alias for the rowid.
If you just do
INSERT INTO t2(name) VALUES ('max');
a value will be automatically generated for xid instead of explicitly using the one provided in the insert like in your example.
Yes it's the normal behavior.
When you define an integer column xid as primary key, then xid is just an alias of rowid.
What you can do is define xid as UNIQUE and not PRIMARY KEY:
create table t2 (name text, xid integer unique)
Then you will have the functionality that you want, because the rowid will be a different auto increment column.
Or define xid as TEXT:
create table t2 (name text, xid text, primary key (xid));
In this case also rowid is a different column and don't worry about the data you store in xid.
You can treat this column just like an integer column so you can perform any arithmetic calculation and aggregation.
You can find more here: https://www.sqlite.org/rowidtable.html

Primary key does not allow unique replace functionality

CREATE TABLE resource_sync
(
_id INTEGER UNIQUE ON CONFLICT REPLACE PRIMARY KEY,
status_id INTEGER,
result_id INTEGER
);
In case two equal _id values get inserted, SQLite would throw an exception:
[13:39:48] Error while executing SQL query on database 'test': UNIQUE
constraint failed: resource_sync._id
However, it allows for desired replaces in case primary key declaration is removed from table creation SQL.
Why is that?
Thanks.
UNIQUE is ignored on primary keys.
The correct syntax, as shown in the documentation, is:
CREATE TABLE resource_sync
(
_id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
status_id INTEGER,
result_id INTEGER
);

Delete row and referencing rows in another table in SQLite 3

I'm using SQLite to play around and learn some more SQL. I have a SQLite 3 database populated like this:
create table playlist (id integer primary key autoincrement, name text);
create table playlistitem (id integer primary key autoincrement,
playlist_id integer, name text);
insert into playlist (name) values ("Moss");
insert into playlist (name) values ("Jen");
insert into playlistitem (playlist_id, name) values (1, "Roy");
insert into playlistitem (playlist_id, name) values (1, "Richmond");
insert into playlistitem (playlist_id, name) values (2, "Denholm");
Great, now I have two playlist items in the "Moss" playlist, "Roy" and "Richmond"; I have one item in the "Jen" playlist: "Denholm".
What I'd like to do is delete the "Moss" playlist and all of its items with a single query.
I saw something like this, which fails for me:
delete playlist, playlistitem from playlist
inner join playlistitem on playlistitem.playlist_id = playlist.id
where playlist.name = "Moss";
Failure:
Error: near "playlist": syntax error
What am I doing wrong?
sqlite doesn't support join in delete statement. You have to use separate query that deletes from second table based on playlist_id, making a delete trigger on playlist, or make that reference a foreign key with on delete cascade:
create table playlistitem (
id integer primary key autoincrement,
playlist_id integer, name text,
foreign key(playlist_id) references playlist(id) on delete cascade);
and then just using delete from playlist where name='Moss'.
Don't forget to enable foreign keys - pragma foreign_keys=1 (you have to re-enable this on each sqlite connection, e.g. as the first command after connecting).

INSERT OR IGNORE INTO doesn't work

I have a table created as:
create table association (_id integer unique primary key autoincrement , id_rules integer, id_places integer)";
To avoid replication of entry, I use the statement INSERT OR IGNOR, but it doesn't work. For example,
value (id_rules , id_places) = ("11","1") alredy in table, but using:
INSERT OR IGNORE INTO association (id_rules , id_places) VALUES ("11","1")
a new row is created.
Please, do anyone Know hwere is my mistake?
INSERT OR IGNORE will ignore any rows that would violate a UNIQUE constraint.
The only such constraint is on the _id column, which you did not specify.
If you want to prevent duplicates in those two columns, you have to add a constraint for them to the table definition:
CREATE TABLE association (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
id_rules INTEGER,
id_places INTEGER,
UNIQUE (id_rules, id_places)
);

Resources