I'm trying to retrieve a single row from my database using SQLite but for some reason my program crashes. When I search the log I get the next error:
Index -1 is requested with size of 1
I searched the web for solutions but it looks like my code is correct. I can delete a row with that parameter so I know that the position is right. It's probably how I write the query but I just don't know what's wrong with it. Can someone see why I'm doing wrong?
This is the code for the query:
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + /jokes_table");
final Uri _URI = Uri.parse(MyContentProvider.CONTENT_URI + "/2");
String positions = intent.getStringExtra("position_in_db");
Cursor cur = getBaseContext().getContentResolver().query(_URI, new String[] {"Joke","Author","Date","Status"} , MyContentProvider.ID + " = " + intent.getStringExtra("position_in_db") , null , null );
my query method :
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String a;
switch (sUriMatcher.match(uri))
{
case COLLECTION_URI_INDICATOR:
qb.setTables(TABLE_NAME);
qb.setProjectionMap(projectionMap);
break;
case SINGLE_ITEM_URI_INDICATOR:
qb.setTables(TABLE_NAME);
qb.setProjectionMap(projectionMap);
qb.appendWhere(ID);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, null);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
When I try to get all the rows by that query it works fine. The only problem is that I can't retrieve specific row. I try to change the selection with ? and try to see the string before I call the query but it won't work. Trying to reach data from the cursor by
cur.getString(getColumnIndex("Joke"));
ends the program. Can someone help me please?
What is your selection and selectionArgs??
you can try this
selection = ROW_ID_COLUMN_NAME + " =? "; // take a note on the "Space" between this statement
selectionArgs = { ROW_ID };
c = qb.query(db, projection, selection, selectionArgs, null, null, null);
// moveToFirst() method may prevent error when accessing 0 row cursor.
if (c.moveToFirst()) {
c.getString(getColumnIndex("Joke"));
}
Related
I am having trouble querying the my SQLiteDatabase using my custom ContentProvider. I am trying to query the database for one user using the 'user_id'. The following pieces of code which are associated with the problem are:
DBProvider.java
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder){
int match_code = myUriMatcher.match(uri);
Cursor c;
switch (match_code){
case USER_BY_ID: {
c = dbHelper.getReadableDatabase().query(
DBContract.User_Table.TABLE_NAME,
projection,
DBContract.User_Table.COLUMN_ID + "='" + ContentUris.parseId(uri) + "'",
selectionArgs,
null,
null,
sortOrder
);
break;
}
default:
throw new UnsupportedOperationException("Not yet implemented");
}
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
ProfileActivity.java
// Columns to load
String[] columns = {
DBContract.User_Table.COLUMN_NAME // String "name"
};
// A cursor is your primary interface to the query results.
Cursor cursor = getActivity().getContentResolver().query(
ContentUris.withAppendedId(DBContract.User_Table.CONTENT_URI, Long.parseLong(id)), // Table to Query
columns, // Columns for the WHERE
DBContract.User_Table.COLUMN_ID, // selection
new String[]{"1"}, // Values for the WHERE
null // Sort Args
);
The error that I am getting is:
java.lang.IllegalArgumentException: Cannot bind argument at index 1 because the index is out of range. The statement has 0 parameters.
Any help would be greatly appreciated!
Thanks
You should use:
Cursor cursor = getActivity().getContentResolver().query(
ContentUris.withAppendedId(DBContract.User_Table.CONTENT_URI, Long.parseLong(id)), // Table to Query
columns, // Columns for the WHERE
DBContract.User_Table.COLUMN_ID, // selection
null, // Values for the WHERE
null // Sort Args
);
You are using the selectionArgs parameter wrong. You don't have any ? in the selection parameter and the binding fails. More info in the documentation.
selectionArgs - You may include ?s in selection, which will be
replaced by the values from selectionArgs, in the order that they
appear in the selection. The values will be bound as Strings.
I would like to display in the Log.d the current SQL query being generated by a CursorLoader in the onCreateLoader method such as:
return new CursorLoader(
getActivity(), // Parent activity context
WifiEntry.CONTENT_URI, // Provider content URI to query
projection, // Columns to include in the resulting Cursor
null, // No selection clause
null, // No selection arguments
WifiEntry.COLUMN_WIFI_NAME + " ASC");
I have looked at the Android docs, and in here, but no success. It is useful to debugging purposes to be able to visualize the SQL string.
Thanks!
UPDATE. I am adding the ContentProvider query method, anybody can answer how to display in the Log the resulting SQL query?
Thanks.
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
// Get readable database
SQLiteDatabase database = mDbHelper.getReadableDatabase();
// This cursor will hold the result of the query
Cursor cursor;
// Figure out if the URI matcher can match the URI to a specific code
int match = sUriMatcher.match(uri);
switch (match)
{
case WIFIS:
// For the WIFIS code, query the wifi table directly with the given
// projection, selection, selection arguments, and sort order. The cursor
// could contain multiple rows of the pets table.
cursor = database.query(WifiEntry.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case WIFI_ID:
// For the WIFI_ID code, extract out the ID from the URI.
// the selection will be "_id=?"
selection = WifiEntry._ID + "=?";
selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };
cursor = database.query(WifiEntry.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query, unknown URI " + uri);
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
// Return the cursor
return cursor;
}
I am trying to create a SQLite database for my Android app.
Everything worked fine until I got to the JUnit Testing for the query function in ContentProvider.
I read the forum very in depth, and saw that some people have the errors below
Create table has typos in it - here's my table creation statement
CREATE TABLE movie (
_id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
overview TEXT DEFAULT 'NO OVERVIEW',
poster_path TEXT DEFAULT 'NO POSTER',
release_date TEXT DEFAULT 'NO DATE AVAILABLE',
vote_average TEXT DEFAULT 'NO VOTES YET',
sort_type INTEGER NOT NULL,
favorite INTEGER DEFAULT 0
);
Not updated Database_Version constant once the column was added.
I tried updating the Database_Version constant and I also tried changing the name of the database, so it is created from scratch.
Deleted all of my old app from my Android device.
Read this post.
I did check for all of the nuances it speaks about.
However, I still have my exception being thrown
android.database.sqlite.SQLiteException: no such column: MovieContract.Movie.favorite (code 1): , while compiling: SELECT * FROM movie WHERE MovieContract.Movie.favorite = ? ORDER BY MovieContract.Movie.title
My testCase method that throws the error.
Error is being thrown on the line Cursor movieCursor...
public void testBasicMovieQuery(){
MovieDBHelper dbHelper = new MovieDBHelper(mContext);
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues movieValues = TestUtilities.createMovieValues();
long recordNum = db.insert(MovieContract.Movie.TABLE_NAME, null,movieValues);
assertTrue("Unable to Insert WeatherEntry into the Database", recordNum != -1);
db.close();
String selection = "MovieContract.Movie.FAVORITE = ?";
String [] selectionArgs = new String [] {"'1'"};
String sortOrder = "MovieContract.Movie.TITLE";
Cursor movieCursor = mContext.getContentResolver().query(
MovieContract.Movie.CONTENT_URI,
null,
selection,
selectionArgs,
sortOrder
);
TestUtilities.validateCursor("testBasicWeatherQuery", movieCursor, movieValues);
movieCursor.close();
}
Here is my query method in my ContentProvider; so when I have 'selection' defined it throws me the 'no such column' but if I put all null, besides the URI it will throw the Unknown Uri exception from the default, even though the Uri actually exists in UriMatcher.
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor;
Log.v("QUERY MovieProvider", uri.toString());
switch (uriMathcher.match(uri)){
case MOVIE_WITH_ID:{
Log.v("MovieProvider QUERY", "MOVIE WITH ID");
//cursor = getMovieWithId(uri);
cursor = dbHelper.getReadableDatabase().query(MovieContract.Movie.TABLE_NAME ,null, "MovieContract.Movie._ID =", selectionArgs,null,null,sortOrder);
}
break;
case MOVIE:{
Log.v("MovieProvider QUERY", "MOVIE");
//Log.v("MovieProvider QUERY", selection);
//Log.v("MovieProvider QUERY", selectionArgs[0]);
cursor = dbHelper.getReadableDatabase().query(MovieContract.Movie.TABLE_NAME, null,selection, selectionArgs, null, null, sortOrder);
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
Please let me know if any additional information is required.
My Github repository is here
Please change these lines:
String selection = "MovieContract.Movie.FAVORITE = ?";
String [] selectionArgs = new String [] {"'1'"};
String sortOrder = "MovieContract.Movie.TITLE";
to
String selection = MovieContract.Movie.FAVORITE + " = ?";
String [] selectionArgs = new String [] {"1"};
String sortOrder = MovieContract.Movie.TITLE;
or to (will work as well)
String selection = "favorite = ?";
String [] selectionArgs = new String [] {"1"};
String sortOrder = "title";
How do I run this query in a contentprovider?
SELECT * FROM sms_data WHERE number != (SELECT DISTINCT number from test) AND time > ?
First, the query is probably wrong. Instead of number != (...) you likely want number NOT IN (...).
Assuming test is a table in your app and not in the content provider, you can perform the query in two steps:
Subquery SELECT DISTINCT number FROM test. Build a comma-separated string such as '12345','23456','45678' from the results.
Do the content provider query with selection
number NOT IN (<that comma-separated list>) AND time > ?
For a subquery you can use rawQuery() or SQLiteQueryBuilder.
Overriding onQuery method of ContentProvider, I managed to put this bunch of code that works well for me.
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (URI_MATCHER.match(uri)) {
case TABLE1:
Cursor cursor = database.query("sms_data", projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
case TABLE2:
Cursor c = database.query(true, "test", projection, selection, selectionArgs, null, null, sortOrder, null);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
case COMBINED:
String sql = "SELECT " + appendColumns(projection) +
" FROM sms_data WHERE number != IFNULL((SELECT DISTINCT number FROM test), 'abc') AND " + selection;
Cursor cur = database.rawQuery(sql, selectionArgs);
cur.setNotificationUri(getContext().getContentResolver(), uri);
return cur;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri + " Match = " + URI_MATCHER.match(uri));
}
}
Hope this works for you too.
What would be the correct way to add DISTINCT and/or GROUPBY to ContentResolver-based queries?
Right now I have to create custom URI for each special case.
Is there a better way?
(I still program for 1.5 as lowest common denominator)
You can do nice hack when querying contentResolver, use:
String selection = Models.SOMETHING + "=" + something + ") GROUP BY (" + Models.TYPE;
If you want to use DISTINCT with SELECT more then one column, You need to use GROUP BY.
Mini Hack over ContentResolver.query for use this:
Uri uri = Uri.parse("content://sms/inbox");
Cursor c = getContentResolver().query(uri,
new String[]{"DISTINCT address","body"}, //DISTINCT
"address IS NOT NULL) GROUP BY (address", //GROUP BY
null, null);
if(c.moveToFirst()){
do{
Log.v("from", "\""+c.getString(c.getColumnIndex("address"))+"\"");
Log.v("text", "\""+c.getString(c.getColumnIndex("body"))+"\"");
} while(c.moveToNext());
}
This code select one last sms for each of senders from device inbox.
Note: before GROUP BY we always need to write at least one condition.
Result SQL query string inside ContentResolver.query method will:
SELECT DISTINCT address, body FROM sms WHERE (type=1) AND (address IS NOT NULL) GROUP BY (address)
Since no one came to answer I'm just going to tell how I solved this. Basically I would create custom URI for each case and pass the criteria in selection parameter. Then inside ContentProvider#query I would identify the case and construct raw query based on table name and selection parameter.
Here's quick example:
switch (URI_MATCHER.match(uri)) {
case TYPES:
table = TYPES_TABLE;
break;
case TYPES_DISTINCT:
return db.rawQuery("SELECT DISTINCT type FROM types", null);
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
return db.query(table, null, selection, selectionArgs, null, null, null);
In your overridden ContentProvider query method have a specific URI mapping to using distinct.
Then use SQLiteQueryBuilder and call the setDistinct(boolean) method.
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
boolean useDistinct = false;
switch (sUriMatcher.match(uri))
{
case YOUR_URI_DISTINCT:
useDistinct = true;
case YOUR_URI:
qb.setTables(YOUR_TABLE_NAME);
qb.setProjectionMap(sYourProjectionMap);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// If no sort order is specified use the default
String orderBy;
if (TextUtils.isEmpty(sortOrder))
{
orderBy = DEFAULT_SORT_ORDER;
}
else
{
orderBy = sortOrder;
}
// Get the database and run the query
SQLiteDatabase db = mDBHelper.getReadableDatabase();
// THIS IS THE IMPORTANT PART!
qb.setDistinct(useDistinct);
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
if (c != null)
{
// Tell the cursor what uri to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
Though I have not used Group By, I have used Distinct in content resolver query.
Cursor cursor = contentResolver
.query(YOUR_URI,
new String[] {"Distinct "+ YOUR_COLUMN_NAME},
null,
null, null);
Adding the Distinct keyword in the projection worked for me too, however, it only worked when the distinct keyword was the first argument:
String[] projection = new String[]{"DISTINCT " + DBConstants.COLUMN_UUID, ... };
In some condition, we can use "distinct(COLUMN_NAME)" as the selection,
and it work perfect.
but in some condition, it will cause a exception.
when it cause a exception, i will use a HashSet to store the column values....
// getting sender list from messages into spinner View
Spinner phoneListView = (Spinner) findViewById(R.id.phone_list);
Uri uri = Uri.parse("content://sms/inbox");
Cursor c = getContentResolver().query(uri, new String[]{"Distinct address"}, null, null, null);
List <String> list;
list= new ArrayList<String>();
list.clear();
int msgCount=c.getCount();
if(c.moveToFirst()) {
for(int ii=0; ii < msgCount; ii++) {
list.add(c.getString(c.getColumnIndexOrThrow("address")).toString());
c.moveToNext();
}
}
phoneListView.setAdapter(new ArrayAdapter<String>(BankActivity.this, android.R.layout.simple_dropdown_item_1line, list));
When you have multiple columns in your projection you should do like this:
val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
"DISTINCT " + MediaStore.Images.Media.BUCKET_ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
MediaStore.Images.Media.BUCKET_ID,
MediaStore.MediaColumns.DATA
)
val groupBySelection = " 1) GROUP BY (${MediaStore.Images.Media.BUCKET_ID}"
contentResolver.query(
uri,
projection,
null,
groupBySelection,
null,
null
)
groupBySelection with closing bracket and number "1" inside is a tiny hack, but it works absolutely fine
I created a utility method for using group by and distinct.
Usage
Here is an example of selecting unseen thread_id with the last message date from the MMS database.
query(contentResolver= contentResolver,
select = arrayOf(Mms.THREAD_ID, "max(${Mms.DATE}) as date"),
from = Mms.CONTENT_URI,
where = "${Mms.SEEN} = 0",
groupBy = "1",
orderBy = "2 desc"
).use {
while (it?.moveToNext() == true){
val threadId = it.getInt(0)
val date = it.getLong(1)
}
}
Source
fun query(
contentResolver: ContentResolver,
from: Uri,
select: Array<String>,
where: String? = null,
groupBy: Array<out String>? = null,
distinct: Boolean = false,
selectionArgs: Array<out String>? = null,
orderBy: String? = null,
): Cursor? {
val tmpSelect = select[0]
val localWhere =
if (groupBy == null) where
else "${where ?: "1"}) group by (${groupBy.joinToString()}"
if (distinct) {
select[0] = "distinct $tmpSelect"
}
val query = contentResolver.query(from, select, localWhere, selectionArgs, orderBy)
select[0] = tmpSelect
return query
}
Maybe its more simple to get distinct values,
try to add the DISTINCT word before the column name you want into the projection table
String[] projection = new String[]{
BaseColumns._ID,
"DISTINCT "+ Mediastore.anything.you.want
};
and use it as an argument to query method of the content resolver!
I hope to help you, cause I have the same question before some days