如何优化核心数据查询以进行全文搜索

时间:2009-11-21 03:07:05

标签: iphone sql cocoa cocoa-touch core-data

在搜索文本中匹配的单词时,我可以优化核心数据查询吗? (这个问题也与iPhone上自定义SQL与核心数据的智慧有关。)

我正在开发一款新的(iPhone)应用程序,它是科学数据库的手持式参考工具。主界面是一个标准的可搜索表视图,我想要用户键入新单词时的类型响应。单词匹配必须是文本中单词的前缀。该文本由100,000个单词组成。

在我的原型中,我直接编写了SQL。我创建了一个单独的“单词”表,其中包含主实体文本字段中的每个单词。我索引了单词并按照

的顺序进行了搜索
SELECT id, * FROM textTable 
  JOIN (SELECT DISTINCT textTableId FROM words 
         WHERE word BETWEEN 'foo' AND 'fooz' ) 
    ON id=textTableId
 LIMIT 50

运行速度非常快。使用IN可能也可以正常工作,即

SELECT * FROM textTable
 WHERE id IN (SELECT textTableId FROM words 
               WHERE word BETWEEN 'foo' AND 'fooz' ) 
 LIMIT 50

LIMIT至关重要,可让我快速显示结果。如果达到限制,我会通知用户显示的内容太多。这是kludgy。

我花了最近几天考虑转移到Core Data的优势,但我担心架构中缺乏控制,索引和查询重要查询。

理论上,textField MATCHES '.*\bfoo.*'的NSPredicate会起作用,但我确信它会很慢。这种文本搜索似乎很常见,我想知道通常的攻击是什么?你会像我上面那样创建一个单词实体并使用“bEGINSWITH'foo'这个词的谓词吗?这会像我的原型一样快吗? Core Data会自动创建正确的索引吗?我找不到任何明确的方法来建议持久性存储关于索引。

我在iPhone应用程序中看到了Core Data的一些优点。故障和其他内存注意事项允许对tableview查询进行有效的数据库检索,而无需设置任意限制。对象图管理允许我轻松遍历实体而无需编写大量SQL。移植功能将来会很好。另一方面,在有限的资源环境(iPhone)中,我担心自动生成的数据库会因元数据,不必要的反向关系,低效的属性数据类型等而膨胀。

我应该潜入或谨慎行事吗?

4 个答案:

答案 0 :(得分:10)

我做了一个解决方案。我认为它与this post类似。我将合并源代码添加到我的Core Data项目中,然后创建了一个不是托管对象子类的全文搜索类。在FTS类I #import "sqlite3.h"(源文件)而不是sqlite框架。 FTS类保存到与Core Data持久存储区不同的.sqlite文件。

当我导入数据时,Core Data对象将相关FTS对象的rowid存储为整数属性。我有一个静态数据集,所以我不担心参照完整性,但保持完整性的代码应该是微不足道的。

要执行FTS,我MATCH查询FTS类,返回一组rowid。在我的托管对象类中,我使用[NSPredicate predicateWithFormat:@"rowid IN %@", rowids]查询相应的对象。我避免以这种方式遍历任何多对多的关系。

性能提升非常显着。我的数据集是142287行,包括194MB(核心数据)和92MB(删除了停用词的FTS)。根据搜索词频率,我的搜索频率从几秒到0.1秒不常用(<100次点击)和0.2秒(频繁的术语)(> 2000次点击)。

我确信我的方法存在无数问题(代码膨胀,可能存在命名空间冲突,丢失一些核心数据功能),但似乎有效。

答案 1 :(得分:3)

为了跟进这个问题,我发现使用Core Data查询是很慢的。我已经在这个问题上摸了好几个小时。

正如在我的问题中的SQL示例中,有两个实体:textTable和单词,其中单词包含每个单词,它被索引,并且textTable和单词之间存在多对多关系。我用4000个单词和360个textTable对象填充数据库。假设与单词对象的textTable关系称为searchWords,那么我可以在textTable实体上使用类似于

的谓词
predicate = [NSPredicate predicateWithFormat:@"ANY searchWords.word BEGINSWITH %@", query];

(我可以为多个查询术语添加此谓词的连词。)

在iPhone上,此查询需要几秒钟。使用更大的测试集的手动编码SQL的响应是即时的。

但这甚至不是结束。 NSPredicate存在一些限制,使得相当简单的查询变得缓慢而复杂。例如,在上面的示例中想象您要使用范围按钮进行过滤。假设单词entity包含所有文本字段中的所有单词,但范围会将其限制为来自特定字段的单词。因此,单词可能具有“源”属性(例如,电子邮件的标题和消息正文)。

然后,自然地,全文将忽略源属性,如上例所示,但过滤后的查询会将搜索限制为特定的源值。这个看似简单的变化需要一个SUBQUERY。例如,这不起作用:

ANY searchWords.word BEGINSWITH "foo" AND ANY searchWords.source = 3

因为两个表达式的实体可能不同。相反,你必须做类似的事情:

SUBQUERY(searchWords, $x, $x.word BEGINSWITH "foo" AND $x.source = 3).@count > 0

我发现这些子查询可能不会令人惊讶地比使用“ANY”的谓词慢。

此时我非常好奇Cocoa程序员如何有效地使用Core Data进行全文搜索,因为我对谓词评估的速度和NSPredicates的可表达性感到沮丧。我碰到了墙。

答案 2 :(得分:2)

潜入。

这是一种方法:

  1. 将您的记录放入Core Data持久性商店
  2. 使用NSFetchedResultsController管理Word实体上的结果集(与SQL相同的核心数据“字”表)
  3. 使用UISearchDisplayController实时在结果集上应用NSPredicate
  4. 通过NSFetchedResultsController获得结果集后,应用谓词非常容易。根据我的经验,它也会有回应。例如:

    if ([self.searchBar.text length]) {
        _predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"(word contains[cd] '%@')", self.searchBar.text]];
        [self.fetchedResultsController.fetchRequest setPredicate:_predicate];
    }
    
    NSError *error;
    if (![self.fetchedResultsController performFetch:&error]) {
        // handle error...
    }
    NSLog(@"filtered results: %@", [self.fetchedResultsController fetchedObjects]);
    

    会动态过滤结果集[self.fetchedResultsController fetchedObjects],对word进行不区分大小写的搜索。

答案 3 :(得分:2)

在遇到同样的问题之后,我遇到了一系列帖子,其中作者遇到了同样的问题,并提出了this solution。他报告说,从6到7秒的搜索时间到0.13到0.05秒之间有所改善。

他的FTS数据集是79个文件(文件大小175k,3600个离散标记,10000个参考文献)。我还没有尝试过他的解决方案,但我想我会尽快发布。另见他的帖子的Part 2有关问题的文档,以及Part 1他的数据集文档。