Использование COLLATE в Android SQLite - Локали игнорируются в операторе LIKE
При создании базы данных SQLite в Android я устанавливал языковой стандарт базы данных - db.setLocale(новый языковой стандарт ("cz_CZ")). Это чешский язык.
Оператор SELECT работает и учитывает локаль, например:
SELECT * from table WHERE name='sctzy' COLLATE LOCALIZED
Найдет запись 'ščťžý'.
Но использование LIKE не удастся:
SELECT * from table WHERE name LIKE '%sctzy%' COLLATE LOCALIZED
Строка не возвращается.
КСТАТИ. В Android нет класса java.text.Normalized. Я думал, что смогу создать второй столбец с нормализованным текстом, лишенным специальных символов, который будет использоваться для поиска - но мне не хватает класса или способа нормализации строки.
4 ответа
Вы смотрели документацию по SQLite для LIKE? Пришла информация о не ASCII символах и баге. Возможно, в Android установлена более старая версия SQLite, где это является проблемой.
Я думаю, что второй нормализованный столбец может быть вашим лучшим вариантом, к сожалению.
Просто сегодня у меня была точно такая же задача, как и у тебя. И в моей ситуации создание дополнительных теневых столбцов не так, потому что мне приходится искать более одного столбца. Поэтому я пришел к такому решению, которое тестируется в реальном проекте. В моем случае я обрабатываю только строчные буквы, но вы также можете расширить функцию заглавными буквами.
db.setLocale(Locale("cz", "CZ"))
val query = "SELECT * FROM table WHERE name GLOB ${getExpr(str)} ORDER BY name COLLATE LOCALIZED ASC"
private fun getExpr(input: String) : String{
var expr = ""
for(lettter in input){
expr += when(lettter){
's','š' -> "[sš]"
'a','á' -> "[aá]"
'e','ě','é' -> "[eěé]"
'i','í' -> "[ií]"
'z','ž' -> "[zž]"
'c','č' -> "[cč]"
'y','ý' -> "[yý]"
'r','ř' -> "[rř]"
'u','ů','ú' -> "[uůú]"
'o','ó' -> "[oó]"
'n','ň' -> "[nň]"
'd','ď' -> "[dď]"
't','ť' -> "[tť]"
else -> lettter
}
}
return "'*${expr}*'"
}
Создание второго нормализованного столбца может использоваться для обхода ограничений (как кратко упомянуто в других ответах).
На практике это означает, что вы должны создать еще один (теневой) столбец вашего первого, где хранятся те же данные в фиксированном регистре (например, все верхние символы). В этом новом столбце могут выполняться запросы без учета регистра (включая аналогичные запросы) со значениями поиска в том же случае.
Если первый столбец "а" содержит
AAA
ааа
Bbb
aaÃ
EEE
Второй столбец a_shadow будет содержать для тех же строк
AAA
AAA
ВВВ
aaÃ
EEE
и ваш оригинальный запрос (пример) "выберите из mytable, где a='äää'"
будет заменен на "выберите из mytable, где A='ÄÄÄ'"
Ваш код должен быть обновлен, чтобы заполнить преобразованный теневой контент при добавлении основного контента. Если столбец добавлен после создания или вы не можете изменить код, существующие значения, возможно, придется преобразовать с помощью запроса на обновление. Пример:
UPDATE mytable SET a_shadow=UPPER(a);
В Android sqlite, LIKE
а также GLOB
игнорировать оба COLLATE LOCALIZED
а также COLLATE UNICODE
(они работают только на ORDER BY
). Однако, как объясняет @asat в своем ответе, вы можете использовать GLOB
с шаблоном, который заменит каждую букву всеми доступными альтернативами этой буквы. В Java:
public static String addTildeOptions(String searchText) {
return searchText.toLowerCase()
.replaceAll("[aáàäâã]", "\\[aáàäâã\\]")
.replaceAll("[eéèëê]", "\\[eéèëê\\]")
.replaceAll("[iíìî]", "\\[iíìî\\]")
.replaceAll("[oóòöôõ]", "\\[oóòöôõ\\]")
.replaceAll("[uúùüû]", "\\[uúùüû\\]")
.replace("*", "[*]")
.replace("?", "[?]");
}
И потом (не буквально так, конечно):
SELECT * from table WHERE lower(column) GLOB "*addTildeOptions(searchText)*"
Таким образом, например, на испанском языке пользователь, выполняющий поиск по mas или más, преобразует результаты поиска в m[aáàäâã], возвращая оба результата.
Важно отметить, что GLOB
игнорируемых COLLATE NOCASE
Вот почему я преобразовал все в нижний регистр как в функции, так и в запросе. Обратите внимание также, что lower()
Функция в sqlite не работает с не-ASCII символами - но, опять же, это те, которые вы уже заменяете!
Функция также заменяет оба GLOB
подстановочные знаки, *
а также ?
, с "сбежавшими" версиями.
Может занять много времени, но вы можете использовать java.text.Normalizer как здесь
Преобразование символов, букв Accent в английский алфавит
Так как Android не является частью java-подмножества, вы можете попытаться найти его в коде java, таком как Normalizer.java. Здесь находится Javadoc:
И скопируйте ту часть кода, которая нужна внутри вашего проекта.
Надеюсь, что это работает!