Android: Distinct и GroupBy в ContentResolver

Как правильно добавить DISTINCT и/или GROUPBY к запросам на основе ContentResolver?

Сейчас мне нужно создать собственный URI для каждого особого случая.

Есть ли способ лучше?

(Я все еще программирую 1,5 как наименьший общий знаменатель)


person Bostone    schedule 22.02.2010    source источник


Ответы (11)


Вы можете сделать хороший взлом при запросе contentResolver, используя:

String selection = Models.SOMETHING + "=" + something + ") GROUP BY (" + Models.TYPE;
person kzotin    schedule 17.12.2010
comment
Хороший хак, если у вас нет доступа к источнику ContentProvider. Спасибо. - person Henry Pootle; 17.02.2011
comment
извините, это моя вина, я использовал GROUP BY со столбцом, с которым я хотел DISTINCT, я плохо разбираюсь в SQL - person Amal; 11.06.2011
comment
это больше не работает. есть ли способ выбрать отдельные в ICS? - person Alex Burdusel; 25.03.2013
comment
Какой ужасный провал от имени дизайнеров :) У меня работает - min sdk 15, target sdk 19. Одно примечание: я добавил условие наличия. - person Peri Hartman; 21.03.2014

Если вы хотите использовать DISTINCT с SELECT более чем для одного столбца, вам нужно использовать GROUP BY.
Мини-хак над ContentResolver.query для использования этого:

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());
        }

Этот код выбирает одно последнее смс для каждого из отправителей из почтового ящика устройства.
Примечание: перед GROUP BY всегда нужно писать хотя бы одно условие. Результирующая строка SQL-запроса внутри метода ContentResolver.query будет:

SELECT DISTINCT address, body FROM sms WHERE (type=1) AND (address IS NOT NULL) GROUP BY (address) 
person P-A    schedule 30.04.2013
comment
Спасибо! Вы спасли мою жизнь :) - person Elnatan Derech; 11.04.2016
comment
Примечание. Столбец с определенным значением должен быть в начале проекционного массива. - person nimelorm; 09.10.2018

Поскольку никто не ответил, я просто расскажу, как я это решил. По сути, я бы создал собственный URI для каждого случая и передал бы критерии в параметре selection. Затем внутри ContentProvider#query я бы определил случай и построил необработанный запрос на основе имени таблицы и параметра выбора.

Вот быстрый пример:

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);
person Bostone    schedule 27.02.2010
comment
@ njzk2 njzk2 Если я вас правильно понял, вы говорите, что можно получить экземпляр реальной базы данных контактов? - person Ilya Gazman; 25.02.2017
comment
@Ilya_Gazman нет, я говорю, что это решение работает, только если вы реализуете поставщика контента. - person njzk2; 26.02.2017
comment
@ njzk2 извините за невезение, но для реализации контент-провайдера вам нужно написать ром костюма, верно? Это как часть базовой системы Android, верно? - person Ilya Gazman; 26.02.2017
comment
@Ilya_Gazman здесь мы говорим о контент-провайдерах в целом. Если у вас есть конкретный вопрос о контактах, напишите новый вопрос. - person njzk2; 26.02.2017

В вашем переопределенном методе запроса ContentProvider есть определенное сопоставление URI с использованием отдельного.

Затем используйте SQLiteQueryBuilder и вызовите метод setDistinct(boolean).

@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;
}
person Brady Kroupa    schedule 20.12.2010
comment
что такое mDBHelper? - person Hamidreza Sadegh; 27.02.2015

Хотя я не использовал Group By, я использовал Distinct в запросе преобразователя контента.

Cursor cursor = contentResolver .query(YOUR_URI, new String[] {"Distinct "+ YOUR_COLUMN_NAME}, null, null, null);

person W00di    schedule 20.08.2015
comment
Это нормально, если вам нужен только один столбец данных, но если вам нужно больше одного столбца, вам нужно будет использовать GROUP BY. - person Chud37; 30.09.2018

Добавление ключевого слова Distinct в проекцию у меня тоже сработало, однако это сработало только тогда, когда ключевое слово Different было первым аргументом:

String[] projection = new String[]{"DISTINCT " + DBConstants.COLUMN_UUID, ... };
person TacoEater    schedule 11.02.2017

В некоторых условиях мы можем использовать «отличное (COLUMN_NAME)» в качестве выбора, и это работает отлично. но в некоторых условиях это вызовет исключение.

когда это вызовет исключение, я буду использовать HashSet для хранения значений столбца....

person Mikonos    schedule 07.05.2012

Если у вас есть несколько столбцов в проекции, вы должны сделать следующее:

    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 с закрывающей скобкой и цифрой "1" внутри - это крошечный хак, но он работает абсолютно нормально

person KLM    schedule 15.04.2020

Я создал служебный метод для использования группы по и отдельно.

Применение

Вот пример выбора невидимого thread_id с датой последнего сообщения из базы данных MMS.

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)
    }
}

Источник

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
}
person Ilya Gazman    schedule 01.04.2021

Возможно, проще получить разные значения, попробуйте добавить слово DISTINCT перед именем столбца, которое вы хотите в таблицу проекций.

String[] projection = new String[]{
                BaseColumns._ID,
                "DISTINCT "+ Mediastore.anything.you.want
};

и используйте его в качестве аргумента для метода запроса распознавателя контента!

Я надеюсь помочь вам, потому что у меня есть тот же вопрос через несколько дней

person javment    schedule 28.06.2011
comment
Это не работает для меня. Мой код: проекция String[] = new String[] {DISTINCT + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME}; Cursor cur = cr.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, проекция, LENGTH( + ContactsContract.CommonDataKinds.Phone.NUMBER + ) ›= 8, null, sortOrder); Я получаю неверный столбец DISTINCT display_name - person Maurice; 02.09.2011
comment
Давайте попробуем добавить пробел в DISTINCT -> DISTINCT ; В этом блоге javment.blogspot.com /2011/07/ Я сделал простой туториал по этому поводу, может вам поможет. - person javment; 04.09.2011
comment
@javment работает только с одним столбцом, это глупое решение, если вам нужно запрашивать несколько столбцов. - person Akshay Mukadam; 04.08.2014
comment
Столбец с отличием должен быть в начале проекционного массива. Также вы должны указать выделение: some_column IS NOT NULL) GROUP BY (distinct_column_name. Источник: qaru.site/questions/2903561/ - person nimelorm; 09.10.2018

person    schedule
comment
ты лучше поясни свой ответ - person Muhammed Refaat; 10.02.2016