GROUP BY with CursorLoader - android

How do I define a GROUP BY query for my CursorLoader?
The two constructors for a CursorLoader I see take either a single Context or a Context, Uri, projection, selection, selectionArgs and sortOrder.
But no groupBy.
(I'm using the support package for a Android 2.3 device)

Not really...
You can define a specific URI to your specific GROUP BY clause.
For example, if you have a table mPersonTable, possibly grouped by gender, you can define the following URIs:
PERSON
PERSON/#
PERSON/GENDER
When querying, switch between your queries so you can add your group by parameter:
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String groupBy = null;
switch (mUriMatcher.match(uri)) {
case PERSON_ID:
...
break;
case PERSON_GENDER:
groupBy = GENDER_COLUMN;
case PERSON:
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(mPersonTable);
builder.setProjectionMap(mProjectionMap);
return builder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder, limit);
default:
break;
}
}
In fact, you could pass any sort of parameters to your query
Obs.: Use a UriMatcher to match the uri with your query implementation.

You can add Group by with selection parameter
new CursorLoader(context,URI,
projection,
selection+") GROUP BY (coloum_name",
null,null);

Apparently (and this is a bit embarrassing) the very first line in the documentation clearly states that the CursorLoader queries the ContentResolver to retrieve the Cursor. While the ContentResolver doesn't expose any means to GROUP BY there is, hence, no way the CursorLoader could expose such functionality either.
So the apparent answer to my very own question is: You can't!

Related

How to use selection args to query specific rows from a contentprovider in android

i have constructed a basic content provider that stores SMS messages for learning purposes, so far i can read(without selection args), insert, update and delete.
However i have been stumped trying to figure out how to format the selection args for the WHERE clause in my provider:
Basicly my application needs to search for a specific timestamp (in long format) and return its _id
say your database has an entry like this that your trying to access:
2|1|1410293471300|test type 1||testing|0
and the entire database looks like this:
_id|CLIENTTRXID|CREATED_AT|TYPE|MESSAGEPRIO|MESSAGE|ACCEPTED
1|1|1410293471000|test type 1||testing|0
2|1|1410293471300|test type 1||testing|0
3|1|1410293471600|test type 1||testing|0
in sql the query would be
"select _id from alerts where CREATED_AT=1410293471300;"
the code i was hoping would do the equivalent:
//normally i would get the string dynamically but to make it equal to the sql
String date = "1410293471300";
String[] selectionArgs = new String[]{ date };
Cursor cursor = getContext().getContentResolver().query(AlertContract.CONTENT_URI, null, AlertContract.Column.CREATED_AT, selectionArgs, AlertContract.DEFAULT_SORT);
seems to always produce the following error no matter what i try as selectionArgs
Exception caught﹕ Cannot bind argument at index 1 because the index is out of range. The statement has 0 parameters.
here is the query method of my contentprovider:
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables( AlertContract.TABLE);
switch (sURIMatcher.match(uri)) {
case AlertContract.STATUS_DIR:
break;
case AlertContract.STATUS_ITEM:
qb.appendWhere(AlertContract.Column.ID + "=" + uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException( "illegal uri: " + uri);
}
String orderBy = (TextUtils.isEmpty(sortOrder)) ? AlertContract.DEFAULT_SORT : sortOrder;
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
//register for uri changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
Log.d(TAG, "queried records: "+cursor.getCount());
return cursor;
}
Presumably im missing something extremely obvious, and will feel quite silly for having posted this question.
But for the moment i would very much appreciate any help, as i am quite stumped.
It looks like your issue is with your selection, rather than with your selectionArgs per se. The selection should be the whole query after the "where". Here your selection is "CREATED_AT". You need two more items to get it to work:
an =, since you want equality (you can also do other operators, of course)
a ?. This is where your selectionArgument will be inserted (each argument needs a ? in the selection, so there should be the same number of ?s in the selection as selectionArguments.
The end result should be more like "CREATED_AT = ?"
Check out the documentation and this tutorial for more info on how to correctly construct a ContentProvider query.
When you query the content provider, try the following. The selection should be AlertContract.Column.CREATED_AT + "=?"
Cursor cursor = getContext().getContentResolver().query(AlertContract.CONTENT_URI, null, AlertContract.Column.CREATED_AT + "=?", selectionArgs, AlertContract.DEFAULT_SORT);

Android - Requerying cursor ordered by RANDOM()

this may sound like a silly question but Im struggling with it. I am working on a quotes application therefore I want to have my quotes ordered from the database randomly each time the application is started.
I am using CursorLoader and a ViewPager. I am having some buttons (put-to-favourites-button) on the screen, which updates a column in database using content resolver which triggers update() in ContentProvider. Update then causes a requery in order to update the change made. All standard.
This of course causes to give me rows that are ordered differently because of the order by random() clause, and causes the screen to "disappear".
Any idea how to get around this? I really want to keep the random ordering functionality.
class MyContentProvider extends ContentProvider {
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(resolver, uri);
return cursor;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = db.update(TABLE_NAME, values, selection, selectionArgs);
resolver.notifyChange(uri, null);
return count;
}
}
you can use something like
Cursor c = db.rawQuery("select * from table ORDER BY RANDOM()");
SQLite docs
What I ended up doing, as zapl suggested, was to add a column. that would hold myOrder, which would be populated with random values in onCreate, and then just do a Select with order by myOrder

Is it a good practice to use rawQuery in ContentProvider?

I am using my custom ContentProvider to communicate with sqlite database. I would like to display on a list (using ListFragment), data that comes from two tables (with many to many relation). The only solution I can think of for such case is to use rawQuery. And the questions is, if it is a good practice, or should I solve this in some other way?
Example of tables:
Table A: ID, COLUMN_FROM_A
Table B: ID, COLUMN_FROM_B
Joining table AB: ID, FK_ID_A, FK_ID_B
Example of overridden query method in ContentProvider:
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
Cursor cursor = null;
int uriType = URIMatcher.match(uri);
switch (uriType) {
case TABLE_A_URI:
queryBuilder.setTables("TABLE_A");
cursor = queryBuilder.query(databaseHelper.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
break;
case TABLE_B_URI:
queryBuilder.setTables("TABLE_B");
cursor = queryBuilder.query(databaseHelper.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
break;
case TABLE_JOIN_A_B_URI:
cursor = databaseHelper.getReadableDatabase().rawQuery("select a.COLUMN_FORM_A, b.COLUMN_FROM_B from TABLE_A a, TABLE_B b, TABLE_AB ab where ab.FK_ID_A=a.ID and ab.FK_ID_B=b.ID", null);
break;
default:
throw new IllegalArgumentException("Unknown URI");
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
It's a good and common practice, very appropriate in this case.
I don't foresee any problems, we have used it in many apps.

CursorLoader with rawQuery

I'm looking into implementing CursorLoader in my app but I'm having a small issue that it seems that there isn't a way to just a pass a raw query to the CursorLoader constructor.
I maybe missing something in the documentation (and google), so if anybody can point me to a simple way to run a raw query with a CursorLoader class I would appreciate it. Otherwise I will have to probably create my own CursorLoader class with the necessary functionality, which I'm trying to avoid.
it seems that there isn't a way to just a pass a raw query to the CursorLoader constructor.
That is because CursorLoader works with content providers, and content providers do not support rawQuery().
so if anybody can point me to a simple way to run a raw query with a CursorLoader class I would appreciate it.
That is impossible, sorry. You are welcome to create your own AsyncTaskLoader that hits a SQLite database and supports rawQuery(). In fact, I will probably write one of these later this year, if I don't see where anyone has beaten me to it.
Raw query is not supported directly, but you can do a dirty hack: from your code call
getContentResolver().query(RAWQUERY_CONTENT_URI, null, rawquery, args, null);
and implement content provider like
#Override
public synchronized Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
int uriType = sURIMatcher.match(uri);
switch (uriType)
{
case RAW_QUERY:
return dbHelper.getReadableDatabase().rawQuery(selection, selectionArgs);
}
[...]
}
**For Custom Search using Content provider **
Change Cursor Loader as Follow (in onCreateLoader )
return new CursorLoader(
getActivity(), // Context
PRODUCT.CONTENT_URI, // URI
PROJECTION, // Projection
PRODUCT.PRODUCT_NAME+ " like ?", // Selection
new String[]{"%" + mCurFilter + "%"}, // Selection args
PRODUCT.PRODUCT_NAME + " asc");
In your Provider Change Accordingly
//C is Cursor object
switch (uriMatch) {
case ROUTE_PRODUCT_ID:
// Return a single entry, by ID.
String id = uri.getLastPathSegment();
builder.where(PRODUCT._ID + "=?", id);
c = builder.query(db, projection, sortOrder);
assert ctx != null;
c.setNotificationUri(ctx.getContentResolver(), uri);
return c;
// break;
case ROUTE_PRODUCT:
// Return all known entries.
builder.table(PRODUCT.PRODUCT_TABLE_NAME)
.where(selection, selectionArgs);
c = builder.query(db, projection, sortOrder);
assert ctx != null;
c.setNotificationUri(ctx.getContentResolver(), uri);
return c;
You can implement your own CursorLoader with raw query. This is the source of the original CursorLoader: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/CursorLoader.java

How do I get the _count in my content provider?

What should I do to get my content provider to return the _count column with the count of records? The documentation says it is automatic, but maybe it's only taking about some built-in content provider. Running a query to the database seems not to return it.
If you are using contentProvider then you have to do it like count(*) AS count.
If you use cursor.getCount(), that would not be as efficient as the above approach. With cursor.getCount() you are fetching all the records just to get counts. The entire code should look like following -
Cursor countCursor = getContentResolver().query(CONTENT_URI,
new String[] {"count(*) AS count"},
null,
null,
null);
countCursor.moveToFirst();
int count = countCursor.getInt(0);
The reason why this works is because android needs a column name to be defined.
If you are using ContentProvider.query() a Cursor is returned. Call Cursor.getCount() to get a count of records in the returned cursor.
I had a similiar problem and found this worked for me. In the example below I wanted to get the count of images from the MediaStore provider.
final String[] imageCountProjection = new String[] {
"count(" + MediaStore.Images.ImageColumns._ID + ")",
};
Cursor countCursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
imageCountProjection,
null,
null,
null);
countCursor.moveToFirst();
int existingImageCount = countCursor.getInt(0);
With cursor.getCount() you can not assure that it returns the real number of items returned. There are much better ways:
1- If you are using Content Providers, you can do a query and use the Column (_COUNT) included in BaseColumns for your projection
#Override
public Cursor query(SQLiteDatabase db, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
...
projection = new String[] {
ContentContract.NotificationCursor.NotificationColumns._COUNT,
};
...
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder);
return cursor;
}
2- To do a rawQuery using SELECT COUNT(*) as #saurabh says in his response.

Categories

Resources