22

I am having two tables with 1:n relationship, I am using content provider and cursorloader.

How would I make a join query to work with cursor loader? I could hack it up somehow with rawSql inside content provider but how to do it in cursor loader constructor is beyond me.

Thanks a lot !

CursorLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

How will query for join when Uri could only point to one table

Community
  • 1
  • 1
Abhishek Chauhan
  • 1,984
  • 16
  • 16

2 Answers2

32

The Uri does not point to any table. It points to whatever you feel like pointing it to.

Let's pretend that your two tables are Customer and Order. One customer may have many orders. You want to execute a query to get all outstanding orders... but you want to join in some customer-related columns that you will need, such as the customer's name.

Let's further pretend that you already have content://your.authority.goes.here/customer and content://your.authority.goes.here/order defined to purely query those tables.

You have two choices:

  1. Add the join of the customer's display name on your /order Uri. Having another available column probably will not break any existing consumers of the provider (though testing is always a good idea). This is what ContactsContract does -- it joins in some base columns, like the contact's name, on pretty much all queries of all tables.

  2. Create content://your.authority.goes.here/orderWithCust that does the same basic query as /order does, but contains your join. In this case, you could have insert(), update(), and delete() throw some sort of RuntimeException, to remind you that you should not be modifying data using /orderWithCust as a Uri.

In the end, designing a ContentProvider Uri system is similar to designing a REST Web service's URL system. In both cases, the join has to be done on the provider/server side, and so you may need to break the one-table-to-one-URL baseline to offer up some useful joins.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 1
    I read your answer above but in my case whole point of using join is to retrieve columns from multiple related table with just one query. inside a single cursor that i get from cursor loader i need DISPLAY_NAME_PRIMARY from ContactsContract.Contact table ,ACCOUNT_NAME from ContactsContract.RawContact table and DATA1 from ContactsContract.DATA table.Now i dont understand how single Uri can help me with this because if i give Uri pointing to ContactsContract.RawContact table then how will i access related columns of ContactsContract.Contact and ContactsContract.Data table – Abhishek Chauhan Dec 30 '12 at 13:29
  • 1
    @AbhishekChauhan: Well, in your case, you did not write the `ContentProvider`, which means that you cannot "hack it up somehow with rawSql inside content provider", as you claimed in your question. My answer is aimed at somebody who is writing the `ContentProvider` and therefore can do the join inside of it. In your case, you will have to manually "join" your data yourself from multiple `Cursor` objects, and the `Loader` framework will not help much in this area. Off the cuff, I'd use an `AsyncTask`, then do queries using `ContentResolver` and merge your data in `doInBackground()`, I guess. – CommonsWare Dec 30 '12 at 14:16
  • ya ... thanks i will use asynctask for it and merge later...this one will suit me better – Abhishek Chauhan Dec 30 '12 at 14:19
  • 2
    @CommonsWare: can you explain option one in some more detail with an example. thanks. – Shaheer Palollathil Aug 28 '16 at 20:31
4

I found a solution using sub-classes of ContentProvider. Let's say you have table tblA and another table tblB. I recommend creating two classes "AContentProvider" and "BContentProvider". Most importantly, make sure that both tables are set up in the same database.

The main part of the solution is to override ContentProvider.query() in the ContentProvider that you will call from your CursorLoader - the URI decides which one it is:

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) {
    SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

    qb.setTables(
            "tblA LEFT JOIN tblB"
                    + " ON ("
                    + "tblA.b_id"
                    + " = "
                    + "tblB.id"
                    + ")"
    );

    ...
    // Content of projection is set by CursorLoader
    // usually in an Activity that implements LoaderManager.LoaderCallbacks<>

    Cursor c = qb.query(
            database,
            projection,
            selection,
            selectionArgs,
            groupBy,
            having,
            orderBy
    );

    ...
    return c;
}

As you can see, the JOIN is done in setTables(). Using projection you make sure that you only display the columns you really need and most of all, you have no duplicate columns, like "id" from both tables:

final String[] projection = new String[] {
        "tblA.*",
        "tblB.columnThatOnlyBHas"
};

Make use of the override and try to get as much work done in the sub-classes as you can; for example: all my overridden query() methods call setNotificationUri() to notify the ContentResolver if the cursor result set changes.:

c.setNotificationUri(getContext().getContentResolver(), uri);
easytarget
  • 749
  • 9
  • 10