I'm making an Android app and using a SQLite database. In particular I'm using the rawQuery method on a database obtained through a SQLiteOpenHelper. The query I build makes use of the ? marks as placeholders for the real values, which are passed along as an array of objects (e.g., select * from table where id = ?).
The question is, is it possible to get the query with the marks already replaced, at least from the cursor returned from the rawQuery method? I mean something like select * from table where id = 56. This would be useful for debugging purposes.
It's not possible. The ? values are not bound at the SQL level but deeper, and there's no "result" SQL after binding the values.
Variable binding is a part of the sqlite3 C API, and the Android SQLite APIs just provide a thin wrapper on top. http://sqlite.org/c3ref/bind_blob.html
For debugging purposes you can log your SQL with the ?, and log the values of your bind arguments.
You could form it as a string like this
int id = 56;
String query = "select * from table where id = '" + id + "'";
and then use it as a rawQuery like this (if I understood your question properly)
Cursor mCursor = mDb.rawQuery(query, null);
You can also use the SQLiteQueryBuilder. Here is an example with a join query:
//Create new querybuilder
SQLiteQueryBuilder _QB = new SQLiteQueryBuilder();
//Specify books table and add join to categories table (use full_id for joining categories table)
_QB.setTables(BookColumns.TABLENAME +
" LEFT OUTER JOIN " + CategoryColumns.TABLENAME + " ON " +
BookColumns.CATEGORY + " = " + CategoryColumns.FULL_ID);
//Order by records by title
_OrderBy = BookColumns.BOOK_TITLE + " ASC";
//Open database connection
SQLiteDatabase _DB = fDatabaseHelper.getReadableDatabase();
//Get cursor
Cursor _Result = _QB.query(_DB, null, null, null, null, null, _OrderBy);
Related
I'm a newbie with Android Studio so please be patient... This forum often leads me with suggestions and examples (as a reader), but today I decided to ask for help:
Since hours, I try to build an SQLite statement in Android Studio: There is a column COLUMN_LAST_ATTEMPT with date and time as String, e.g. 2020-01-09 17:23, see screenshot, and I want to get the newest date (without time) from the table, e.g. 2020-09-01. I tried various options but I can't get it to run.
What I need is an Android SQLite Statement for
SELECT MAX(SUBSTR(last_attempt,11,20)) FROM quiz_questions
(which runs on DBBrowser), where 'last attempt' is a column of table 'quiz_questions', screenshot of that column in table 'quiz_questions'
I tried the following rawQueries, none of them works:
In QuizDBHelper-Class
//...
final QuizDbHelper dbHelper = QuizDbHelper.getInstance(this);
//...
public String newestQuiz(){
db = getReadableDatabase();
String result = null;
Cursor cursor = db.rawQuery("SELECT MAX(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ") FROM "
+ QuizContract.QuestionsTable.TABLE_NAME, null);
//Cursor cursor = db.rawQuery("SELECT MAX(SUBSTR(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT +
// ",11,20)) FROM " + QuizContract.QuestionsTable.TABLE_NAME, null);
//Cursor cursor = db.rawQuery("SELECT " + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + " FROM " +
// QuizContract.QuestionsTable.TABLE_NAME, null);
if(cursor.moveToFirst()){
do {
result = cursor.getString(c.getColumnIndex(QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT));
} while (cursor.moveToNext());
}
cursor.close();
return result;
}
In Statistics-Class
String LastUse = dbHelper.newestQuiz();
LastUsage.setText("Letzte Challenge: " + LastUse);
//LastUsage is a TextView in activity_Statistics.xml
//attached with LastUsage = findViewById(R.id.text_lastUsage);
Either the SQLite statements are totally wrong or I make (basic?) mistakes in statistics class. I need ...newbie help!
I need something like Select column from table where substring of date-Entry == newest
Your issue appear to be column names. That is a Cursor only contains the columns extracted, not all the columns from the table. Although you are basing your query on the column as per QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT that will not be the column name in the cursor.
Rather it will will MAX(SUBSTR(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT +
// ",11,20))
The simplest way of managing this is to give the column in the Cursor a specific name using AS. As such perhaps use :-
Cursor cursor = db.rawQuery("SELECT MAX(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ") AS " + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + " FROM "
+ QuizContract.QuestionsTable.TABLE_NAME, null);
However, you may prefere to use a column name (AS ????) specififc to the situation e.g.
........ AS max_" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ........
You would then have to use :-
result = cursor.getString(c.getColumnIndex("max_" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT));
Alternately, as it's just a single value/column that is returned in the cursor you could use the column offset of 0, in which case the column name is irrelevant as long as it is valid. However, using offsets is not typically recommended due to the lack of validation of the column being accessed.
re the comment :-
I just need the date part
As the date is a recognised DateTime format (and also that such formats are directly sortable/orderable), use max(date(column_name)) or even max(column_name).
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).
Recently I got to know that raw query in android can not prevent SQL injection and thus I decided to convert all queries in Prepared statement which is SQL injection prevention. But I don't know how to convert complex queries in Prepared Statement.
I want to convert below queries:
1.
select
*
FROM
TableName
where
(tab1col1 in(SELECT tab2Col2 FROM MasterTable where tab2col1='Y')
or tab1col2 = CV.TRUE)
order by
tab1col3, tab1col4, tab1col5,tab1col6
2.
Select
* ,count(*) as TOTAL_COUNT ,
SUM(CASE WHEN tabCol1 LIKE '%todayDate%' THEN 1 ELSE 0 END) as TOTAL_COL1_COUNT
from
TableName
group by tabCol2;
You can use rawQuery to prevent injection by passing any arguments via the selectionargs (2nd parameter).
SQL injection, wouldn't apply to either of the queries, as they are hard coded and have no user generated/supplied inputs.
e.g. your first query could be (assuming that, 'Y' and CV.TRUE are passed as parameters (i.e. user generated/supplied) for the sake of demonstration) :-
public Cursor query1raw(String indicator1,String indicator2) {
String sql = "SELECT * " +
" FROM TableName " +
" WHERE (tab1col1" +
" IN(" +
" SELECT tab2col2 " +
" FROM MasterTable " +
" WHERE tab2col1=?)" +
" OR tab1col2=?)" +
" ORDER BY tab1col3, tab1col4,tab1col5,tab1col6";
String[] args = new String[]{indicator1,indicator2};
return mDB.rawQuery(sql,args);
}
However, the convenience methods are generally recommended rather than rawQuery or execSQL when they can be used, again using bound strings via arguments, the above, using the query convenience method could be :-
public Cursor query1(String indicator1, String indicator2) {
String whereclause = "(tab1col1 IN(SELECT tab2col2 FROM MasterTable WHERE tab2col1=?) OR tab1col2=?)";
String[] whereargs = new String[] {indicator1,indicator2};
String order_columns = "tab1col3,tab1col4,tab1col5,tab1col6";
return mDB.query("TableName",null,whereclause,whereargs,null,null,order_columns);
}
You wouldn't use prepared statements themselves as they are restricted to returning single values, not a row or rows with multiple columns.
Warning not advised
However, you could, if you really wanted, use :-
public Cursor query1ps(String indicator1,String indicator2) {
String[] whereargs = new String[] {indicator1,indicator2};
SQLiteStatement stmnt = mDB.compileStatement("SELECT * " +
" FROM TableName " +
" WHERE (tab1col1" +
" IN(" +
" SELECT tab2col2 " +
" FROM MasterTable " +
" WHERE tab2col1=?)" +
" OR tab1col2=?)" +
" ORDER BY tab1col3, tab1col4,tab1col5,tab1col6");
stmnt.bindAllArgsAsStrings(whereargs);
Log.d("PREPAREDSQL",stmnt.toString());
String sql = stmnt.toString().replace("SQLiteProgram:","");
return mDB.rawQuery(sql,null);
}
As you can see all the prepared statement is doing as such, is substituting the arguments, so has little benefit over the other methods. This would also be dependant upon SQLIteProgram: remaining constant.
The only way to prevent SQL injections is to use parameters. (In some PHP APIs, the only way to get parameters is to use prepared statements, but that is not one of the warts in the Android database API.)
Just write ? for any string, and pass the values separately:
String name = ...;
String password = ...;
cursor = db.rawQuery("SELECT SomeCol FROM Users WHERE Name = ? AND Password = ?",
new String[]{ name, password });
Please not that SQL injection could happen only if you have string values that are controlled by the (potentially-hostile) user. Your queries above do not look as if this were the case.
Hi to All I am new to Android.
I am using SQLite DataBase in my Application
meanwhile I am Written Queries using +
Like delete from tablename where value = + value;
this is my query
String delete_query = "delete from " + tableName
+ " where title = '" + title + "'";
database.execSQL(delete_query);
I want to write this Query using placeholder ?.
so that i tried
database.delete(tableName, title + "?" , new String[] {title});
instead "?" i tried (?)/('?')/'?'
but it is giving me an error....
can any one tell me how to write appropriate query using ?.....
Thanks in Advance.
Mahaveer
Make sure you have put the equal sign:-
database.delete(tableName, title + "=?" , new String[] {title});
As far as possible, try to use the less raw queries you can. Two advantages:
Query parameters will be escaped by the system (protection against SQL injection)
The code will be more readable
See the delete function of SQLiteDatabase class
public int delete (String table, String whereClause, String[]
whereArgs)
Convenience method for deleting rows in the
database.
table the table to delete from
whereClause the optional WHERE clause
to apply when deleting. Passing null will delete all rows.
Returns the number of rows affected if a whereClause is passed in, 0
otherwise. To remove all rows and get a count pass "1" as the
whereClause.
In your case:
final String where = "title=?";
final String[] args = new String[] { title };
database.delete(tableName, where, args);
Background
I have an Android project that has a database with two tables: tbl_question and tbl_alternative.
To populate the views with questions and alternatives I am using cursors. There are no problems in getting the data I need until I try to join the two tables.
Tbl_question
-------------
_id
question
categoryid
Tbl_alternative
---------------
_id
questionid
categoryid
alternative
I want something like the following:
SELECT tbl_question.question, tbl_alternative.alternative where
categoryid=tbl_alternative.categoryid AND tbl_question._id =
tbl_alternative.questionid.`
This is my attempt:
public Cursor getAlternative(long categoryid) {
String[] columns = new String[] { KEY_Q_ID, KEY_IMAGE, KEY_QUESTION, KEY_ALT, KEY_QID};
String whereClause = KEY_CATEGORYID + "=" + categoryid +" AND "+ KEY_Q_ID +"="+ KEY_QID;
Cursor cursor = mDb.query(true, DBTABLE_QUESTION + " INNER JOIN "+ DBTABLE_ALTERNATIVE, columns, whereClause, null, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
}
return cursor;
I find this way to form queries harder than regular SQL, but have gotten the advice to use this way since it is less error prone.
Question
How do I join two SQLite tables in my application?
You need rawQuery method.
Example:
private final String MY_QUERY = "SELECT * FROM table_a a INNER JOIN table_b b ON a.id=b.other_id WHERE b.property_id=?";
db.rawQuery(MY_QUERY, new String[]{String.valueOf(propertyId)});
Use ? bindings instead of putting values into raw sql query.
An alternate way is to construct a view which is then queried just like a table.
In many database managers using a view can result in better performance.
CREATE VIEW xyz SELECT q.question, a.alternative
FROM tbl_question AS q, tbl_alternative AS a
WHERE q.categoryid = a.categoryid
AND q._id = a.questionid;
This is from memory so there may be some syntactic issues.
http://www.sqlite.org/lang_createview.html
I mention this approach because then you can use SQLiteQueryBuilder with the view as you implied that it was preferred.
In addition to #pawelzieba's answer, which definitely is correct, to join two tables, while you can use an INNER JOIN like this
SELECT * FROM expense INNER JOIN refuel
ON exp_id = expense_id
WHERE refuel_id = 1
via raw query like this -
String rawQuery = "SELECT * FROM " + RefuelTable.TABLE_NAME + " INNER JOIN " + ExpenseTable.TABLE_NAME
+ " ON " + RefuelTable.EXP_ID + " = " + ExpenseTable.ID
+ " WHERE " + RefuelTable.ID + " = " + id;
Cursor c = db.rawQuery(
rawQuery,
null
);
because of SQLite's backward compatible support of the primitive way of querying, we turn that command into this -
SELECT *
FROM expense, refuel
WHERE exp_id = expense_id AND refuel_id = 1
and hence be able to take advanatage of the SQLiteDatabase.query() helper method
Cursor c = db.query(
RefuelTable.TABLE_NAME + " , " + ExpenseTable.TABLE_NAME,
Utils.concat(RefuelTable.PROJECTION, ExpenseTable.PROJECTION),
RefuelTable.EXP_ID + " = " + ExpenseTable.ID + " AND " + RefuelTable.ID + " = " + id,
null,
null,
null,
null
);
For a detailed blog post check this
http://blog.championswimmer.in/2015/12/doing-a-table-join-in-android-without-using-rawquery
"Ambiguous column" usually means that the same column name appears in at least two tables; the database engine can't tell which one you want. Use full table names or table aliases to remove the ambiguity.
Here's an example I happened to have in my editor. It's from someone else's problem, but should make sense anyway.
select P.*
from product_has_image P
inner join highest_priority_images H
on (H.id_product = P.id_product and H.priority = p.priority)