How do I use prepared statements in SQlite in Android?
For prepared SQLite statements in Android there is SQLiteStatement. Prepared statements help you speed up performance (especially for statements that need to be executed multiple times) and also help avoid against injection attacks. See this article for a general discussion on prepared statements.
SQLiteStatement is meant to be used with SQL statements that do not return multiple values. (That means you wouldn't use them for most queries.) Below are some examples:
Create a table
String sql = "CREATE TABLE table_name (column_1 INTEGER PRIMARY KEY, column_2 TEXT)";
SQLiteStatement stmt = db.compileStatement(sql);
stmt.execute();
The execute() method does not return a value so it is appropriate to use with CREATE and DROP but not intended to be used with SELECT, INSERT, DELETE, and UPDATE because these return values. (But see this question.)
Insert values
String sql = "INSERT INTO table_name (column_1, column_2) VALUES (57, 'hello')";
SQLiteStatement statement = db.compileStatement(sql);
long rowId = statement.executeInsert();
Note that the executeInsert() method is used rather than execute(). Of course, you wouldn't want to always enter the same things in every row. For that you can use bindings.
String sql = "INSERT INTO table_name (column_1, column_2) VALUES (?, ?)";
SQLiteStatement statement = db.compileStatement(sql);
int intValue = 57;
String stringValue = "hello";
statement.bindLong(1, intValue); // 1-based: matches first '?' in sql string
statement.bindString(2, stringValue); // matches second '?' in sql string
long rowId = statement.executeInsert();
Usually you use prepared statements when you want to quickly repeat something (like an INSERT) many times. The prepared statement makes it so that the SQL statement doesn't have to be parsed and compiled every time. You can speed things up even more by using transactions. This allows all the changes to be applied at once. Here is an example:
String stringValue = "hello";
try {
db.beginTransaction();
String sql = "INSERT INTO table_name (column_1, column_2) VALUES (?, ?)";
SQLiteStatement statement = db.compileStatement(sql);
for (int i = 0; i < 1000; i++) {
statement.clearBindings();
statement.bindLong(1, i);
statement.bindString(2, stringValue + i);
statement.executeInsert();
}
db.setTransactionSuccessful(); // This commits the transaction if there were no exceptions
} catch (Exception e) {
Log.w("Exception:", e);
} finally {
db.endTransaction();
}
Check out these links for some more good info on transactions and speeding up database inserts.
Atomic Commit In SQLite (Great in depth explanation, go to Part 3)
Database transactions
Android SQLite bulk insert and update example
Android SQLite Transaction Example with INSERT Prepared Statement
Turbocharge your SQLite inserts on Android
https://stackoverflow.com/a/8163179/3681880
Update rows
This is a basic example. You can also apply the concepts from the section above.
String sql = "UPDATE table_name SET column_2=? WHERE column_1=?";
SQLiteStatement statement = db.compileStatement(sql);
int id = 7;
String stringValue = "hi there";
statement.bindString(1, stringValue);
statement.bindLong(2, id);
int numberOfRowsAffected = statement.executeUpdateDelete();
Delete rows
The executeUpdateDelete() method can also be used for DELETE statements and was introduced in API 11. See this Q&A.
Here is an example.
try {
db.beginTransaction();
String sql = "DELETE FROM " + table_name +
" WHERE " + column_1 + " = ?";
SQLiteStatement statement = db.compileStatement(sql);
for (Long id : words) {
statement.clearBindings();
statement.bindLong(1, id);
statement.executeUpdateDelete();
}
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.w("Exception:", e);
} finally {
db.endTransaction();
}
Query
Normally when you run a query, you want to get a cursor back with lots of rows. That's not what SQLiteStatement is for, though. You don't run a query with it unless you only need a simple result, like the number of rows in the database, which you can do with simpleQueryForLong()
String sql = "SELECT COUNT(*) FROM table_name";
SQLiteStatement statement = db.compileStatement(sql);
long result = statement.simpleQueryForLong();
Usually you will run the query() method of SQLiteDatabase to get a cursor.
SQLiteDatabase db = dbHelper.getReadableDatabase();
String table = "table_name";
String[] columnsToReturn = { "column_1", "column_2" };
String selection = "column_1 =?";
String[] selectionArgs = { someValue }; // matched to "?" in selection
Cursor dbCursor = db.query(table, columnsToReturn, selection, selectionArgs, null, null, null);
See this answer for better details about queries.
I use prepared statements in Android all the time, it's quite simple:
SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
stmt.bindString(1, "US");
stmt.executeInsert();
If you want a cursor on return, then you might consider something like this:
SQLiteDatabase db = dbHelper.getWritableDatabase();
public Cursor fetchByCountryCode(String strCountryCode)
{
/**
* SELECT * FROM Country
* WHERE code = US
*/
return cursor = db.query(true,
"Country", /**< Table name. */
null, /**< All the fields that you want the
cursor to contain; null means all.*/
"code=?", /**< WHERE statement without the WHERE clause. */
new String[] { strCountryCode }, /**< Selection arguments. */
null, null, null, null);
}
/** Fill a cursor with the results. */
Cursor c = fetchByCountryCode("US");
/** Retrieve data from the fields. */
String strCountryCode = c.getString(cursor.getColumnIndex("code"));
/** Assuming that you have a field/column with the name "country_name" */
String strCountryName = c.getString(cursor.getColumnIndex("country_name"));
See this snippet Genscripts in case you want a more complete one. Note that this is a parameterized SQL query, so in essence, it's a prepared statement.
jasonhudgins example won't work. You can't execute a query with stmt.execute() and get a value (or a Cursor) back.
You can only precompile statements that either returns no rows at all (such as an insert, or create table statement) or a single row and column, (and use simpleQueryForLong() or simpleQueryForString()).
To get a cursor, you can't use a compiledStatement. However, if you want to use a full prepared SQL statement, I recommend an adaptation of jbaez's method... Using db.rawQuery() instead of db.query().
Related
I have to make more than 300 selects from my database.
Each of those queries has to be called inside of a for each loop, here's an example:
for(int id : myids){
Cursor cursor = MyDatabaseHelper.runMyQuery(id);
while(cursor.moveToNext()){
//my stuff...
}
}
MyDatabaseHelper is an instance of a database helper class, the function is like this
public Cursor runMyQuery(int id){
SQLiteDatabase db = this.getWritableDatabase();
Cursor ret = db.rawQuery("select Name, Surname, Age from PTable where Id = " + id, null);
return ret;
}
I've been told that the constant "open and close" of the db because of multiple queries it the cause of my performance issues and I should, instead, make a single query (using union etc).
Changing my code to a single query would mean changing the entire database, and I was hoping not to do that.
Is there anything I can do to improve the performance and keep the multiple selects at the same time?
Thanks
I think what you are looking for is the in clause.
Convert your myids into a string. Something like
String inClause = "(1,2,3)"
and you can use it as
"select Name, Surname, Age from PTable where Id in " + inClause
You can read more of the in operator here
You can return a single Cursor containing all the rows.
First change your runMyQuery() method to this:
public Cursor runAll(String list){
SQLiteDatabase db = this.getWritableDatabase();
String sql = "select Name, Surname, Age from PTable where " + list + " like '%,' || id || ',%'"
Cursor ret = db.rawQuery(sql, null);
return ret;
}
So you pass to the method runAll() a String which is the the comma separated list of all the ids that you have in myids and with th eoperator LIKE you compare it to each id of the table.
You create this list and get the results in a Cursor object like this:
StringBuilder sb = new StringBuilder(",");
for(int id : myids){
sb.append(String.valueOf(id)).append(",");
}
String list = sb.length() > 1 ? sb.toString() : "";
if (list.length() > 0) {
Cursor c = runAll(list);
while(c.moveToNext()){
//your stuff...
}
}
I want to fetch phone number linked to particular email in the database. I am not able to find the query for it or how
public String getContactNumber(String email){
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT " + COLUMN_USER_MOBILE_NUMBER + " FROM " + TABLE_USER + " WHERE " + email + " = " + COLUMN_USER_EMAIL;
Cursor cursor = db.rawQuery(query,null);
//What to put here to extract the data.
String contact = cursor.getString(get);
cursor.close();
return contact;
}
to extract the data. Completely a beginner
Try this ..
public List<String> getMyItemsD(String emailData) {
List<String> stringList = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
String selectQuery = "SELECT COLUMN_USER_MOBILE_NUMBER FROM " + USER_TABLE_NAME + " WHERE email= " + emailData;
Cursor c = db.rawQuery(selectQuery, null);
if (c != null) {
c.moveToFirst();
while (c.isAfterLast() == false) {
String name = (c.getString(c.getColumnIndex("Item_Name")));
stringList.add(name);
c.moveToNext();
}
}
return stringList;
}
public String getContactNumber(String email){
String contact = "";
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT " + COLUMN_USER_MOBILE_NUMBER + " FROM " + TABLE_USER + " WHERE " + email + " = " + COLUMN_USER_EMAIL;
Cursor cursor = db.rawQuery(query,null);
if(cursor.getCount()>0) {
cursor.moveToNext();
contact = cursor.getString(cursor.getColumnIndex(COLUMN_USER_MOBILE_NUMBER));
}
//What to put here to extract the data.
cursor.close();
return contact;
}
From this method you get phone number value of that email which you pass any other method easily.
I'd suggest the following :-
public String getContactNumber(String email){
String contact = "NO CONTACT FOUND"; //<<<<<<<<<< Default in case no row is found.
SQLiteDatabase db = this.getWritableDatabase(); //<<<<<<<<<< Generally getReadable gets a writable database
String[] columns_to_get = new String[]{COLUMN_USER_MOBILE_NUMBER};
String whereclause = COLUMN_USER_EMAIL + "=?";
String[] whereargs = new String[]{email};
Cursor cursor = db.query(TABLE_USER,columns_to_get,whereclause,whereargs,null,null,null);
//What to put here to extract the data.
if (cursor.moveToFirst()) {
contact = csr.getString(csr.getColumnIndex(COLUMN_USER_MOBILE_NUMBER));
}
cursor.close();
return contact;
}
The above does assumes that there will only be 1 row per email (which is most likely).
Explanations
A default value is set so that you can easily tell if an invalid/non-existent email is passed (you'd check the return value if need be (might be easier to simply have "" and check the length as a check)).
getReadableDatabase has been replaced with getWritableDatabase as unless there are issues with the database a writable database will be returned, as per :-
Create and/or open a database. This will be the same object returned
by getWritableDatabase() unless some problem, such as a full disk,
requires the database to be opened read-only. In that case, a
read-only database object will be returned. If the problem is fixed, a
future call to getWritableDatabase() may succeed, in which case the
read-only database object will be closed and the read/write object
will be returned in the future.
getReadableDatabase
Note no real problem either way;
The recommended query method has been used instead of the rawQuery method. This has distinct advantages, it builds the underlying SQL and also offers protection against SQL injection (just in case the email passed is input by a user).
this version of the method takes 7 parameters :-
The table name as a string
The columns to be extracted as an array of Strings (aka String array). null can be all columns.
The where clause less the WHERE keyword with ?'s to represent arguments (see next). null if no WHERE clause.
The arguments to be applied (replace ?'s 1 for 1) as a String array. null if none or no WHERE clause.
The GROUP BY clause, less the GROUP BY keywords. null if no GROUP BY clause.
The HAVING clause, less the HAVING keyword. null if no HAVING clause.
The ORDER BY clause, less the ORDER BY keywords. null if no ORDER BY clause.
SQLiteDatabase - query
- Note there are 4 query methods (see link for the subtle difference, I believe this is the most commonly used)
The data extraction is the new code. When a Cursor is returned it is at a position BEFORE THE FIRST ROW, so you need to move to a valid row. So the moveToFirst* method is suitable (note that if a move cannot be made by a move method that it will return false, hence how you can say if (cursor.moveToFirst())). The data is then extracted from the appropriate column use the **getString method, which takes an int as an argumnet for the column offset (0 in this case). However, using hard coded values can lead to issues so the getColumnIndex method is used to get the offset according to the column name (-1 is returned if the named column is not in the Cursor).
I am developing an application where the user inputs title and the date. I want to prevent the duplicated titles being inputted on the same day in to database. I am checking if the title exists on the selected date. However my query seems not to work and i don't know why, the application just crashes.Is this query correct? Can someone help?
public boolean checkExist(String title, String date) {
SQLiteDatabase db = this.getWritableDatabase();
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
boolean exists = c.moveToFirst();
c.close();
return exists;
}
One issue that you have is that c.moveToFirst will always fail if a match does not exist as you are trying to move to a row in an empty cursor.
The resolution is to not use c.moveToFirst and instead get the count of the rows and then set the return value accordingly.
e.g.
public boolean checkExist(String title, String date) {
SQLiteDatabase db = this.getWritableDatabase();
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
boolean exists = c.getCount() > 0;
c.close();
return exists;
}
The second issue is that the query itself is wrong as you do not have spaces either side of the AND keyword. That is instead of
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
You should have
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +" AND " + DATE+"=?", new String[] {title,date});
Personally, I setup constants for SQL keywords that include the space and then use these. So I'd have something along the lines of +TITLE+"=?" + SQLAND + DATE+"=?". Where SQLAND would be defined along the lines of String SQLAND=" AND ";
PS look at Cricket_007's answer, the code is neater/better it's easier to read.
Your spacing is off. TITLE+"=?" +"AND" + DATE becomes TITLE=?ANDDATE=?
I would suggest this. See DatabaseUtils.queryNumEntries
public boolean checkExist(String title, String date) {
SQLiteDatabase db = getReadableDatabase();
String[] args = new String[] {title,date};
String filter = String.format("%s=? AND %s=?", TITLE, DATE);
return DatabaseUtils.queryNumEntries(db, TABLE_NAME, filter, args) > 0;
}
you should be using c.getCount() instead of c.moveToFirst()
if the value is greater than 0, then it exists
Insertion code using SQLiteStatement usually looks like this,
String sql = "INSERT INTO table_name (column_1, column_2, column_3) VALUES (?, ?, ?)";
SQLiteStatement statement = db.compileStatement(sql);
int intValue = 57;
String stringValue1 = "hello";
String stringValue2 = "world";
// corresponding to each question mark in the query
statement.bindLong(1, intValue);
statement.bindString(2, stringValue1);
statement.bindString(3, stringValue2);
long rowId = statement.executeInsert();
Now this works perfectly fine but the issue I find here is that I have to be very careful about binding correct data to corresponding indexes. A simple swap of index will give me an error.
Also let's say in future my column_2 gets dropped from the table, then I would have to change all the indexes after the column_2 index otherwise the statement won't work. This seems trivial if I just have 3 columns. Imagine if a table has 10-12 (or even more) columns and column 2 gets dropped. I'll have to update the index of all the subsequent columns. This whole process seems inefficient and error prone.
Is there an elegant way to handle all this?
Edit : Why would I want to use SQLiteStatement ? Check this :Improve INSERT-per-second performance of SQLite?
Insertions can be done with ContentValues:
ContentValues cv = new ContentValues();
cv.put("column_1", 57);
cv.put("column_2", "hello");
cv.put("column_3", "world");
long rowId = db.insertOrThrow("table_name", null, cv);
But in the general case, the most correct way would be to use named parameters. However, these are not supported by the Android database API.
If you really want to use SQLiteStatement, write your own helper function that constructs it from a list of columns and takes care of matching it with the actual data. You also could write your own bindXxx() wrapper that maps previously-saved column names to parameter indexes.
You can use ContentValues with beginTransaction into SQLite that is quite easy as well as faster then prepared statements
For this you have to create ContentValues Array previously or create Content values object into your loop. and pass into insert method .this solution solve your both of problem in one.
mDatabase.beginTransaction();
try {
for (ContentValues cv : values) {
long rowID = mDatabase.insert(table, " ", cv);
if (rowID <= 0) {
throw new SQLException("Failed to insert row into ");
}
}
mDatabase.setTransactionSuccessful();
count = values.length;
} finally {
mDatabase.endTransaction();
}
I tried the following SQLite query:
int idServizo = 150;
String whereClause = id_servizio+" = '"+idServizio+" ' ";
ContentValues cv = new ContentValues();
cv.put("sync", 1);
int r = dbManager.updateTable("myTable", cv, whereClause);
Where fields sync and id_servizio are both integer.
The method updateTable is:
public int updateTable(String table, ContentValues values, String whereClause){
int r = mDb.update(table, values, whereClause, null);
return r;
}
// mDb is SQLiteDatabase object
All this works good.
But if I try this with the rawQuery() method:
public Cursor RawQuery(String sqlQuery, String[] columns){
return mDb.rawQuery(sqlQuery, columns);
}
The table is not updated! even if no error occurs.
int idServizo = 150;
String updateQuery ="UPDATE myTable SET sync = 1 WHERE id_servizio = "+idServizio;
dbManager.RawQuery(updateQuery, null);
Why does this not work?
This is because when a rawQuery is executed cursor is returned. Without the call to cursor.moveToFirst() and cursor.close() the database won't get updated.
int idServizo = 150;
String updateQuery ="UPDATE myTable SET sync = 1 WHERE id_servizio = "+idServizio;
Cursor c= dbManager.rawQuery(updateQuery, null);
c.moveToFirst();
c.close();
I dont know the need to call moveToFirst() but this works fine and the database gets updated.
Problem solved.
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
Can't works because rawQuery runs the provided SQL and returns a Cursor over the result set.
If I want to return a table I have to use rawQuery, otherwise no!
Increase the value of a record in android/sqlite database
You should use db.execSQL() instead db.rawQuery().
Instead of doing this:
Cursor c= dbManager.RawQuery(updateQuery, null);
c.moveToFirst();
c.close();
You just need this:
dbManager.execSQL(updateQuery, null);
----------------------------------------------------------------------------
Posting answer because sometimes many people (like me) not reading comments.
Most popular answer is not correct but Yaqub Ahmad's comment is correct.
Answer from CommonsWare explained in this answer:
rawQuery() is for SQL statements that return a result set. Use
execSQL() for SQL statements, like INSERT, that do not return a result
set.
----------------------------------------------------------------------------
Documentation for execSQL:
public void execSQL (String sql)
Execute a single SQL statement that is NOT a SELECT or any other SQL statement that returns data.
Documentation for rawQuery:
public Cursor rawQuery (String sql,
String[] selectionArgs)
Runs the provided SQL and returns a Cursor over the result set.
Your update call formats the ID as string, while the rawQuery call formats is as number.
Assuming that the ID in the table indeed is a string, use:
String updateQuery = "UPDATE myTable SET sync = 1 WHERE id_servizio = '" + idServizio + "'";