Как работать с ударными символами в iOS SQLite?

Мне нужно выполнить SELECT-запросы, которые нечувствительны к случаю и акцентам. Для демонстрационных целей я создаю таблицу следующим образом:

create table table
(
  column text collate nocase
);

insert into table values ('A');
insert into table values ('a');
insert into table values ('Á');
insert into table values ('á');

create index table_cloumn_Index
  on table (column collate nocase);

Затем я получаю эти результаты при выполнении следующих запросов:

SELECT * FROM table WHERE column LIKE 'a';
> A
> a

SELECT * FROM table WHERE column LIKE 'á';
> á

SELECT * FROM table WHERE column LIKE 'Á';
> Á

Как я могу исправить это, чтобы результаты для любого из следующих запросов были такими:

> A
> a
> Á
> á

sqlite работает на iOS, кстати.

Спасибо заранее,

Ответ 1

Два основных подхода:

  • Вы можете создать второй столбец в таблице, который содержит строку без международных символов. Кроме того, перед тем, как выполнять поиск по этому второму столбцу поиска, вы также должны удалить международные символы из строки поиска, тоже (таким образом, вы сравниваете не-международные с не-международными).

    Это обычная программа, используемая для преобразования международных символов:

    NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    

    Вы также можете заменить акцентированные символы:

    NSMutableString *mutableString = [string mutableCopy];
    CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformStripCombiningMarks, NO);
    

    Кстати, если вам нужно отсортировать результаты, вы можете также сортировать это вторичное поле поиска вместо основного поля, что позволит избежать проблем, связанных с невозможностью SQLite сортировать международные символы.

  • Вы также можете создать свою собственную "безударную" функцию C (определите эту функцию C вне @implementation для своего класса):

    void unaccented(sqlite3_context *context, int argc, sqlite3_value **argv)
    {
        if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
            sqlite3_result_null(context);
            return;
        }
    
        @autoreleasepool {
            NSMutableString *string = [NSMutableString stringWithUTF8String:(const char *)sqlite3_value_text(argv[0])];
            CFStringTransform((__bridge CFMutableStringRef)string, NULL, kCFStringTransformStripCombiningMarks, NO);
            sqlite3_result_text(context, [string UTF8String], -1, SQLITE_TRANSIENT);
        }
    }
    

    Затем вы можете определить функцию SQLite, которая вызовет эту C-функцию (вызовите этот метод после открытия базы данных, которая будет действовать до закрытия этой базы данных):

    - (void)createUnaccentedFunction
    {
        if (sqlite3_create_function_v2(database, "unaccented", 1, SQLITE_ANY, NULL, &unaccented, NULL, NULL, NULL) != SQLITE_OK)
            NSLog(@"%s: sqlite3_create_function_v2 error: %s", __FUNCTION__, sqlite3_errmsg(database));
    }
    

    Сделав это, вы теперь можете использовать эту новую функцию unaccented в SQL, например:

    if (sqlite3_prepare_v2(database, "select a from table where unaccented(column) like 'a'", -1, &statement, NULL) != SQLITE_OK)
        NSLog(@"%s: insert 1: %s", __FUNCTION__, sqlite3_errmsg(database));
    

Ответ 2

Вам нужно будет создать некоторую функцию пользователя или переопределить (то есть заменить) реализацию по умолчанию like() функции. Причина в том, что оператор LIKE в sqlite не поддерживает не-ASCII-чувствительность к регистру:

SQLite понимает только верхний/нижний регистр для символов ASCII по умолчанию. Оператор LIKE чувствителен к регистру по умолчанию для unicode символов, которые находятся за пределами диапазона ASCII. Например, выражение 'a' LIKE 'A' TRUE, но 'æ' LIKE 'Æ' FALSE.

Это имеет смысл, иначе sqlite должен поддерживать разные культуры, поскольку случай варьируется от одного к другому. Примером является капитал i в Турции, который не i, а пунктир İ, а нижний регистр i - точка без ı. Вложение всей этой информации о культуре в sqlite было бы очень обременительным (т.е. Увеличило бы объектный код sqlite).

Ответ 3

Вот мое решение проблемы LIKE

static void myLow(sqlite3_context *context, int argc, sqlite3_value **argv)
{
    NSString* str = [[NSString alloc] initWithUTF8String:
                            (const char *)sqlite3_value_text(argv[0])];
    const char* s = [[str lowercaseString] UTF8String];
    sqlite3_result_text(context, s, strlen(s), NULL);
    [str release];
}

// call it once after opening db
sqlite3_create_function(_db, "myLow", 1, SQLITE_UTF8,NULL, &myLow, NULL, NULL);

А затем вместо запроса

SELECT * FROM table WHERE column LIKE 'a'

вы должны использовать

SELECT * FROM table WHERE myLow(column) LIKE 'a'