SQLite - integer PRIMARY INDEX constraint failing? - android

In my Android app, I create a FULLTEXT table like this:
CREATE VIRTUAL TABLE products USING fts3 (
_id integer PRIMARY KEY,
product_name text NOT NULL,
...
)
And I add this index:
CREATE INDEX product_name_index ON products (product_name)
The app populates the table with various products, each with a unique _id value.
However, when I then try to insert an already-existing product ID (using an _id value that is already in the table, but with a different product_name value) like this:
long rowId = db.insertOrThrow("products", null, contentValues);
a new row is added to the table (with a brand new rowId value returned)!
I expected the insertOrThrow command to fail, so where am I going wrong? Is it something to do with the fact that it's a FULLTEXT table or could the index I specified on the product_name column be messing things up somehow?
I read this section about INTEGER PRIMARY KEY, but unfortunately I'm none the wiser.
Update
When I try to perform the same operation on a standard (non-FULLTEXT) table, then the insertOrThrow command results in the expected SQLiteConstraintException.

I think the issue might be that an FTS table has the concept of a docid and a rowid column and specifying null for the docid results in that being given a value.
as per :-
There is one other subtle difference between "docid" and the normal
SQLite aliases for the rowid column.
Normally, if an INSERT or UPDATE
statement assigns discrete values to two or more aliases of the rowid
column, SQLite writes the rightmost of such values specified in the
INSERT or UPDATE statement to the database.
However, assigning a
non-NULL value to both the "docid" and one or more of the SQLite rowid
aliases when inserting or updating an FTS table is considered an
error. See below for an example.
1.3. Populating FTS Tables

Related

how can we get the id of a row that we insert through code

I need to retrieve the id of the row inserted just now. ie, i have a table for words and a table for meaning. i need the wordId of the word i insert in the table for words and that wordId is used for inserting the meaning in meaning table. Can anyone help me out??
I thought i could use trigger and tried the trigger:
"CREATE TRIGGER IF NOT EXISTS word_insert_trigger AFTER INSERT ON tb_words BEGIN select NEW.word_id from tb_words; END;"
like this. i tried this in sqlite dbbrowser. but it didn't work out.
i need the row id when i insert a row like this :"insert into tb_words(word_name) values('test');"
How can i do that without using "SELECT last_insert_rowid()"? like in the following link:
How to retrieve the last autoincremented ID from a SQLite table?
No need for a trigger. Use the SQliteDatabase insert method. It returns the id (as a long) (more correctly it returns the rowid and assuming that the word_id column has been defined as an alias of the rowid column, then the returned value will be the value assigned to the word_id column).
An alias of the rowid column is defined if word_id INTEGER PRIMARY KEY is coded (the AUTOINCREMENT key may be used BUT in generally should not be used).
You may wish to read SQLite AUTOINCREMENT and/or Rowid Tables
Instead of something like :-
db.execsql("insert into tb_words(word_name) values('test');");
You would use something like :-
ContentValues cv = new ContentValues();
cv.put("word_name","test");
long word_id = db.insert("tb_words",null,cv);

SQLite Autoincrement not inserting

I have an SQLite Database and when I insert the ID should be automaticly incrementet with AUTOINCREMENT.
But it is always null.
This is the create table
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE ausgaben (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, TAG text,DATUM text, AUSGABE text, MENGE text, KATEGORIE text)");
}
And this is how I insert data:
public boolean insertAusgabe(String tag, String datum, String ausgabe, String menge, String kategorie){
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.putNull("id");
contentValues.put(AUSGABEN_TAG,tag);
contentValues.put(AUSGABEN_DATUM,datum);
contentValues.put(AUSGABEN_AUSGABE,ausgabe);
contentValues.put(AUSGABEN_MENGE,menge);
contentValues.put(AUSGABEN_KATEGORIE,kategorie);
db.insert(TABLE_NAME,null,contentValues);
return true;
}
If I understand right, this should work correctly.
But the database looks like this:
It would appear that you are expecting the id column to be null rather than a number.
If you code id INTEGER PRIMARY KEY NOT NULL AUTOINCREMENT (see note about AUTOINCREMENT below) then that column is a special column that is an alias of the rowid column (unless the table has been defined using WITHOUT ROWID).
The rowid column cannot be null and must be a integer value. If an attempt is made to insert a row where the value for the column is null (or not specified) then SQLite will assign an integer value (long for java). 1 if there are no rows in the table then 1 greater than the highest number used.
Hence why you have a sequence of numbers in the id column.
If, for example the table were defined using id INT PRIMARY KEY NOT NULL then, the id column IS NOT an alias of the rowid column. (AUTOINCREMENT can then not be used as it can only be used for an alias of the rowid column) Then none of the inserts would work as the value for the id column would be NULL which due to the coding of the NOT NULL constraint will result in a constraint conflict.
However if the column were defined using id INT PRIMARY KEY, then null values for the id would be allowed. Noting that coding PRIMARY KEY, implies a UNIQUE constraint, that is all values must be UNIQUE. SQLite considers all NULL values as being unique in comparison to each other.
So the last definition would allow what appears to be your expected result. However, what use would an indeterminate value be for the purpose of identifiyting a row? (that's rhetorical).
As such the result you initially obtained, is the more useful result. Even if not intended.
A note on AUTOINCREMENT
AUTOINCREMENT is very likely not needed, this specifies an extension of the rowid determination algorithm in that it
enforces the latest rowid value being greater than any existing or used rowid,
that is it relies upon another table, namely sqlite_sequence to record the highest allocated rowid and then it uses the higher of the highest existing rowid or the value stored for the table in the sqlite_sequence table.
With AUTOINCREMENT when the highest possible value (9223372036854775807) has been assigned and an attempt is made to insert a new row. Then an SQLITE_FULL error will result. Without, attempts are made to use an random unused value (e.g. if rows have been deleted).
With AUTOINCREMENT there is an overhead (something like 8-12% according to What are the overheads of using AUTOINCREMENT for SQLite on Android?).
NOTE
It should be noted that there is no gaurantee that the rowid, with or without the AUTOINCREMENT keyword will increase by 1. There are some situations where values may be skipped as per
Note that "monotonically increasing" does not imply that the ROWID always increases by exactly one. One is the usual increment. However, if an insert fails due to (for example) a uniqueness constraint, the ROWID of the failed insertion attempt might not be reused on subsequent inserts, resulting in gaps in the ROWID sequence. AUTOINCREMENT guarantees that automatically chosen ROWIDs will be increasing but not that they will be sequential.
SQLite Autoincrement
*In short it is not wise to have any expectation of the * id/rowid column to be anything other than a means of efficiently identifying a row.

how to store syntax in sqlite

1.which data type should be used to store data like group=AB+ ?
E/SQLiteLog: (1) near "group": syntax error SQLiteDatabase:
Error inserting
district=jhapa phone=9843284985 name=Tom group=AB+
My table is in this format
CREATE TABLE IF NOT EXISTS `Doners` (\n" +
"\t`name`\tTEXT,\n" +
"\t`phone`\tNUMERIC,\n" +
"\t`group`\tBLOB,\n" +
"\t`district`\tTEXT\n" +
");";
For Android you can either use
- native SQL via the SQLiteDatabase execSQL method
- the SQLiteDatabase convenience insert family of methods :-
insert (effectively INSERT OR IGNORE)
insertOrThrow (standard INSERT)
insertWithOnConflict
SQLiteDatabase - insert
So assuming that you want to insert :-
Tom into the name column,
9843284985 into the phone column,
AB+ into the group column,
NOTE that group is an SQLite keyword and therefore cannot be used and will result in a syntax error, unless it is enclosed SQL As Understood By SQLite - SQLite Keywords
jhapa
And that the variable db is an instantiated instance of the SQliteDatabase class then :-
you could use :-
db.execSQl("INSERT INTO `Doners` VALUES('Tom',9843284985,'AB+','jhapa')");
noting that a value must be provided for all the defined columns and that the values should be in the order that the columns were defined in.
or you could use :-
db.execSQL("INSERT INTO Doners (district,phone,name,`group`) VALUES ('jhapa','9843284985','Tom','AB+')");
Here you specify the columns into which the values will be placed, you can specify them in what order you like (values will be inserted according to the order), you can also omit columns (dependant upon the column definition)
Defining a column as NOT NULL would require a column and value. However, if a DEFAULT value has been defined as well as NOT NULL then the column can be omitted.
you could use the insert convenience method like :-
ContentValues cv = new ContentValues();
cv.put("phone","9843284985");
cv.put("name","Tom);
cv.put("`group`","AB+");
cv.put("district","jhapa");
long rowid = db.insert("Doners",null,cv);
rowid will be the rowid of the inserted row (a unique identifier of the row) or if no row was inserted then -1.
the convenience method :-
it builds the SQL on your behalf
protects against SQL injection
encloses values accordingly
suitable encodes byte[]'s into the the x'ff00fe.......' used by SQL.
returns the rowid (executes a query using last_insert_rowid()).
in regards to :-
which data type should be used to store data like group=AB+ ?
Due to SQLite's flexibility it probably does not matter what type is assigned to the column. That is with the exception of the rowid or an alias of the rowid (the_column INTEGER PRIMARY KEY makes the_column an alias of the rowid column) any type of data can be stored in any type of column and to further expand on the flexibility type can be virtually anything (keywords and other syntactically confusing values excepted).
As such CREATE TABLE mytable (mycolumn RUMPLESTILTSKIN) is valid (column has numeric affinity). see - Datatypes In SQLite Version 3

How to get unique _id column for a multi-column primary key?

According to some blogs like http://reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications/ and even in some of the aswers here.
One of the first steps before including the datababe into the project is to rename the primary id field of your tables to "_id" so Android will know where to bind the id field of your tables.
What should be done with a table that have a combined primary key
Assume that i'm creating the relation between the product id and the store id to assign it's price.
CREATE TABLE `Products-Stores` (
`product` INTEGER NOT NULL,
`store` INTEGER NOT NULL,
`price` INTEGER NOT NULL,
PRIMARY KEY(product,store)
);
There is no need to rename any column in your database. SQL allows column aliases like this:
SELECT integer_primary_key AS _id
...
The only time this is necessary is when you are using a ListAdapter to display the contents of a cursor queried from your DB. You must have an integer primary key column, named "_id" in the cursor, to do that
Better yet, every SQLite database table has an implicit column named "rowid". You don't even have to have your own integer primary key column. Just use rowid, like so:
SELECT rowid AS _id
...
EDITED TO INCLUDE #CL's EXPLANATION OF WORKING JOINS
Obviously, this trick won't work, for many kinds of joins. As long as the rowids are unique over all the rows in the join, though, it works fine.

SQLite prevents duplicate column names in my VIEW

I am creating a SQLite VIEW that is the result of multiple joined tables. All my tables have an _id column as required by Android. The result has multiple columns with the same _id name, but SQLite adds ":1" and ":2" to the duplicate names so they are no longer duplicates.
If you run the below SQL you can see the resulting view has interesting column names:
CREATE TABLE things ("_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE , "name" TEXT NOT NULL);
CREATE TABLE thing_colors ("_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE , "thing_id" INTEGER NOT NULL , "color" TEXT NOT NULL);
INSERT INTO things VALUES ("1","car");
INSERT INTO things VALUES ("2","horse");
INSERT INTO things VALUES ("3","lamp");
INSERT INTO thing_colors VALUES ("1","1","blue");
INSERT INTO thing_colors VALUES ("2","1","red");
INSERT INTO thing_colors VALUES ("3","2","brown");
INSERT INTO thing_colors VALUES ("4","3","silver");
INSERT INTO thing_colors VALUES ("5","3","gold");
CREATE VIEW things_and_colors AS SELECT * FROM things JOIN thing_colors ON things._id=thing_colors.thing_id;
SELECT * FROM things_and_colors;
I find these renamed column names useful but is this normal SQL behavior and is it fine for me to rely on it?
But of course this is just an example, in real life I am joining three tables and the result has about 70 columns in it, of which 3 are named _id.
Don't select star, select the columns individually and assign an alias as needed.
No, you can't depend on the view renaming your columns to avoid conflicts. I don't have a copy of the standard handy so I can't quote chapter and verse but I know that PostgreSQL will say this:
ERROR: column "_id" specified more than once
and MySQL will say this:
ERROR 1060 (42S21): Duplicate column name '_id'
Those are the only databases I have handy at the moment.

Categories

Resources