I need to reorder the rowid sequentially after deleting some rows. Vacuum on table seems to be a good fit for the situation but for some reasons its not reordering the rowid.
sqlite> .schema inboxmessages
CREATE TABLE InboxMessages(id text not null, title text not null, text text not null, senders_username text not null, created_at text not null, sender_image text not null, read text not null, Unique(id));
sqlite> select rowid, id from inboxmessages limit 5;
1|746915
3|746540
4|746195
5|745403
6|745371
sqlite> vacuum inboxmessages;
sqlite> select rowid, id from inboxmessages;
1|746915
3|746540
4|746195
5|745403
6|745371
whats wrong ?
VACUUM is not guaranteed to reorder rowid values, and if there is some INTEGER PRIMARY KEY column, it never will.
If you want to have specific values for your rowids, you have to set them manually.
Please note that in most cases, it is a better idea to just leave gaps in the rowid sequence.
Related
In order to migrate old db to room library, I have to make equivalent to line
primary key(id1, id2 desc)
from
create table t(
id1 integer not null,
id2 integer not null,
....
primary key(id1, id2 desc)
)
I can't find how to define sorting order of composite indices or primary keys. It seems like Android room haven't particular annotations, or Annotation parameters if see androidx.room.Entity or androidx.room.Index.
Is it not supported? What are other ways to solve the problem? Create regular composite key on id1 and id2, and in all select queries set order by ?
Unless your table is WITHOUT ROWID, the primary key in your table is just a separate unique index.
So you just have to create your own index after the table is created.
Is an index needed for a primary key in SQLite?
Look at the sqlite3 example - sqlite just creates a separate sqlite_autoindex_t_with_pk_1 for you along with manually created indices.
sqlite> create table t_with_pk(id1 integer not null, id2 integer not null, primary key(id1, id2 desc));
sqlite> create table t_no_pk(id1 integer not null, id2 integer not null);
sqlite> create unique index t_uniq_idx on t_no_pk(id1, id2 desc);
sqlite> select * from sqlite_master;
table|t_with_pk|t_with_pk|2|CREATE TABLE t_with_pk(id1 integer not null, id2 integer not null, primary key(id1, id2 desc))
index|sqlite_autoindex_t_with_pk_1|t_with_pk|3|
table|t_no_pk|t_no_pk|4|CREATE TABLE t_no_pk(id1 integer not null, id2 integer not null)
index|t_uniq_idx|t_no_pk|5|CREATE UNIQUE INDEX t_uniq_idx on t_no_pk(id1, id2 desc)
The behavior is also identical
sqlite> explain query plan select * from t_with_pk order by id1, id2 desc;
QUERY PLAN
`--SCAN TABLE t_with_pk USING COVERING INDEX sqlite_autoindex_t_with_pk_1
sqlite> explain query plan select * from t_no_pk order by id1, id2 desc;
QUERY PLAN
`--SCAN TABLE t_no_pk USING COVERING INDEX t_uniq_idx
Note also (regarding #Developer's answer) that just inverting the order in a SELECT statements requires sqlite to create a temp index
sqlite> explain query plan select * from t_no_pk order by id1, id2;
QUERY PLAN
|--SCAN TABLE t_no_pk USING COVERING INDEX t_uniq_idx
`--USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
Room supports raw queries via RoomDatabase.Callback so you can override onCreate method of callback and execute raw sql of creating index.
You can do somehow like
SELECT
select_list
FROM
table
ORDER BY
column_1 ASC,
column_2 DESC;
for details https://www.sqlitetutorial.net/sqlite-order-by/
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
I am writing an Android app and need a database for it. I will have three tables but only managed to make one right now. I do them in the console to debug and to implement them in my Javacode later. The following statements were succesfull:
sqlite3 progressapp.db
CREATE TABLE Z_Type(_Z_T_ID INTEGER PRIMARY KEY NOT NULL,
Description TEXT NOT NULL, Unit TEXT NOT NULL);
But now I want to refference the PK of T_Type in my other table:
CREATE TABLE goals (_Z_id INTEGER PRIMARY KEY NOT NULL, Title TEXT NOT NULL,
Type INTEGER NOT NULL, FOREIGN KEY(Type) REFERENCES Z_Type(_Z_T_ID),
Timeframe TEXT, Goaldate INTEGER);
Is Type INTEGER NOT NULL, FOREIGN KEY(Type) REFERENCES Z_Type(_Z_T_ID) a valid SQLite Statement in Android? It says "Error: near "Timeframe": syntax error" But I simply can't find it due to lack with SQL Experience I guess.
Is there a better way to reference the FK maybe?
Try this:
CREATE TABLE goals (_Z_id INTEGER PRIMARY KEY NOT NULL, Title TEXT NOT NULL,
Type INTEGER NOT NULL,Timeframe TEXT, Goaldate INTEGER, FOREIGN KEY(Type) REFERENCES Z_Type(_Z_T_ID));
I think that the order is important.
For further documentation you could visit sqlite.org/foreignkeys.html
You can define the reference as part of the column definition
CREATE TABLE goals (_Z_id INTEGER PRIMARY KEY NOT NULL, Title TEXT NOT NULL,
Type INTEGER NOT NULL REFERENCES Z_Type(_Z_T_ID),
Timeframe TEXT, Goaldate INTEGER);
In a sqlite CREATE TABLE statement, column definitions come first and table constraints only after that.
FOREIGN KEY(Type) REFERENCES Z_Type(_Z_T_ID) is a table constraint that should go at the end.
Now I have a weird problem, I've done all kinds of test and I believe I'm seeing something weird.
I create three tables in SQLiteOpenHelper:
public void onCreate(SQLiteDatabase db) {
try {
db.execSQL(TABLE_CHANNELS_CREATE);
db.execSQL(TABLE_FEEDS_CREATE);
db.execSQL(TABLE_FEEDMAP_CREATE);
}
catch (SQLiteException e){
Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_LONG).show();
}
}
The CREATE statements for the three tables follow:
CREATE TABLE IF NOT EXISTS IRChannels (
ChannelId INTEGER PRIMARY KEY,
ChannelHash TEXT NOT NULL,
ChannelTitle TEXT NOT NULL,
ChannelDesc TEXT, ChannelLink TEXT);
CREATE TABLE IF NOT EXISTS IRFeeds (
FeedId INTEGER PRIMARY KEY,
FeedHash TEXT NOT NULL,
FeedTitle TEXT NOT NULL,
FeedDescription TEXT,
FeedLink TEXT);
CREATE TABLE IF NOT EXISTS IRFeedMap (
ChannelHash_FK TEXT NOT NULL,
FeedHash_FK TEXT NOT NULL,
FOREIGN KEY (ChannelHash_FK) REFERENCES IRChannels (ChannelHash),
FOREIGN KEY (FeedHash_FK) REFERENCES IRFeeds (FeedHash));
The problem is apparently the column FeedHash in IRFeeds is not created while others are. I'm looking at the output in sqlite3 command prompt;
sqlite> .schema
CREATE TABLE IRChannels (
ChannelId INTEGER PRIMARY KEY,
ChannelHash TEXT NOT NULL,
ChannelTitle TEXT NOT NULL,
ChannelDesc TEXT,
ChannelLink TEXT);
CREATE TABLE IRFeedMap (
ChannelHash_FK TEXT NOT NULL,
FeedHash_FK TEXT NOT NULL,
FOREIGN KEY (ChannelHash_FK) REFERENCES IRChannels (ChannelHash),
FOREIGN KEY (FeedHash_FK) REFERENCES IRFeeds (FeedHash));
CREATE TABLE IRFeeds (
FeedId INTEGER PRIMARY KEY,
FeedHash TEXT NOT NULL,
FeedTitle TEXT NOT NULL,
FeedDescription TEXT,
FeedLink TEXT);
This does list the FeedHash column in IRFeeds. However, when I execute
sqlite> select * from IRFeeds where FeedHash='';
SQL error: no such column: FeedHash
All other columns do not give such errors. This condition is causing my code to fail unexpectedly as well. What could I be missing?
sqlite> select * from IRFeeds where FeedID=1;
sqlite> select * from IRFeeds where FeedTitle='';
sqlite> select * from IRFeeds where FeedDescription='';
sqlite> select * from IRFeeds where FeedLink='';
No errors above when I execute select statement for other columns.
There is no error in your SQL. I tested and everything was created properly. Also your SQL query did not cause no such column error. So try to delete the database with context.deleteDatabase(databaseName); and try again.
After an entire day of struggle, I managed to isolate why the problem triggers. Still don't know why, but it does fix the problem. The problems occurs because of the following table which has foreign keys on the other two tables:
CREATE TABLE IRFeedMap (
ChannelHash_FK TEXT NOT NULL,
FeedHash_FK TEXT NOT NULL,
FOREIGN KEY (ChannelHash_FK) REFERENCES IRChannels (ChannelHash),
FOREIGN KEY (FeedHash_FK) REFERENCES IRFeeds (FeedHash));
Changing the column names of foreign key columns to be the same as the column they reference fixes the problem. I changed the statement above to:
CREATE TABLE IRFeedMap (
ChannelHash TEXT NOT NULL,
FeedHash TEXT NOT NULL,
FOREIGN KEY (ChannelHash) REFERENCES IRChannels (ChannelHash),
FOREIGN KEY (FeedHash) REFERENCES IRFeeds (FeedHash));
And voila! Sanity was restored. Beats me.
In my case I was using a SQLite reserved word (column, that was)
I ended up in this SO question, so maybe it helps others in my situation
How do I get the row ID from a Cursor?
I don't think the Cursor exposes this directly.
SQLiteDatabase.insert() returns the row id of the newly inserted row. Or in Android the convention is that there is a column named "_id" that contains the primary autoincrement key of the table. So cursor.getLong(cursor.getColumnIndex("_id")) would retrieve this.
I had this same problem where the column index for the primary key was reported as -1 (meaning it isn't there). The problem was that I forgot to include the _ID column in the initial SELECT clause that created the cursor. Once I made sure it was included, the column was accessible just like any of the others.
Concerning the last sentence of Nic Strong's answer,following command didn't work for me. cursor.getColumnIndex("_id") was still -1
cursor.getLong(cursor.getColumnIndex("_id"))
Maybe there's some other issue in my configuration that's causing the problem?
Personally I've taken to maintaining my own custom unique id column in each table I create; A pain, but it gets around this issue.
return sqlite_db.query(table, new String[] { "rowid", "*" }, where, args, null, null, null);
In my case I have "rowid" in DataManager.FIELD_ID and this is SQLite identity column (each table in sqlite has this special kind of column), so I don't need any of my own custom unique id column in tables.
Cursor cursor = mySQLiteHelper.getReadableDatabase().query(TABLE_NAME, new String[] { "ROWID", "*" }, where, null, null, null, null);
then
long rowId = cursor.getLong(0);
As long as the TABLE is not defined using WITHOUT ROWID you can get the ROWID (or should that be rowid see below for case) if you specify it as a column to be retrieved.
For example for a table with 3 defined columns (Names, Colour and Age) with no conventional _id column. The following rawQuery works and returns the ROWID :-
Cursor csr = db.rawQuery("SELECT Names, Colour, Age, ROWID FROM " + TABLE_NAME,null);
Note! that the column name in the cursor is lowercase as per :-
Note! ROWID in the SELECT SQL is case independent (e.g. RoWiD works).
Using
Cursor csr = db.rawQuery("SELECT * FROM " + TABLE_NAME,null);
WILL NOT return the ROWID (likewise for null for the columns when using the query method).
Using query (as opposed to rawQuery) works in the same way, that is you need to specifiy ROWID (note alternatives to ROWID below) as a column to be retrieved e.g. :-
Cursor csr = db.query(TABLE_NAME,new String[]{
"OiD",
"Names",
"Colour",
"Age"
},null,null,null,null,null);
Alternatives to ROWID
Instead of ROWID, you can also use _ROWID_ or OID (case independent) as the column name in the query noting that the column name in the resultant cursor is rowid i.e. it is lowercase.