Sqlite update and autoincrement value - sqlite

I want to update the schema of a db. I have copied the auto-generated script, but the last line after each table's script is this:
UPDATE "main"."sqlite_sequence" SET seq = 8 WHERE name = 'table';
The sec value is indeed correct for my installed DB, but it could vary on other installations. So, would it be safe to set it to 0, or should I select it from each installation's table? Or could I just skip this line and run the script without it?

If by "auto-generated" script you mean the full .dump of your database, then it will include the create table statements, and the insert statements, so you probably want the update to be executed along.
If you modify that auto-generated script, then you can obviously change the seq value as necessary.
Here is what the documentation has to say:
SQLite keeps track of the largest ROWID that a table has ever held
using the special SQLITE_SEQUENCE table. The SQLITE_SEQUENCE table is
created and initialized automatically whenever a normal table that
contains an AUTOINCREMENT column is created. The content of the
SQLITE_SEQUENCE table can be modified using ordinary UPDATE, INSERT,
and DELETE statements. But making modifications to this table will
likely perturb the AUTOINCREMENT key generation algorithm. Make sure
you know what you are doing before you undertake such changes.
In the end, you need to make sure that the seq value matches the highest value. This demonstrates:
sqlite> create table foo (a INTEGER PRIMARY KEY AUTOINCREMENT, b text);
sqlite> insert into foo values (NULL, 'blabla');
sqlite> select * from foo;
1|blabla
sqlite> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE foo (a INTEGER PRIMARY KEY AUTOINCREMENT, b text);
INSERT INTO "foo" VALUES(1,'blabla');
DELETE FROM sqlite_sequence;
INSERT INTO "sqlite_sequence" VALUES('foo',1);
COMMIT;

Related

Can Sqlite's AUTOINCREMENT/Primary Key be started at a number other than 1?

I would like to start different tables off at different values for their primary keys during testing to verify I don't have any bugs in my code. Is this possible in Sqlite?
As documented, the last value of an AUTOINCREMENT column is stored in the internal sqlite_sequence table, where it can be changed.
Yes, you can do that.
The simplest way is probably just to insert a row, and specify the the number. If I wanted to start with 1000, I might do something like this.
sqlite> create table test (test_id integer primary key, s char(1));
sqlite> insert into test values (999, 'a');
sqlite> insert into test (s) values ('b');
sqlite> select * from test;
999|a
1000|b
After you've inserted the "first" row (test_id is 1000), you can delete the "seed" row (test_id is 999).

ALTER COLUMN in sqlite

How do I alter column in sqlite?
This is in Postgresql
ALTER TABLE books_book ALTER COLUMN publication_date DROP NOT NULL;
I believe there is no ALTER COLUMN in sqlite at all, only ALTER TABLE is supported.
Any idea? Thanks!
There's no ALTER COLUMN in sqlite.
I believe your only option is to:
Rename the table to a temporary name
Create a new table without the NOT NULL constraint
Copy the content of the old table to the new one
Remove the old table
This other Stackoverflow answer explains the process in details
While it is true that the is no ALTER COLUMN, if you only want to rename the column, drop the NOT NULL constraint, or change the data type, you can use the following set of dangerous commands:
PRAGMA writable_schema = 1;
UPDATE SQLITE_MASTER SET SQL = 'CREATE TABLE BOOKS ( title TEXT NOT NULL, publication_date TEXT)' WHERE NAME = 'BOOKS';
PRAGMA writable_schema = 0;
You will need to either close and reopen your connection or vacuum the database to reload the changes into the schema.
For example:
Y:\> **sqlite3 booktest**
SQLite version 3.7.4
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> **create table BOOKS ( title TEXT NOT NULL, publication_date TEXT NOT
NULL);**
sqlite> **insert into BOOKS VALUES ("NULLTEST",null);**
Error: BOOKS.publication_date may not be NULL
sqlite> **PRAGMA writable_schema = 1;**
sqlite> **UPDATE SQLITE_MASTER SET SQL = 'CREATE TABLE BOOKS ( title TEXT NOT
NULL, publication_date TEXT)' WHERE NAME = 'BOOKS';**
sqlite> **PRAGMA writable_schema = 0;**
sqlite> **.q**
Y:\> **sqlite3 booktest**
SQLite version 3.7.4
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> **insert into BOOKS VALUES ("NULLTEST",null);**
sqlite> **.q**
REFERENCES FOLLOW:
pragma writable_schema
When this pragma is on, the SQLITE_MASTER tables in which database can be changed using ordinary UPDATE, INSERT, and DELETE statements. Warning: misuse of this pragma can easily result in a corrupt database file.
[alter table](From http://www.sqlite.org/lang_altertable.html)
SQLite supports a limited subset of ALTER TABLE. The ALTER TABLE command in SQLite allows the user to rename a table or to add a new column to an existing table. It is not possible to rename a column, remove a column, or add or remove constraints from a table.
SQLite supports a limited subset of ALTER TABLE. The ALTER TABLE command in SQLite allows the user to rename a table or to add a new column to an existing table. It is not possible to rename a column, remove a column, or add or remove constraints from a table. But you can alter table column datatype or other property by the following steps.
BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
CREATE TABLE t1(a,b);
INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT
For more detail you can refer the link.
CREATE TABLE temp_Table(x,y[,etc]);
INSERT INTO temp_Table SELECT * FROM Table;
DROP TABLE Table;
ALTER TABLE temp_Table RENAME TO Table;
Thanks for helping me to find a definitive method!
ALTER COLUMN does not exist in SQLite.
Only Supported alter operations:
Alter Table Name
Alter Table Column Name
Add New Column
Drop Column
Alex Jasmin's answer shows possible way
Reference:
Sqlite Alter Table

How to merge N SQLite database files into one if db has the primary field?

I have a bunch of SQLite db files, and I need to merge them into one big db files.
How can I do that?
Added
Based on this, I guess those three commands should merge two db into one.
attach './abc2.db' as toMerge;
insert into test select * from toMerge.test
detach database toMerge
The problem is the db has PRIMARY KEY field, and I got this message - "Error: PRIMARY KEY must be unique".
This is the test table for the db.
CREATE TABLE test (id integer PRIMARY KEY AUTOINCREMENT,value text,goody text)
I'm just thinking off my head here... (and probably after everybody else has moved on, too).
Mapping the primary key to "NULL" should yield the wanted result (no good if you use it as foreign key somewhere else, since the key probably exists, but has different contents)
attach './abc2.db' as toMerge;
insert into test select NULL, value, goody from toMerge.test;
detach database toMerge;
actual test:
sqlite> insert into test select * from toMerge.test;
Error: PRIMARY KEY must be unique
sqlite> insert into test select NULL, value, goody from toMerge.test;
sqlite> detach database toMerge;
I'm not 100% sure, but it seems that I should read all the elements and insert the element (except the PRIMARY KEY) one by one into the new data base.

Sqlite insert into with unique names, getting id

I have a list of strings to insert into a db. They MUST be unique. When i insert i would like their ID (to use as a foreign key in another table) so i use last_insert_rowid. I get 2 problems.
If i use replace, their id
(INTEGER PRIMARY KEY) updates which
breaks my db (entries point to
nonexistent IDs)
If i use ignore, rowid is not updated so i do not get the correct ID
How do i get their Ids? if i dont need to i wouldnt want to use a select statement to check and insert the string if it doesnt exist . How should i do this?
When a UNIQUE constraint violation occurs, the REPLACE algorithm deletes pre-existing rows that are causing the constraint violation prior to inserting or updating the current row and the command continues executing normally. This causes the rowid to change and creates the following problem
Y:> **sqlite3 test**
SQLite version 3.7.4
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> **create table b (c1 integer primary key, c2 text UNIQUE);**
sqlite> **insert or replace into b values (null,'test-1');**
sqlite> **select last_insert_rowid();**
1
sqlite> **insert or replace into b values (null,'test-2');**
sqlite> **select last_insert_rowid();**
2
sqlite> **insert or replace into b values (null,'test-1');**
sqlite> **select last_insert_rowid();**
3
sqlite> **select * from b;**
2|test-2
3|test-1
The work around is to change the definition of the c2 column as follows
create table b (c1 integer primary key, c2 text UNIQUE ON CONFLICT IGNORE);
and to remove the "or replace" clause from your inserts;
then when test after your insert, you will need to execute the following sql: select last_insert_rowid(), changes();
sqlite> **create table b (c1 integer primary key, c2 text UNIQUE ON CONFLICT IGNORE);**
sqlite> **insert into b values (null,'test-1');**
sqlite> **select last_insert_rowid(), changes();**
1|1
sqlite> **insert into b values (null,'test-2');**
sqlite> **select last_insert_rowid(), changes();**
2|1
sqlite> **insert into b values (null,'test-1');**
sqlite> **select last_insert_rowid(), changes();**
2|0
The return value of changes after the 3rd insert will be a notification to your application that you will need to lookup the rowid of "test-1", since it was already on file. Of course if this is a multi-user system, you will need to wrap all this in a transaction as well.
I use the below currently
insert into tbl(c_name) select 'val' where not exists(select id from tbl where c_name ='val');
select id from tbl where c_name ='val';
By "they MUST be unique", do they mean you are sure that they are, or that you want an error as a result if they aren't? If you just make the string itself a key in its table, then I don't understand how either 1 or 2 could be a problem -- you'll get an error as desired in case of unwanted duplication, otherwise the correct ID. Maybe you can clarify your question with a small example of SQL code you're using, the table in question, what behavior you are observing, and what behavior you'd want instead...?
Edited: thanks for the edit but it's still unclear to me what SQL is giving you what problems! If your table comes from, e.g.:
CREATE TABLE Foo(
theid INTEGER PRIMARY KEY AUTOINCREMENT,
aword TEXT UNIQUE ABORT
)
then any attempt to INSERT a duplicated word will fail (the ABORT keyword is optional, as it's the default for UNIQUE) -- isn't that what you want given that you say the words "MUST be unique", i.e., it's an error if they aren't?
The correct answer to your question is: This cannot be done in sqlite. You have to make an additional select query. Quoting the docs for last_insert_rowid:
An INSERT that fails due to a constraint violation is not a successful INSERT and does not change the value returned by this routine. Thus INSERT OR FAIL, INSERT OR IGNORE, INSERT OR ROLLBACK, and INSERT OR ABORT make no changes to the return value of this routine when their insertion fails
Having the same problem in 2022, but since SQLite3 version 3.35.0 (2021-03-12), we have RETURNING.
Combined with UPSERT, it is now possible to achieve this
sqlite> create table test (id INTEGER PRIMARY KEY, text TEXT UNIQUE);
sqlite> insert into test(text) values("a") on conflict do update set id = id returning id;
1
sqlite> insert into test(text) values("a") on conflict do update set id = id returning id;
1
sqlite> insert into test(text) values("b") on conflict do update set id = id returning id;
2
sqlite> insert into test(text) values("b") on conflict do update set id = id returning id;
2
sqlite> select * from test;
1|a
2|b
sqlite> insert into test(text) values("b") on conflict do nothing returning id;
sqlite>
Sadly, this is still a workaround rather than an elegant solution...
On conflict, the insert becomes an update. This means that your update triggers will fire, so you may want to stay away from this!
When the insert is converted into an update, it needs to do something (cf link). However, we don't want to do anything, so we do a no-op by updating id with itself.
Then, returning id gives us the what we want.
Notes:
Our no-op actually does an update, so it costs time, and the trigger on update will fire. But without triggers, it has no effect on the data
Using on conflict do nothing returning id does not fail, but does not return the id.
If usable (again, check your triggers), and if all your tables use the primary key id, then this technique does not need any specialization: just copy/paste on conflict do update set id = id returning id;

Set start value for AUTOINCREMENT in SQLite

How can I set the start value for an AUTOINCREMENT field in SQLite?
From the SQLite web site:
SQLite keeps track of the largest ROWID that a table has ever held using the special SQLITE_SEQUENCE table. The SQLITE_SEQUENCE table is created and initialized automatically whenever a normal table that contains an AUTOINCREMENT column is created. The content of the SQLITE_SEQUENCE table can be modified using ordinary UPDATE, INSERT, and DELETE statements. But making modifications to this table will likely perturb the AUTOINCREMENT key generation algorithm. Make sure you know what you are doing before you undertake such changes.
I tried this, and it works:
UPDATE SQLITE_SEQUENCE SET seq = <n> WHERE name = '<table>'
Where n+1 is the next ROWID you want and table is the table name.
Explicitly insert the value-1 into the table, then delete the row.
Edit: the next comment down, which discusses editing the SQLITE_SEQUENCE table directly is probably preferable: https://stackoverflow.com/a/692871/10093
I am using the below query which solves the problem when the sqlite_sequence does not have a record for the table (i.e. first record was not added yet to the table), otherwise it updates the sequence.
BEGIN TRANSACTION;
UPDATE sqlite_sequence SET seq = <n> WHERE name = '<table>';
INSERT INTO sqlite_sequence (name,seq) SELECT '<table>', <n> WHERE NOT EXISTS
(SELECT changes() AS change FROM sqlite_sequence WHERE change <> 0);
COMMIT;
One way to do it is to insert the first row specifying explicitly the row id you want to start with. SQLite will then insert row ids that are higher than the previous highest.
In solution with SQLITE_SEQUENCE table, the entry into this table seems to be added after the first insert into the table with the autoincrement column is added. In some cases this might cause troubles (i.e autoincrement still starts from 1, not from wanted value).
Just wanted to add a few notes to the very much appreciated answer from iTech:
The name column in sqlite_sequence is case sensitive. (Perhaps its only me, but coming from other databases I always assume that string comparison is case insensitive).
SQLite seems to be robust: if the number in sqlite_sequence is wrong and would lead to a duplicated rowid value, sqlite will use the next available number for the rowid (checked with sqlite 3.28)
Same is true if the row in sqlite_sequence gets deleted.
I used as suggested in a comment the "WHERE NOT EXISTS SELECT name from sqlite_sequence WHERE name = 'table'" instead of checking "changes()"
I tried this and it works good:
FOR INSERT
INSERT INTO sqlite_sequence (name, seq) VALUES ('<table name>', <value>)
TO UPDATE
UPDATE sqlite_sequence SET seq = <value> WHERE name= '<table name>'

Resources