Web 应用的常见任务是根据用户输入查出数据库中的数据。在一个简单例子中,会是通过分类筛选一个对象列表。一个更复杂的用例可能会要求根据重量,分类,多语言等筛选对象。本文介绍了一些常见用例和你能使用的工具。
我们会引用与 执行查询 中一样的模型。
Text-based fields have a selection of matching operations. For example, you may wish to allow lookup up an author like so:
>>> Author.objects.filter(name__contains="Terry")
[<Author: Terry Gilliam>, <Author: Terry Jones>]
这是一种非常简陋的方案,因为它要求用户必须知道用户名中包含的字符串。大小写不敏感的匹配 (icontains
) 不失为一种更好的方案,但优化的程度有限。
若你使用的是 PostgreSQL,Django 提供了 数据特殊筛选工具 帮助你巧妙利用更复杂的查询条件。其它数据库也有不同的筛选工具,可能是通过插件或用户自定义函数实现的。Django 此时还没有为他们提供任何支持。我们会用来自 PostgreSQL 的实例来证明其包含的功能函数。
在其它数据库中搜索
所有由 django.contrib.postgres
提供的筛选工具均基于 自定义查询 和 数据库函数 实现。根据你使用的数据库,你应该构建响应的查询,实现类似的 API。若有某个东西无法以这种方式实现,请新建一个工单。
In the above example, we determined that a case insensitive lookup would be
more useful. When dealing with non-English names, a further improvement is to
use unaccented comparison
:
>>> Author.objects.filter(name__unaccent__icontains="Helen")
[<Author: Helen Mirren>, <Author: Helena Bonham Carter>, <Author: Hélène Joy>]
这展开了另一个关于通过名字的不同拼写进行比较的讨论。但这种比较是不对称的 —— 筛选 Helen
能拿到 Helena
或 Hélène
,但反着来却不行。还有一个选项允许使用 trigram_similar
比较,这回比较字母的序列。
例如:
>>> Author.objects.filter(name__unaccent__lower__trigram_similar="Hélène")
[<Author: Helen Mirren>, <Author: Hélène Joy>]
现在还有一个问题 —— 名字 "Helena Bonham Carter" 有点太长了,以至于没有显示。三元搜索综合考虑了三种字母的所有组合形式,并同时再查询和源字符串中比较了出现的次数。对于长名字,源字符串中包含了更多的组合方式,所以其不再被认为是一种近似匹配。
要基于你提供的特定数据集合选择一个合适的比较函数,例如依据使用的语言和待搜索的文本。我们见过的所有例子都是关于短字符串的,这使得用户可以输入与源数据关联较大(根据不同的定义)的内容。
标准数据库操作对于大量文本搜索来说太过简陋了。虽然上面的示例可以看作是对字符串的操作,但是全文搜索查看的是实际的单词。依据所使用的系统,可以采用下面的某些方法:
使用搜索软件有很多选项,最常见的有 Elastic 和 Solr。它们都是基于全文搜索的解决方案。要用它们搜索来自 Django 模型的数据,你需要一个抽象层,将数据(包括对数据库 id 的指针)转换为文本文档。当使用该引擎的某次搜索返回了一份文档,你可以在数据库中查看它。有很多第三方库被设计为处理这种问题。
PostgreSQL 内置了其专属的全文本搜索实现。虽然并不像其它搜索引擎那样强大,但它的优点是内置在数据库中,所以它能很方便的与其它关联查询条件进行联合查询,如按分类查询。
The django.contrib.postgres
module provides some helpers to make these
queries. For example, a query might select all the blog entries which mention
"cheese":
>>> Entry.objects.filter(body_text__search="cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]
You can also filter on a combination of fields and on related models:
>>> Entry.objects.annotate(
... search=SearchVector("blog__tagline", "body_text"),
... ).filter(search="cheese")
[
<Entry: Cheese on Toast recipes>,
<Entry: Pizza Recipes>,
<Entry: Dairy farming in Argentina>,
]
参阅 contrib.postgres
全文搜索 文档获取全部细节。
5月 12, 2023