【问题标题】:How to deal with accented characters in iOS SQLite?如何处理 iOS SQLite 中的重音字符?
【发布时间】:2026-02-19 21:15:02
【问题描述】:

我需要执行不区分大小写和重音的 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 上运行。

提前致谢,

【问题讨论】:

    标签: ios sqlite cultureinfo diacritics


    【解决方案1】:

    两种基本方法:

    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 无法对国际字符进行排序而引起的问题。

    2. 您也可以创建自己的“无重音”C 函数(在 @implementation 之外为您的班级定义此 C 函数):

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

      完成后,您现在可以在 SQL 中使用这个新的 unaccented 函数,例如:

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

    【讨论】:

    • 是的,我也在使用这种方法,但是现在我需要处理一个我无法更改其结构的数据库。但是,如果可以,并且您的数据库不是太大,这是一个简单的方法。
    • @LeandroAlves 我知道你早就解决了这个问题,但我 (a) 包含了另一种非重音字符串的变体; (b) 向您展示如何在 SQLite 中即时取消重音字符串。
    • @Rob 选项 2 的代码正确执行和处理数据,但占用 CPU 到 99% 并且内存 > 1000 MB。操作完成后,应用程序立即崩溃并显示EXC_BAD_ACCESS (code=2, address=0x0)。有什么可以用这段代码来控制内存使用的吗?
    • @ChrisSchiffhauer 这需要一些讨论,所以我为这个后续问题创建了一个聊天室:chat.*.com/rooms/41329/…
    • @ChrisSchiffhauer 通过引入@autoreleasepool 可以解决对许多很多记录执行此更新时出现的内存问题。请参阅修改后的答案。
    【解决方案2】:

    您将需要create some user function,或覆盖(即替换)like() functions 的默认实现。原因是 sqlite 中的 LIKE 运算符不支持非 ASCII 不区分大小写:

    SQLite 只能通过以下方式理解 ASCII 字符的大写/小写 默认。 LIKE 运算符对于 unicode 默认情况下区分大小写 超出 ASCII 范围的字符。例如, 表达式 'a' LIKE 'A' 为 TRUE,但 'æ' LIKE 'Æ' 为 FALSE。

    这是有道理的,否则 sqlite 将需要支持不同的文化,因为情况因人而异。一个例子是the capital i in Turkey which is not I but a dotted İ, and the lower-case of I is a dot-less ı。将所有这些文化信息嵌入到 sqlite 中会非常繁琐(即会增加 sqlite 对象代码)。

    【讨论】:

    • 您是否已经在 iOS 上创建了任何用户功能?你有任何关于那个的示例代码吗?这似乎是更正确的做法。
    【解决方案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'
    

    【讨论】: