Как правильно добавить DISTINCT
и/или GROUPBY
к запросам на основе ContentResolver
?
Сейчас мне нужно создать собственный URI для каждого особого случая.
Есть ли способ лучше?
(Я все еще программирую 1,5 как наименьший общий знаменатель)
Как правильно добавить DISTINCT
и/или GROUPBY
к запросам на основе ContentResolver
?
Сейчас мне нужно создать собственный URI для каждого особого случая.
Есть ли способ лучше?
(Я все еще программирую 1,5 как наименьший общий знаменатель)
Вы можете сделать хороший взлом при запросе contentResolver, используя:
String selection = Models.SOMETHING + "=" + something + ") GROUP BY (" + Models.TYPE;
Если вы хотите использовать 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)
Поскольку никто не ответил, я просто расскажу, как я это решил. По сути, я бы создал собственный 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);
В вашем переопределенном методе запроса 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;
}
Хотя я не использовал Group By, я использовал Distinct в запросе преобразователя контента.
Cursor cursor = contentResolver
.query(YOUR_URI,
new String[] {"Distinct "+ YOUR_COLUMN_NAME},
null,
null, null);
Добавление ключевого слова Distinct в проекцию у меня тоже сработало, однако это сработало только тогда, когда ключевое слово Different было первым аргументом:
String[] projection = new String[]{"DISTINCT " + DBConstants.COLUMN_UUID, ... };
В некоторых условиях мы можем использовать «отличное (COLUMN_NAME)» в качестве выбора, и это работает отлично. но в некоторых условиях это вызовет исключение.
когда это вызовет исключение, я буду использовать HashSet для хранения значений столбца....
Если у вас есть несколько столбцов в проекции, вы должны сделать следующее:
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" внутри - это крошечный хак, но он работает абсолютно нормально
Я создал служебный метод для использования группы по и отдельно.
Вот пример выбора невидимого 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
}
Возможно, проще получить разные значения, попробуйте добавить слово DISTINCT перед именем столбца, которое вы хотите в таблицу проекций.
String[] projection = new String[]{
BaseColumns._ID,
"DISTINCT "+ Mediastore.anything.you.want
};
и используйте его в качестве аргумента для метода запроса распознавателя контента!
Я надеюсь помочь вам, потому что у меня есть тот же вопрос через несколько дней