Как реализовать сложные запросы с помощью Content Provider?

Я спрашиваю об этом, потому что я не совсем уверен, как работать с контентными провайдерами Android. У меня есть подмножество моей базы данных с 8 таблицами, и мне нужно создать сложные запросы для получения некоторых данных. Мой контент-провайдер отлично работает с простыми запросами. Например, у меня есть стол Person в моем классе PersonModel.java, и я получаю данные с помощью:

String [] projection = {PersonModel.C_FIRST_NAME, PersonModel.C_LAST_NAME};
Cursor cursor = context.getContentResolver().query(
            MyProvider.CONTENT_URI_PERSONS, projection, null,
            null, null);

и он отлично работает.

В MyProvider У меня есть куча констант CONTENT_URI, для каждой из моих таблиц.

public class MyProvider extends ContentProvider {
    MyDbHelper dbHelper;
    SQLiteDatabase db;


    private static final String AUTHORITY = "com.myapp.models";

    //Paths for each tables
    private static final String PATH_PROFILE_PICTURES = "profile_pictures";
    private static final String PATH_PERSONS = "persons";
    private static final String PATH_USERS = "users";
    ....

    //Content URIs for each table
    public static final Uri CONTENT_URI_PROFILE_PICTURES = Uri
        .parse("content://" + AUTHORITY + "/" + PATH_PROFILE_PICTURES);
    public static final Uri CONTENT_URI_PERSONS = Uri.parse("content://"
        + AUTHORITY + "/" + PATH_PERSONS);
    public static final Uri CONTENT_URI_USERS = Uri.parse("content://"
        + AUTHORITY + "/" + PATH_USERS);
    ...


    private static final int PROFILE_PICTURES = 1;
    private static final int PROFILE_PICTURE_ID = 2;
    private static final int PERSONS = 3;
    private static final int PERSON_ID = 4;
    private static final int USERS = 5;
    private static final int USER_ID = 6;

    private static final UriMatcher sURIMatcher = new UriMatcher(
        UriMatcher.NO_MATCH);
    static {
    sURIMatcher.addURI(AUTHORITY, PATH_PROFILE_PICTURES, PROFILE_PICTURES);
    sURIMatcher.addURI(AUTHORITY, PATH_PROFILE_PICTURES + "/#",
            PROFILE_PICTURE_ID);
    sURIMatcher.addURI(AUTHORITY, PATH_PERSONS, PERSONS);
    sURIMatcher.addURI(AUTHORITY, PATH_PERSONS + "/#", PERSON_ID);
    sURIMatcher.addURI(AUTHORITY, PATH_USERS, USERS);
    sURIMatcher.addURI(AUTHORITY, PATH_USERS + "/#", USER_ID);
    ...
    }



  public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {

    // Uisng SQLiteQueryBuilder instead of query() method
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

    // check if the caller has requested a column which does not exists
    //checkColumns(projection);

    int uriType = sURIMatcher.match(uri);

    switch (uriType) {
    case PROFILE_PICTURES:
        queryBuilder.setTables(ProfilePictureModel.TABLE_PROFILE_PICTURE);
        break;
    case PROFILE_PICTURE_ID:
        // Adding the ID to the original query
        queryBuilder.appendWhere(ProfilePictureModel.C_ID + "="
                + uri.getLastPathSegment());
    case PERSONS:
        queryBuilder.setTables(PersonModel.TABLE_PERSON);
        break;
    case PERSON_ID:
        // Adding the ID to the original query
        queryBuilder.appendWhere(PersonModel.C_ID + "="
                + uri.getLastPathSegment());
    case USERS:
        queryBuilder.setTables(UserModel.TABLE_USER);
        break;
    case USER_ID:
        // Adding the ID to the original query
        queryBuilder.appendWhere(UserModel.C_ID + "="
                + uri.getLastPathSegment());

    default:
        throw new IllegalArgumentException("Unknown URI: " + uri);

    }

    db = dbHelper.getWritableDatabase();
    Cursor cursor = queryBuilder.query(db, projection, selection,
            selectionArgs, null, null, sortOrder);
    // make sure that potential listeners are getting notified
    cursor.setNotificationUri(getContext().getContentResolver(), uri);

 }

Это небольшая часть моего поставщика контента. Поэтому мои вопросы:

1) Как реализовать rawQuery() в моем контент-провайдере? или как я правильно использую свой запросBuilder?, скажите, что я хочу выполнить этот запрос, используя несколько таблиц, переименовав их, а также передав p1.id в качестве параметра?

SELECT p1.first_name, p1_last_name 
FROM Person p1, Person P2, Relationship r 
WHERE p1.id = ? AND 
      p1.id = r.relative_id AND
      p2.id = r.related_id;

Я попытался сделать это, выполнив следующее: по моему методу query() (показано выше) у меня есть новый случай, называемый GET_RELATIVES:

case GET_RELATIVES:
        db = dbHelper.getWritableDatabase();
        queryBuilder.setTables(PersonModel.TABLE_PERSON + " p1, "
                + PersonModel.TABLE_PERSON + " p2, "
                + RelationshipModel.TABLE_RELATIONSHIP + " r");
        queryBuilder.appendWhere("p2."+PersonModel.C_ID + "=" + uri.getLastPathSegment());
        queryBuilder.appendWhere("p2."+PersonModel.C_ID + "=" + "r.related_id");
        queryBuilder.appendWhere("p1."+PersonModel.C_ID + "=" + "r.relative_id");

поэтому я определил новый PATH, CONTENT URI и добавлю его в UriMatcher, например:

private static final String PATH_GET_RELATIVES = "get_relatives";
public static final Uri CONTENT_URI_GET_RELATIVES = Uri
        .parse("content://" + AUTHORITY + "/"
                + PATH_GET_RELATIVES);
private static final int GET_RELATIVES = 22;

private static final UriMatcher sURIMatcher = new UriMatcher(
    UriMatcher.NO_MATCH);
static {
...
sURIMatcher.addURI(AUTHORITY, PATH_GET_RELATIVES, GET_RELATIVES);
}

но это, похоже, не работает, поэтому я думаю, что я, вероятно, определяю что-то не так на моем поставщике контента или внутри метода запроса.

2) Я не совсем уверен, в чем смысл иметь для каждой таблицы константу, называемую TABLE_ID, и добавление ее в папку switch. Для чего это используется? Как мне это назвать?

Надеюсь, что кто-нибудь сможет мне помочь, спасибо заранее!

Ответ 1

Я действительно нашел ответ на свой вопрос в самом очевидном месте: документация по android.

Первый вопрос: выполнить rawQuery. Было ли это так:

Внутри моего коммутатора в поставщике контента я добавил новый URI, который для меня представляет собой JOIN между таблицами, поэтому я создал для него новую константу ContentUri, новый идентификатор и зарегистрировал его на UriMatcher и затем написал rawQuery. Итак, MyProvider теперь выглядит так:

public class MyProvider extends ContentProvider {
...
// JOIN paths
    private static final String PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = 
            "relationship_join_person_get_relatives";
...
public static final Uri CONTENT_URI_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = Uri
            .parse("content://" + AUTHORITY + "/"
                    + PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES);
...
    private static final int RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = 21;
private static final UriMatcher sURIMatcher = new UriMatcher(
            UriMatcher.NO_MATCH);
    static {
...
//JOINS
        sURIMatcher.addURI(AUTHORITY, PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES + "/#",
                RELATIONSHIP_JOIN_PERSON_GET_RELATIVES);
...

public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {

        // Uisng SQLiteQueryBuilder instead of query() method
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

        // check if the caller has requested a column which does not exists
        //checkColumns(projection);

        int uriType = sURIMatcher.match(uri);

        switch (uriType) {
        ...
        case RELATIONSHIP_JOIN_PERSON_GET_RELATIVES:
            db = dbHelper.getWritableDatabase();
            String[] args = {String.valueOf(uri.getLastPathSegment())};
            Cursor cursor = db.rawQuery(
                    "SELECT p1.first_name, p1.last_name " +
                    "FROM Person p1, Person p2, Relationship r " +
                    "WHERE p1.id = r.relative_id AND " +
                    "p2.id = r.related_id AND " + 
                    "p2.id = ?", args);
            cursor.setNotificationUri(getContext().getContentResolver(), uri);
            return cursor;
        ...
}

И чтобы вызвать метод query() и передать id ad параметр, я сделал это в своем контроллере:

String[] projection = { PersonModel.C_FIRST_NAME,
                PersonModel.C_LAST_NAME };
        Cursor cursor = context.getContentResolver().query(
                ContentUris.withAppendedId(
                        AkdemiaProvider.CONTENT_URI_RELATIONSHIP_JOIN_PERSON_GET_RELATED, id), 
                        projection, null, null, null);

Второй вопрос: наличие константы TABLE_ID полезно для запроса каждой таблицы, передающей идентификатор в качестве параметра, я не знал, как вызвать метод запроса, передающий такой идентификатор, и именно так Документация разработчика Android объясняет, как для этого используйте ContentUris.withAppendedId

// Request a specific record.


Cursor managedCursor = managedQuery(
                ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2),
                projection,    // Which columns to return.
                null,          // WHERE clause.
                null,          // WHERE clause value substitution
                People.NAME + " ASC");   // Sort order.

Я, ребята, хочу, чтобы вся документация переходила к этой ссылке.

Надеюсь, это поможет кому-то еще с той же проблемой понять ContentProvider, ContentUris и все такое.

Ответ 2

Ниже код работал у меня. Внутри поставщика приложений:

public static final String PATH_JOIN_TWO_TABLES = "my_path";

    public static final Uri URI_JOIN_TWO_TABLES =
            Uri.parse("content://" + AUTHORITY + "/" + PATH_JOIN_TWO_TABLES);

    private static final int ID_JOIN_TWO_TABLES = 1001;

    private static final UriMatcher sURIMatcher = new UriMatcher(
            UriMatcher.NO_MATCH);

    static {
        sURIMatcher.addURI(AUTHORITY,
                PATH_JOIN_TWO_TABLES + "/#", ID_JOIN_TWO_TABLES );
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection,String[] selectionArgs,
                        String sortOrder, CancellationSignal cancellationSignal) {

        int uriType = sURIMatcher.match(uri);
            switch (uriType) {

                case ID_JOIN_TWO_TABLES:
                    return getWritableDatabase()
                            .rawQuery("select * from " +
                                    "table_one" + " LEFT OUTER JOIN "
                                    + "table_two" + " ON ("
                                    + "table_one.ID"
                                    + " = " + "table_two.id" + ")", null);
            }
        return super.query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal);
    }

И при выполнении запроса внутри вашей активности или фрагмента:

 Cursor cursor = getActivity().getContentResolver()
                .query(ContentUris.withAppendedId(MYContentProvider.URI_JOIN_TWO_TABLES, MyContentProvider.ID_JOIN_TWO_TABLES), null, null, null, null);

Надеюсь, что это сработает для вас.

Ответ 3

Для простых запросов используйте selectionArgs в ContentProvider. Он работает, как показано ниже

String[] args = { "first string", "[email protected]" };
Cursor cursor = db.query("TABLE_NAME", null, "name=? AND email=?", args, null);

Наличие TABLE_ID внутри, чтобы создать разные запросы для каждой таблицы.

Обратитесь к следующему классу для всех нескольких таблиц в поставщиках контента