全文搜索¶
django.contrib.postgres.search 模块中的数据库函数方便了 PostgreSQL 的 全文搜索引擎 的使用。
在本文档的例子中,我们将使用 执行查询 中定义的模型。
See also
有关搜索的高级概述,请参见 主题文档。
search 查找¶
常见的使用全文搜索的方式是对数据库中的单个列搜索单个术语。例如:
>>> Entry.objects.filter(body_text__search="Cheese")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]>
这将使用默认的数据库搜索配置,从 body_text 字段在数据库中创建一个 to_tsvector,从搜索词 'Cheese' 中创建一个 plainto_tsquery。通过匹配查询和向量得到结果。
要使用 search 查找,'django.contrib.postgres' 必须在你的 INSTALLED_APPS。
SearchVector¶
对单个字段进行搜索非常有用,但也有一定的限制。我们要搜索的 Entry 实例属于一个 Blog,该 Blog 有一个 tagline 字段。要对这两个字段进行查询,可以使用 SearchVector:
>>> from django.contrib.postgres.search import SearchVector
>>> Entry.objects.annotate(
... search=SearchVector("body_text", "blog__tagline"),
... ).filter(search="Cheese")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]>
SearchVector 的参数可以是任何 Expression 或字段名。多个参数将使用空格连接在一起,这样搜索文档就会包含所有参数。
SearchVector 对象可以组合在一起,允许您重复使用它们。例如:
>>> Entry.objects.annotate(
... search=SearchVector("body_text") + SearchVector("blog__tagline"),
... ).filter(search="Cheese")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]>
SearchQuery¶
SearchQuery 将用户提供的术语转化为搜索查询对象,数据库将其与搜索向量进行比较。默认情况下,用户提供的所有词语都会通过词干算法,然后寻找所有结果词语的匹配。
如果 search_type 是 'plain',即默认值,则将术语作为单独的关键字处理。如果 search_type 是 'phrase',则将术语作为一个单一的短语处理。如果 search_type 是 'raw',那么你可以提供一个带有术语和运算符的格式化搜索查询。如果 search_type 是 'websearch',那么你可以提供一个格式化的搜索查询,类似于网络搜索引擎使用的格式。'websearch' 需要 PostgreSQL ≥ 11。请阅读 PostgreSQL 的 全文搜索文档 来了解两者的区别和语法。举例说明。
>>> from django.contrib.postgres.search import SearchQuery, Lexeme
>>> SearchQuery("red tomato") # two keywords
>>> SearchQuery("tomato red") # same results as above
>>> SearchQuery("red tomato", search_type="phrase") # a phrase
>>> SearchQuery("tomato red", search_type="phrase") # a different phrase
>>> SearchQuery("'tomato' & ('red' | 'green')", search_type="raw") # boolean operators
>>> SearchQuery(
... "'tomato' ('red' OR 'green')", search_type="websearch"
... ) # websearch operators
>>> SearchQuery(Lexeme("tomato") & (Lexeme("red") | Lexeme("green"))) # Lexeme objects
SearchQuery 术语可以逻辑组合,以提供更大的灵活性:
>>> from django.contrib.postgres.search import SearchQuery
>>> SearchQuery("meat") & SearchQuery("cheese") # AND
>>> SearchQuery("meat") | SearchQuery("cheese") # OR
>>> ~SearchQuery("meat") # NOT
参见 更改搜索配置 对 config 参数的解释。
Lexeme objects were added.
SearchRank¶
到目前为止,我们已经返回了向量和查询之间可能存在任何匹配的结果。您可能希望按某种相关性对结果进行排序。PostgreSQL 提供了一个排名函数,该函数考虑了查询术语在文档中出现的频率、术语在文档中的紧密程度以及它们出现的文档部分的重要性。匹配越好,排名的值就越高。要按相关性排序:
>>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
>>> vector = SearchVector("body_text")
>>> query = SearchQuery("cheese")
>>> Entry.objects.annotate(rank=SearchRank(vector, query)).order_by("-rank")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]>
参见 加权查询 关于 weights 参数的解释。
将 cover_density 参数设置为 True,启用覆盖密度排序,即考虑匹配的查询词的接近程度。
提供一个整数给 normalization 参数来控制排名的归一化。这个整数是一个位掩码,所以您可以组合多种行为:
>>> from django.db.models import Value
>>> Entry.objects.annotate(
... rank=SearchRank(
... vector,
... query,
... normalization=Value(2).bitor(Value(4)),
... )
... )
PostgreSQL 文档中有更多关于 不同排序归一化选项 的细节。
SearchHeadline¶
- class SearchHeadline(expression, query, config=None, start_sel=None, stop_sel=None, max_words=None, min_words=None, short_word=None, highlight_all=None, max_fragments=None, fragment_delimiter=None)[source]¶
接受一个文本字段或一个表达式、一个查询、一个配置和一组选项。返回高亮显示的搜索结果。
将 start_sel 和 stop_sel 参数设置为字符串值,用于在文档中高亮显示查询词。PostgreSQL 的默认值是 <b> 和 </b>。
为 max_words 和 min_words 参数提供整数值,以确定最长和最短的标题。PostgreSQL 的默认值是 35 和 15。
为 short_word 参数提供一个整数值,以便在每个标题中丢弃这个长度或更少的字。PostgreSQL 的默认值是 3。
将 highlight_all 参数设置为 True,以使用整个文档来代替片段,并忽略 max_words、min_words 和 short_word 参数。这在 PostgreSQL 中是默认禁用的。
为 max_fragments 提供一个非零的整数值,以设置要显示的最大片段数。在 PostgreSQL 中默认是禁用的。
设置 fragment_delimiter 字符串参数来配置片段之间的定界符。PostgreSQL 的默认值是 " ... "。
PostgreSQL 文档中有更多关于 高亮搜索结果 的细节。
用法示例:
>>> from django.contrib.postgres.search import SearchHeadline, SearchQuery
>>> query = SearchQuery("red tomato")
>>> entry = Entry.objects.annotate(
... headline=SearchHeadline(
... "body_text",
... query,
... start_sel="<span>",
... stop_sel="</span>",
... ),
... ).get()
>>> print(entry.headline)
Sandwich with <span>tomato</span> and <span>red</span> cheese.
参见 更改搜索配置 对 config 参数的解释。
更改搜索配置¶
您可以为 SearchVector 和 SearchQuery 指定 config 属性,以使用不同的搜索配置。这允许使用数据库定义的不同语言解析器和字典:
>>> from django.contrib.postgres.search import SearchQuery, SearchVector
>>> Entry.objects.annotate(
... search=SearchVector("body_text", config="french"),
... ).filter(search=SearchQuery("œuf", config="french"))
<QuerySet [<Entry: Pain perdu>]>
config 的值也可以存储在另一个列中:
>>> from django.db.models import F
>>> Entry.objects.annotate(
... search=SearchVector("body_text", config=F("blog__language")),
... ).filter(search=SearchQuery("œuf", config=F("blog__language")))
<QuerySet [<Entry: Pain perdu>]>
加权查询¶
不同字段在查询中的相关性可能不同,因此您可以在将它们组合之前设置各个向量的权重:
>>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
>>> vector = SearchVector("body_text", weight="A") + SearchVector(
... "blog__tagline", weight="B"
... )
>>> query = SearchQuery("cheese")
>>> Entry.objects.annotate(rank=SearchRank(vector, query)).filter(rank__gte=0.3).order_by(
... "rank"
... )
权重应该是以下字母之一:D、C、B、A。默认情况下,这些权重分别对应数字 0.1、0.2、0.4 和 1.0。如果您希望以不同的方式设置权重,可以将一个包含四个浮点数的列表传递给 SearchRank,按照上述相同的顺序设置权重:
>>> rank = SearchRank(vector, query, weights=[0.2, 0.4, 0.6, 0.8])
>>> Entry.objects.annotate(rank=rank).filter(rank__gte=0.3).order_by("-rank")
Lexeme¶
Lexeme objects allow search operators to be safely used with strings from
an untrusted source. The content of each lexeme is escaped so that any
operators that may exist in the string itself will not be interpreted.
You can combine lexemes with other lexemes using the & and | operators
and also negate them with the ~ operator. For example:
>>> from django.contrib.postgres.search import SearchQuery, SearchVector, Lexeme
>>> vector = SearchVector("body_text", "blog__tagline")
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("fruit") & Lexeme("dessert"))
... )
<QuerySet [<Entry: Apple Crumble Recipes>, <Entry: Banana Split Recipes>]>
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("fruit") & Lexeme("dessert") & ~Lexeme("banana"))
... )
<QuerySet [<Entry: Apple Crumble Recipes>]>
Lexeme objects also support term weighting and prefixes:
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("Pizza") | Lexeme("Cheese"))
... )
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]>
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("Pizza") | Lexeme("Cheese", weight="A"))
... )
<QuerySet [<Entry: Pizza recipes>]>
>>> Entry.objects.annotate(search=vector).filter(
... search=SearchQuery(Lexeme("za", prefix=True))
... )
<QuerySet []>
性能¶
使用这些函数都不需要特殊的数据库配置,但是,如果你搜索的记录超过几百条,你很可能会遇到性能问题。例如,全文搜索是一个比比较整数大小更密集的过程。
如果您查询的所有字段都包含在一个特定的模型中,您可以创建一个与您希望使用的搜索向量匹配的功能性 GIN 或 GiST 索引。例如:
GinIndex(
SearchVector("body_text", "headline", config="english"),
name="search_vector_idx",
)
The PostgreSQL docs has details on creating indexes for full text search.
SearchVectorField¶
如果这种方法变得太慢,您可以在模型中添加一个 SearchVectorField。您需要使用触发器保持它的数据更新,例如,可以参考 PostgreSQL 文档 中的描述。然后,您可以查询该字段,就像它是一个已注释的 SearchVector 一样:
>>> Entry.objects.update(search_vector=SearchVector("body_text"))
>>> Entry.objects.filter(search_vector="cheese")
<QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]>
三元相似度¶
另一种搜索的方法是三字母相似度。三字母是指三个连续的字符组合。除了 trigram_similar、trigram_word_similar 和 trigram_strict_word_similar 查找之外,您还可以使用一些其他表达式。
要使用它们,你需要激活 PostgreSQL 上的 pg_trgm 扩展 。你可以使用 TrigramExtension 迁移操作来安装它。
TrigramSimilarity¶
接受一个字段名或表达式,以及一个字符串或表达式。返回两个参数之间的三元相似度。
用法示例:
>>> from django.contrib.postgres.search import TrigramSimilarity
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Katie Stephens"
>>> Author.objects.annotate(
... similarity=TrigramSimilarity("name", test),
... ).filter(
... similarity__gt=0.3
... ).order_by("-similarity")
<QuerySet [<Author: Katy Stevens>, <Author: Stephen Keats>]>
TrigramWordSimilarity¶
接受一个字符串或表达式,以及一个字段名或表达式。返回两个参数之间的三元相似度。
用法示例:
>>> from django.contrib.postgres.search import TrigramWordSimilarity
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Kat"
>>> Author.objects.annotate(
... similarity=TrigramWordSimilarity(test, "name"),
... ).filter(
... similarity__gt=0.3
... ).order_by("-similarity")
<QuerySet [<Author: Katy Stevens>]>
TrigramStrictWordSimilarity¶
接受一个字符串或表达式,以及一个字段名或表达式。返回两个参数之间的三字母严格单词相似度。类似于 TrigramWordSimilarity(),但它强制范围边界与单词边界匹配。
TrigramDistance¶
接受一个字段名或表达式,以及一个字符串或表达式。返回两个参数之间的三元距离。
用法示例:
>>> from django.contrib.postgres.search import TrigramDistance
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Katie Stephens"
>>> Author.objects.annotate(
... distance=TrigramDistance("name", test),
... ).filter(
... distance__lte=0.7
... ).order_by("distance")
<QuerySet [<Author: Katy Stevens>, <Author: Stephen Keats>]>
TrigramWordDistance¶
接受一个字符串或表达式,以及一个字段名或表达式。返回两个参数之间的三元字距离。
用法示例:
>>> from django.contrib.postgres.search import TrigramWordDistance
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Kat"
>>> Author.objects.annotate(
... distance=TrigramWordDistance(test, "name"),
... ).filter(
... distance__lte=0.7
... ).order_by("distance")
<QuerySet [<Author: Katy Stevens>]>
TrigramStrictWordDistance¶
接受一个字符串或表达式,以及一个字段名或表达式。返回两个参数之间的三字母严格单词距离。