Behavior of SqlLite rowid in case of a INTEGER primary key - sqlite

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

Related

Does PRIMARY KEY constraint defined on table level guarantees AUTOINCREMENT and no values reuse?

Consider the following table definition:
CREATE TABLE names (
id INTEGER,
name TEXT NOT NULL,
PRIMARY KEY (id)
)
Does it guarantee that the id will be auto-incremented for every new insert AND that the values for deleted rows will not be reused?
I looked up in the documentation for Sqlite3, but couldn't find the answer.
id INTEGER PRIMARY KEY on it's own guarantees (requires) a unique integer value and will if no value is specifically assigned provide one until the highest value has reached the highest allowed value for a 64 bit signed integer (9223372036854775807) after which an unused value may be found and applied.
With AUTOINCREMENT there is a guarantee (if not circumvented) of always providing a higher value BUT if 9223372036854775807 is reached instead of allocating an unused number an SQLITE_FULL error will result. That is the only difference from the point of view of what number will be assigned.
Neither guarantees a monotonically increasing value.
Without AUTOINCREMENT the calculation/algorithm is equivalent to
1 + max(rowid) and if the value is greater than 9223372036854775807 an attempt is made to find an unused and therefore lower value.
I've not seen that anyone has come across the situation where a random unused value has not been assigned.
With AUTOINCREMENT the calculation/algorithim is
the greater of 1 + max(rowid) or SELECT seq FROM sqlite_sequence WHERE name = 'the_table_name_the_rowid_is_being_assigned_to' and if the value is greater than 9223372036854775807 then SQLITE_FULL ERROR.
noting that either way there is the possibility that the max rowid is for a row that eventually doesn't get inserted and therefore the potential for gaps.
The answer is perhaps best put as: it's best/recommended to use the id column solely for it's intended purpose, that of efficiently identifying a row and not as a means of handling other data requirements, and if done so, there there is no need for AUTOINCREMENT (which has overheads)
In short
Does it guarantee that the id will be auto-incremented
NO
values for deleted rows will not be reused?
NO for the given code
for :-
CREATE TABLE names (id INTEGER PRIMARY KEY AUTOINCREMENT name TEXT NOT NULL)
again NO as if 9223372036854775807 is reached then an SQLITE_FULL error will result, otherwise YES.
So really AUTOINCREMENT is only really relevant (if the id used as expected/intended) when the 9223372036854775807'th row has been inserted.
Perhaps consider the following :-
DROP TABLE IF EXISTS table1;
DROP TABLE IF EXISTS table2;
CREATE TABLE IF NOT EXISTS table1 (id INTEGER PRIMARY KEY, somecolumn TEXT);
CREATE TABLE IF NOT EXISTS table2 (id INTEGER PRIMARY KEY AUTOINCREMENT, somecolumn TEXT);
INSERT INTO table1 VALUES (9223372036854775807,'blah');
INSERT INTO table2 VALUES (9223372036854775807,'blah');
INSERT INTO table1 (somecolumn) VALUES(1),(2),(3);
SELECT * FROM table1;
INSERT INTO table2 (somecolumn) VALUES(1),(2),(3);
This creates the two similar tables, the only difference being the use of AUTOINCREMENT. Each has a row inserted with the highest allowable value for the id column.
An attempt is then made to insert 3 rows where the id will be assigned by SQLite.
3 rows are inserted into the table without AUTOINCREMENT but no rows are inserted when AUTOINCREMENT is used. as per :-
CREATE TABLE IF NOT EXISTS table1 (id INTEGER PRIMARY KEY, somecolumn TEXT)
> OK
> Time: 0.098s
CREATE TABLE IF NOT EXISTS table2 (id INTEGER PRIMARY KEY AUTOINCREMENT, somecolumn TEXT)
> OK
> Time: 0.098s
INSERT INTO table1 VALUES (9223372036854775807,'blah')
> Affected rows: 1
> Time: 0.094s
INSERT INTO table2 VALUES (9223372036854775807,'blah')
> Affected rows: 1
> Time: 0.09s
INSERT INTO table1 (somecolumn) VALUES(1),(2),(3)
> Affected rows: 3
> Time: 0.087s
SELECT * FROM table1
> OK
> Time: 0s
INSERT INTO table2 (somecolumn) VALUES(1),(2),(3)
> database or disk is full
> Time: 0s
The result of the SELECT for table1 (which may differ due to randomness) was :-

Unique columns pair constraint in SQLite with conflict strategy

I need to create a unique constraint for two columns in a row with conflict strategy attached. Suppose we have a table:
CREATE TABLE `telephones`(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
telephone STRING NOT NULL);
So it is clear that it is a separate table for one-to-many relationship between a user and his telephones. What I need is to create a unique index for user_id and telephone, so database shouldn't have duplicates.
AFAIK, here are two ways of creating such a constraint: either by creating an index as a separated SQL request or by creating a constraint inside CREATE TABLE statement. First way looks like this:
CREATE UNIQUE INDEX `user_ids_and_telephones` ON `telephones`(`user_id`, `telephone`) ON CONFLICT IGNORE
And the second way looks like this:
CREATE TABLE `telephones`(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
telephone STRING NOT NULL,
UNIQUE(`user_id`, `telephone`) ON CONFLICT IGNORE);
My question is: are these ways equivalent and will both work correctly for the goal described, or do they have some logical differences that will affect subsequent duplicates inserting logic?
I didn't find documentation quiet clear about that.
Both ways create an index and as such they act in the same way (see below). The documentation states this as :-
In most cases, UNIQUE and PRIMARY KEY constraints are implemented by
creating a unique index in the database. (The exceptions are INTEGER
PRIMARY KEY and PRIMARY KEYs on WITHOUT ROWID tables.) Hence, the
following schemas are logically equivalent:
CREATE TABLE t1(a, b UNIQUE);
CREATE TABLE t1(a, b PRIMARY KEY);
CREATE TABLE t1(a, b);
CREATE UNIQUE INDEX t1b ON t1(b);
SQL As Understood By SQLite - CREATE TABLE - SQL Data Constraints
However, I do not believe that you can code a conflict clause when defining an index independently. So
CREATE UNIQUE INDEX user_ids_and_telephones ON telephones(user_id, telephone) ON CONFLICT IGNORE is not valid.
As such, the conflict handling will differ.
For example consider the following :-
DROP TABLE IF EXISTS `telephones1`;
CREATE TABLE IF NOT EXISTS `telephones1`(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
telephone STRING NOT NULL);
DROP INDEX IF EXISTS user_ids_and_telephones;
CREATE UNIQUE INDEX `user_ids_and_telephones` ON `telephones1`(`user_id`, `telephone`)
-- ON CONFLICT IGNORE commented out as is invalid
;
DROP TABLE IF EXISTS `telephones2`;
CREATE TABLE IF NOT EXISTS `telephones2`(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
telephone STRING NOT NULL,
UNIQUE(`user_id`, `telephone`) ON CONFLICT IGNORE);
SELECT * FROM sqlite_master WHERE type = 'index' AND name LIKE '%telephones%';
INSERT INTO `telephones2` VALUES
(null,1,'phone1'),(null,2,'phone2'),(null,3,'phone1'),(null,1,'phone1');
INSERT INTO `telephones1` VALUES
(null,1,'phone1'),(null,2,'phone2'),(null,3,'phone1'),(null,1,'phone1');
The insert into telephones2 will not fail but only insert 3 of the 4 rows.
The
insert into telephones1 fails without inserting any rows.
as per :-
DROP TABLE IF EXISTS `telephones1`
> OK
> Time: 0.389s
CREATE TABLE IF NOT EXISTS `telephones1`(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
telephone STRING NOT NULL)
> OK
> Time: 0.31s
DROP INDEX IF EXISTS user_ids_and_telephones
> OK
> Time: 0s
CREATE UNIQUE INDEX `user_ids_and_telephones` ON `telephones1`(`user_id`, `telephone`)
-- ON CONFLICT IGNORE
> OK
> Time: 0.366s
DROP TABLE IF EXISTS `telephones2`
> OK
> Time: 0.383s
CREATE TABLE IF NOT EXISTS `telephones2`(
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
telephone STRING NOT NULL,
UNIQUE(`user_id`, `telephone`) ON CONFLICT IGNORE)
> OK
> Time: 0.358s
SELECT * FROM sqlite_master WHERE type = 'index' AND name LIKE '%telephones%'
> OK
> Time: 0s
INSERT INTO `telephones2` VALUES
(null,1,'phone1'),(null,2,'phone2'),(null,3,'phone1'),(null,1,'phone1')
> Affected rows: 3
> Time: 0.356s
INSERT INTO `telephones1` VALUES
(null,1,'phone1'),(null,2,'phone2'),(null,3,'phone1'),(null,1,'phone1')
> UNIQUE constraint failed: telephones1.user_id, telephones1.telephone
> Time: 0.004s
As can be seen from the output of the query of the slqite_master two indexes are in fact created :-
The one attached to telephones2 being an automatically generated index (i.e. it starts with sqlite_autoindex)

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)
);

sqlite3: how to detect rowid alias column (primary key)

Using SQLite3, if you create a table like this:
CREATE TABLE MyTable (
id int primary key,
--define other columns here--
)
it turns out sqlite3_column_type(0) always returns SQLITE_NULL.
If I read on a bit, this may well be by design because this column is actually an alias to the internal rowid field.
Still, what is the programatical way to determine a certain column is an/the alias to the rowid field?
(Perhaps related, can I use sqlite3_column_type(x)==SQLITE_NULL to determine if the field of the current record holds NULL?)
According to http://www.sqlite.org/draft/lang_createtable.html#rowid
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.
So in your case it's "int" so invalid alias

How do I specify a Primary Key in Sqlite

How to define your specified attribute like StudentId in student table as Primary key in sqlite
CREATE TABLE Student(
id INTEGER PRIMARY KEY,
first_name TEXT,
last_name TEXT
);
From the Sqlite spec:
One exception to the typelessness of
SQLite is a column whose type is
INTEGER PRIMARY KEY. (And you must use
"INTEGER" not "INT". A column of type
INT PRIMARY KEY is typeless just like
any other.) INTEGER PRIMARY KEY
columns must contain a 32-bit signed
integer. Any attempt to insert
non-integer data will result in an
error.
http://www.sqlite.org/datatypes.html
You can also place a primary key on the arbitrary blobish data eg:
CREATE TABLE Student(id PRIMARY KEY, name)
Its a bit risky cause
INSERT INTO Student(1, "hello")
INSERT INTO Student("1", "hello")
will result in two rows.
If you need a unique constraint on other stuff you can try using the Create Index command
CREATE TABLE Students (
StudentId INTEGER PRIMARY KEY,
Name VARCHAR(80)
)
is one simple way.

Resources