NOTE: Apart from
(and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.
Friday, 13 September 2024
//Less than a minute
搜索内容是任何内容重的网站的关键部分。 它提高了可发现性和用户经验。 本文将报导我如何加入搜寻网站全文的全文,
本系列的下一部分:
[技选委
有很多方法可以进行全文搜索 包括
我最近改用Postgres作为数据库。 Postgres 具有全文搜索功能,功能非常强大,而且(有些)易于使用。 它也非常快,可以轻松地处理复杂的查询。
当建造高温时 DbContext
您可以指定哪些字段已启用全文搜索功能。
Postgres利用搜索矢量的概念实现快速、高效的全文本搜索。 搜索矢量是一个包含文档中的单词及其位置的数据结构。 基本上预先计算数据库中每行的搜索矢量,使Postgres能够非常迅速地搜索文档中的单词。 它为此使用两种特殊数据类型:
此外,它还提供排序功能,使您能够根据结果与搜索查询的匹配程度排列结果。 这是非常强大的, 并允许你按相关性来决定结果。 PostgreSQL根据相关性对结果进行排名。 相关性是通过考虑检索术语相互接近等因素以及这些术语在文件中的出现频率来计算的。 函数 ts_ rank 或 ts_ rk_ cd 用于计算此排序 。
您可以阅读更多关于 Postgres 全文搜索功能的更多信息 在这里
Postgres实体框架一揽子计划 在这里 为全文搜索提供强有力的支持。 它允许您指定哪些字段为完整文本索引,以及如何查询它们。
为此,我们为我们的实体增加具体的索引类型,其定义如下: DbContext
:
modelBuilder.Entity<BlogPostEntity>(entity =>
{
entity.HasIndex(x => new { x.Slug, x.LanguageId });
entity.HasIndex(x => x.ContentHash).IsUnique();
entity.HasIndex(x => x.PublishedDate);
entity.HasIndex(b => new { b.Title, b.PlainTextContent})
.HasMethod("GIN")
.IsTsVectorExpressionIndex("english");
...
我们在此添加一个全文索引 Title
和 PlainTextContent
区域中的 BlogPostEntity
.. 我们还在具体说明索引应该使用 GIN
索引类型和 english
语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 语言 这一点很重要,因为它告诉Postgres如何编制数据索引,用什么语言来阻止和阻止言词。
这显然是我们博客的一个问题,因为我们有多种语言。 不幸的是,现在我只是在使用 english
用于所有职位的语文。 这是我将来需要处理的事情 但现在已经足够有效了
我们还将增加一个索引。 Category
实体:
modelBuilder.Entity<CategoryEntity>(entity =>
{
entity.HasIndex(b => b.Name).HasMethod("GIN").IsTsVectorExpressionIndex("english");;
...
通过此 Postgres 生成数据库中每行的搜索矢量 。 此矢量包含在 Title
和 PlainTextContent
字段。 然后我们可以使用此矢量搜索文档中的单词 。
这将转换为 SQL 中生成行搜索矢量的 to_tsverctor 函数。 然后我们可以使用 ts_ rank 函数来根据关联性排列结果的排序 。
SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats');
to_tsvector
-----------------------------------------------------
'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4
应用它作为向数据库的迁移, 我们准备开始搜索。
要搜索, 我们使用将使用 EF.Functions.ToTsVector
和 EF.Functions.WebSearchToTsQuery
函数创建搜索矢量和查询。 然后我们就可以使用 Matches
函数在搜索矢量中查找查询。
var posts = await context.BlogPosts
.Include(x => x.Categories)
.Include(x => x.LanguageEntity)
.Where(x =>
EF.Functions.ToTsVector("english", x.Title + " " + x.PlainTextContent)
.Matches(EF.Functions.WebSearchToTsQuery("english", query)) // Search in title and content
&& x.Categories.Any(c =>
EF.Functions.ToTsVector("english", c.Name)
.Matches(EF.Functions.WebSearchToTsQuery("english", query))) // Search in categories
&& x.LanguageEntity.Name == "en") // Filter by language
.OrderByDescending(x =>
EF.Functions.ToTsVector("english", x.Title + " " + x.PlainTextContent)
.Rank(EF.Functions.WebSearchToTsQuery("english", query))) // Rank by relevance
.Select(x => new { x.Title, x.Slug })
.ToListAsync();
EF. Functions. WebSearchToTs 查询函数生成基于通用 Web 搜索引擎语法的行查询 。
SELECT websearch_to_tsquery('english', '"sad cat" or "fat rat"');
websearch_to_tsquery
-----------------------------------
'sad' <-> 'cat' | 'fat' <-> 'rat'
在此示例中, 您可以看到此选项生成一个查询, 以查找文档中的“ sad cat” 或“ fat rat” 等词 。 这是一个强有力的特点,使我们能够轻松地查询复杂的问题。
如前所述,这些方法同时产生行的搜索矢量和查询。 然后我们使用 Matches
函数在搜索矢量中查找查询。 我们也可以使用 Rank
函数按关联性排列结果的排序。
正如你所看到的,这不是一个简单的查询, 但它非常强大, 使我们能够在 Title
, PlainTextContent
和 Category
区域中的 BlogPostEntity
并按相关性排列这些要素的等级。
要使用这些( 未来) 我们可以创建一个简单的 WebAPI 端点, 以查询并返回结果 。 这是一个简单的控制器, 进行查询并返回结果 :
[ApiController]
[Route("api/[controller]")]
public class SearchApi(IMostlylucidDbContext context) : ControllerBase
{
[HttpGet]
public async Task<JsonHttpResult<List<SearchResults>>> Search(string query)
{;
var posts = await context.BlogPosts
.Include(x => x.Categories)
.Include(x => x.LanguageEntity)
.Where(x =>
EF.Functions.ToTsVector("english", x.Title + " " + x.PlainTextContent)
.Matches(EF.Functions.WebSearchToTsQuery("english", query)) // Search in title and content
&& x.Categories.Any(c =>
EF.Functions.ToTsVector("english", c.Name)
.Matches(EF.Functions.WebSearchToTsQuery("english", query))) // Search in categories
&& x.LanguageEntity.Name == "en") // Filter by language
.OrderByDescending(x =>
EF.Functions.ToTsVector("english", x.Title + " " + x.PlainTextContent)
.Rank(EF.Functions.WebSearchToTsQuery("english", query))) // Rank by relevance
.Select(x => new { x.Title, x.Slug })
.ToListAsync();
var output = posts.Select(x => new SearchResults(x.Title.Trim(), x.Slug)).ToList();
return TypedResults.Json(output);
}
使用这些“ 简单” TsVector Indices 的另一种办法,是使用生成的柱子存储搜索矢量,然后用它进行搜索。 这是一种更为复杂的办法,但可以提高业绩。
我们在这里修改 BlogPostEntity
添加特殊类型的列 :
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public NpgsqlTsVector SearchVector { get; set; }
这是生成行的搜索矢量的计算列。 然后,我们可以使用这一栏来搜索文件中的字词。
我们随后在实体定义内设置了这个索引(尚有待确认),但这也可能允许我们使用多种语文,为每个职位指定一个语文栏目。
entity.Property(b => b.SearchVector)
.HasComputedColumnSql("to_tsvector('english', coalesce(\"Title\", '') || ' ' || coalesce(\"PlainTextContent\", ''))", stored: true);
你会看到我们在这里使用 HasComputedColumnSql
以明确指定 PostGreSQL 函数来生成搜索矢量。 我们还具体说明,该栏目储存在数据库中。 这一点很重要,因为它告诉 Postgres 将搜索矢量存储在数据库中 。 这样我们就可以使用搜索矢量搜索文档中的单词 。
在数据库中, 这是为每行生成的, 它们是文档中的“ lexemes” 及其位置 :
"'1992':464 '1996':468 '20':480 '200':115 '2007':426 '2009':428 '2012':88 '2015':397 '2018':370 '2020':372 '2021':288,327,329,399 '2022':196,243,245,290 '2024':156,158,198 '25':21,477,486,522 '3d':346 '6':203,256 '8':179,485 '90':120,566 'ab':282 'access':221 'accomplish':14 'achiev':118 'across':60 'adapt':579 'advanc':134 'applic':168,316,526 'apr':155,197 'architect':83,97,159 'architectur':307,337 ...
然后,我们可以使用这一栏来搜索文件中的字词。 我们可以使用 Matches
函数在搜索矢量中查找查询。 我们也可以使用 Rank
函数按关联性排列结果的排序。
var posts = await context.BlogPosts
.Include(x => x.Categories)
.Include(x => x.LanguageEntity)
.Where(x =>
// Search using the precomputed SearchVector
x.SearchVector.Matches(EF.Functions.ToTsQuery("english", query + ":*")) // Use precomputed SearchVector for title and content
&& x.Categories.Any(c =>
EF.Functions.ToTsVector("english", c.Name)
.Matches(EF.Functions.ToTsQuery("english", query + ":*"))) // Search in categories
&& x.LanguageEntity.Name == "en") // Filter by language
.OrderByDescending(x =>
// Rank based on the precomputed SearchVector
x.SearchVector.Rank(EF.Functions.ToTsQuery("english", query + ":*"))) // Use precomputed SearchVector for ranking
.Select(x => new { x.Title, x.Slug })
.ToListAsync();
你看这里,我们用不同的查询构建器 EF.Functions.ToTsQuery("english", query + ":*")
它使我们能够提供一种“头型”功能(我们可以在其中输入,例如: "猫"和得到"猫","猫","猫","猫毛虫"等等。
此外,它让我们简化主要博客主博客的海报查询,以便搜索该页面中的查询。 SearchVector
列内。 这是一个强大的特点,使我们能够在《世界人权宣言》和《世界人权宣言》中寻找词语。 Title
, PlainTextContent
.. 我们仍然使用上面显示的索引 CategoryEntity
.
x.Categories.Any(c =>
EF.Functions.ToTsVector("english", c.Name)
.Matches(EF.Functions.ToTsQuery("english", query + ":*")))
然后我们使用 Rank
函数根据查询按相关性排列结果。
x.SearchVector.Rank(EF.Functions.ToTsQuery("english", query + ":*")))
这让我们可以使用以下的终点, 我们可以在开头几个字中通过, 并拿回所有符合该词的文章:
您可以查看 API 此处行动 查找 /api/SearchApi
.. (注意; 我启用了这个网站的 Swagger, 这样您就可以在操作中看到 API, 但大部分时间应该保留给 `Is Development () 。 )
未来我会在使用此功能的网站的搜索框中 添加一个前型特征
你可以看到,使用Postgres和实体框架 可以获得强大的搜索功能。 然而,它有复杂和局限,我们需要说明(如语言问题)。 下一部分我会用OpenSearch 来报导我们如何做这个工作—— 开放搜索系统有一大堆的装置,但更强大,更可扩缩。