Let's say I have a table T and an index on field f. I want to filter on f based on some integer myF. If the integer is 0, I want all records where f is null. A sure way to write this would be:
db.rawQuery("SELECT someField FROM T WHERE "
+ (myF == 0 ? "f IS NULL" : "f = ?"),
(myF == 0 ? new String[] {}, new String[] {String.valueOf(myF)}));
This is a bit inconvenient; especially if the query is more complex than this and has additional parameters. So I thought I'd write
db.rawQuery("SELECT someField FROM T WHERE IFNULL(f, 0) = ?",
new String[] {String.valueOf(myF)});
instead, which is much simpler, easier to read and easier to maintain.¹
My question is: If there is an index on f, will SQLite still use that index or will it resort to a table scan? I'm asking because in the latter case I'm not comparing a field and a parameter but an expression and a parameter, and I'm not sure how "smart" the SQLite query optimizer is.
¹ Note that there are no records with f = 0 in the database.
It will result in a table scan.
Example using the sqlite3 command line client:
sqlite> create table t(f);
sqlite> create index tf on t(f);
sqlite> explain query plan select * from t where ifnull(f,0)=1;
0|0|0|SCAN TABLE t (~500000 rows)
sqlite> explain query plan select * from t where f is null;
0|0|0|SEARCH TABLE t USING COVERING INDEX tf (f=?) (~10 rows)
sqlite> explain query plan select * from t where f=1;
0|0|0|SEARCH TABLE t USING COVERING INDEX tf (f=?) (~10 rows)
Related
I am trying to create a view using the following source code:
SQLiteDatabase db = mManagerDbHelper.getWritableDatabase();
String sql = "SELECT * FROM users WHERE name = ?";
String[] selectionArgs = new String[] {"Bob"};
db.execSQL("CREATE VIEW bob_user AS " + sql, selectionArgs);
However, this code always returns this error:
android.database.sqlite.SQLiteException: parameters are not allowed in views (code 1)
How do I use the bindArgs parameter of execSQL(String, Object[]) method?
How do I use the bindArgs parameter of execSQL(String, Object[]) method?
In short you can't, a VIEW is intended to be stored and thus cannot be dynamically changed and hence why you can't bind parameters.
You could however get around it by using an expression for the right hand expression of the WHERE clause and if that expression were to select the value from a table then the value could be changed in that table (i.e. mimicbind in the following) e.g.
DROP VIEW IF EXISTS a_user;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS mimicbind;
CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO users (name) VALUES('bob'),('mary'),('fred'),('sue');
CREATE TABLE IF NOT EXISTS mimicbind (mimicname TEXT PRIMARY KEY, value TEXT);
INSERT INTO mimicbind VALUES('a_user','bob');
CREATE VIEW a_user AS SELECT * FROM users WHERE name = (SELECT value FROM mimicbind WHERE mimicname = 'a_user');
SELECT * FROM a_user;
UPDATE mimicbind SET value = 'sue' WHERE mimicname = 'a_user';
SELECT * FROM a_user;
DROP VIEW IF EXISTS a_user;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS mimicbind;
The result from the queries being:-
and then after the value column in the mimicbind table is changed from bob to sue then:-
However, you may wish to consider what benefit there is to using a view as it would appear that for what you want it is an unnecessary complexity, when a straight forward select would likely be as, if not more efficient. That is a VIEW will, I believe. take up a minimum of 4k per VIEW so bob, alice, etc .... and that's going to be storage space largely wasted.
Even the solution via another table is probably not going to afford any benefit over just using a query. The end result of both would be a Cursor accessing exactly the same core data *(of course in the solution then mimicbind table is additionally accessed)(
I never found a solution here in stackoverflow. So I modified my sql statement and ended up with the following code:
SQLiteDatabase db = mManagerDbHelper.getWritableDatabase();
String sql = "SELECT * FROM users WHERE name = 'Bob'";
db.execSQL("CREATE VIEW bob_user AS " + sql);
Note that I used execSQL(String).
I have a link table event_user with a composite key based on an user_id and event_id.
I have an array of (user_id, event_id) pairs in Java where i want to ensure that there doesn't exists any other (user_id, event_id) pair in the table that doesn't exists in the array.
In other tables, i just created a string of ids, and then i created the following query
DELETE FROM tablename WHERE column NOT IN ( 1 , 2 , ... n)
However this behavior with NOT IN can not be achieved with multiple columns.
How can achieve that using Java and sqlite efficiently?
Thanks in advance.
In SQLite, IN works only with a single column.
So you have to write out all comparisons explicitly:
DELETE FROM TableName
WHERE (Col1 != 1 OR Col2 != 10)
AND (Col1 != 2 OR Col2 != 20)
AND ...;
SQLite can build an index for a large IN list, but this is not done for large expressions like this.
So if the query becomes too big, you can put the IDs into a temporary table, and use a subquery to check for matching rows:
DELETE FROM TableName
WHERE NOT EXISTS (SELECT 1
FROM IDsToKeep
WHERE IDsToKeep.Col1 = TableName.Col1
AND IDsToKeep.Col2 = TableName.Col2);
For efficiency, the temporary table should be indexed (or just be a WITHOUT ROWID table with a PK on both columns).
I'm using ORMLite for working with local database and have an issue with one custom query.
I need to get one row which's some column's value is most common in table.
E.g. I have a column text and I have 3 rows with value text0, 4 rows with value text1 and 5 rows with value text2. I need to get one of the rows which have value text2 of text column.
Can I do this with ORMLite's query builder or I should run a raw query?
This is raw sql query which works fine and returns array of strings.
Dao<Case, Long> caseDao = getHelper().getCaseDao();
GenericRawResults<String[]> cases = caseDao.queryRaw("SELECT * ," +
" COUNT('name') AS 'name_occurrence'" +
" FROM 'MEDICAL_CASE'" +
" GROUP BY 'name'" +
" ORDER BY 'name_occurrence' DESC" +
" LIMIT 1", new String[]{});
After getting strings I have to create an instance of my model, set values and do my jon with that object.
But it's a bit ugly that's why I'm asking if it's possible with methods of ormlite.
Can I do this with ORMLite's query builder or I should run a raw query?
Any results that aren't an entity need to be done through ORMLite's dao.queryRaw(...) methods. If you can do it with SQL then you can do it through those methods.
See: http://ormlite.com/docs/raw-queries
You can't use the standard entity mapper because you are doing a SELECT *, COUNT('name'). ORMLite wouldn't know what to do with the COUNT(...) field.
with a simple query, using bindargs works fine, but as soon as I include select case ..... in the query it always fails "Can't pass bindargs for this sql", if I test with all the '?'s replaced with fixed values, it compiles and runs OK, so it does seem to be that argument binding cannot cope with selects embedded inside select case. Is this true?
the query I am compiling is:
(SELECT CASE
WHEN ((SELECT _id FROM point2D WHERE x=? AND y=?) IS NULL ) THEN 0
ELSE (SELECT _id FROM point2D WHERE x=? AND y=? LIMIT 1))
and the table is setup with
create table if not exists point2D ( _id INTEGER PRIMARY KEY AUTOINCREMENT,
x REAL, y REAL )
I only want a single compiled query that returns the key to an existing matching record, or a known 'its not there' value, but of course a simple query like
SELECT _id FROM point2D WHERE x=? AND y=? LIMIT 1
will cause an exception with simpleQueryForLong when there is nothing found (which is quite expensive in cpu time)
I cannot use insert or update, because update actually replaces the old row with a new row (and hence a new _id) which screws up all the other stuff pointing to this.
You have kept the actual parameter code a secret, but I'd guess that you provide only two parameters although the query has four.
You can use one parameter multiple times if you give it a name or a number:
(SELECT CASE
WHEN ((SELECT _id FROM point2D WHERE x=?1 AND y=?2) IS NULL ) THEN 0
ELSE (SELECT _id FROM point2D WHERE x=?1 AND y=?2 LIMIT 1))
However, for this particular query, you'd better use the ifnull function:
(SELECT ifnull((SELECT _id FROM point2D WHERE x=? AND y=?), 0))
(And I doubt that the outer subquery is actually needed.)
I'm using SQLite on Android using SQLiteDatabase (http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html)
I am developing a bible application, which has a single table with the following columns:
book : int
chapter : int
verse : int
wordIdx : int
strongId : string
word : string
each sentence is broken down in to a series of strongId/word pairs, so wordIdx is used to order the words, strongId is simply a index in to a concordance, and word is the word in the sentence.
so I have 300,000 rows
the bottleneck appears to be my query to get a list of words for each verse:
My SQL is effectively this:
SELECT strongId, word FROM ? WHERE book=? AND chapter=? AND verse=?
Here is the code:
Cursor cursor = mBible.database().rawQuery("SELECT " + KEY_STRONGID + "," + KEY_WORD + " FROM " + tableName() + " WHERE " + KEY_BOOK + "=? AND " + KEY_CHAPTER + "=? AND " + KEY_VERSE + "=?" , new String[] { String.valueOf(mChapter.mBook.index()), String.valueOf(mChapter.index()), String.valueOf(verse) });
cursor.moveToFirst();
mWordList = new ArrayList<Word>();
do {
mWordList.add(new Word(cursor.getString(1), cursor.getString(0)));
} while (cursor.moveToNext());
Now, I've tried putting each chapter in to its own temporary view (using CREATE TEMP VIEW) which cuts down the records to about 400 in my example how ever it is still taking far to long to query
Its taking of the order of 30 seconds to generate the text for two chapters to display to the user (using a temporary view and without using a temporary view). It takes about 5 seconds if I set up a dummy list of words to avoid the database query.
How can I improve the performance of this? It seems as if a temp view is having no impact on performance as I had hoped.
A view does not change the performance of a query; it just saves the query itself, not the results of the query.
If you open your database with the sqlite3 command-line tool on your desktop machine, you can use the EXPLAIN QUERY PLAN command to check how efficient your queries are.
Without any indexes, you query always scans the entire table:
> sqlite3 bible.db
SQLite version 3.7.15.2 2013-01-09 11:53:05
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> EXPLAIN QUERY PLAN SELECT strongId, word FROM MyTable WHERE book=1 AND chapter=2 AND verse=3;
0|0|0|SCAN TABLE MyTable (~1000 rows)
With an index on your three lookup fields, SQLite can do a fast search in the index and needs to read only the matching records from the table:
sqlite> CREATE INDEX b_c_v ON MyTable(book, chapter, verse);
sqlite> EXPLAIN QUERY PLAN SELECT strongId, word FROM MyTable WHERE book=1 AND chapter=2 AND verse=3;
0|0|0|SEARCH TABLE MyTable USING INDEX b_c_v (book=? AND chapter=? AND verse=?) (~8 rows)
If you create a covering index (with all fields used in the query, lookup fields first), SQLite does not need to read from the table at all. However, this does not give a big speedup over a normal index, and might not be worth the additional storage cost:
sqlite> CREATE INDEX cov ON MyTable(book, chapter, verse, strongId, word);
sqlite> EXPLAIN QUERY PLAN SELECT strongId, word FROM MyTable WHERE book=1 AND chapter=2 AND verse=3;
0|0|0|SEARCH TABLE MyTable USING COVERING INDEX cov (book=? AND chapter=? AND verse=?) (~8 rows)
Please note that SQLite can use at most one index per table in a query, so it does not always make sense to create multiple indexes.
Use EXPLAIN QUERY PLAN to check which indexes are actually used, and whether you can create a few indexes to optimize most of your queries.
Also see the Query Planning documentation.
I ended up creating temporary tables and performance is now acceptable