作者选择了 开放式互联网 / 自由言论基金作为 写给捐赠计划的一部分接受捐款。
介绍
MongoDB 查询通过搜索准确匹配、使用比比较大或小或使用常规表达式来过滤数据在许多情况下都足够有效,但是当涉及到对包含丰富文本数据的字段进行过滤时,这些方法不足。
想象一下,你在网页搜索引擎中输入了咖啡食谱
,但它只返回了包含这句话的页面,在这种情况下,你可能无法找到你正在寻找的内容,因为大多数有咖啡食谱的流行的网站可能不包含咖啡食谱
的确切短语,但是,如果你要在真正的搜索引擎中输入这个短语,你可能会找到有类似咖啡饮料(带食谱!)
或咖啡店饮料和疗法,你可以在家做
等标题的页面,在这些例子中,咖啡
一词存在,但标题中含有食谱
一词的另一种形式,或者完全排除。
将文本匹配到搜索查询的这种灵活程度是专门搜索文本数据的全文本搜索引擎的典型特征。对于这些应用程序,有多个专门的开源工具,而 ElasticSearch是特别受欢迎的选择,然而,对于不需要专用搜索引擎中强大的搜索功能的场景,一些通用数据库管理系统提供自己的全文本搜索功能。
在本教程中,您将通过示例学习如何在MongoDB中创建文本索引,并使用它来搜索数据库中的文档,而不是常见的全文本搜索查询和过滤器。
前提条件
要遵循本教程,您将需要:
- 拥有
sudo
特权和与 UFW 配置的防火墙的常规非根用户的服务器. 本教程是使用运行 Ubuntu 20.04 的服务器进行验证的,您可以通过遵循此(Ubuntu 20.04 的初始服务器设置教程)来准备您的服务器(https://andsky.com/tech/tutorials/initial-server-setup-with-ubuntu-20-04)。 - MongoDB 安装在您的服务器上。 要设置此项,请遵循我们的教程(How to Install MongoDB on Ubuntu 20.04)(https://andsky.com/tech/tutorials/how-to-install-mongodb-on-ubuntu-20-04)。
- 您的服务器的 MongoDB 实例通过启用身份验证和创建管理用户而得到保护。 为了确保 MongoDB 像这样,请遵循我们的教程(How To Secure MongoDB on Ubuntu 20.04)(https://andsky.com/tech/tutorials/how-to-secure-mongodb-on-ubuntu-20-04). _M
<$>[注] **注:**有关如何配置您的服务器,安装MongoDB和安全MongoDB安装的链接教程参考Ubuntu 20.04.本教程专注于MongoDB本身,而不是潜在的操作系统。
步骤 1 – 准备测试数据
为了帮助您了解如何在 MongoDB 中执行全文本搜索,此步骤概述了如何打开 MongoDB 壳以连接到本地安装的 MongoDB 实例。它还解释了如何创建样本集合并插入一些样本文档。
要创建此样本集合,请连接到 MongoDB 壳作为您的管理用户。本教程遵循前提教程(MongoDB 安全教程)(https://andsky.com/tech/tutorials/how-to-secure-mongodb-on-ubuntu-20-04)的惯例,并假定该管理用户的名称是 AdminSammy,其身份验证数据库是 admin
。
1mongo -u AdminSammy -p --authenticationDatabase admin
输入您在安装过程中设置的密码以获取存取壳. 提供密码后,您的提示将更改为大于一个标志:
<$>[注]
注: 新连接时,MongoDB 壳将默认连接到测试
数据库,您可以安全地使用此数据库来实验 MongoDB 和 MongoDB 壳。
或者,您可以切换到另一个数据库以运行本教程中提供的所有示例命令. 要切换到另一个数据库,请运行使用
命令,然后是您的数据库名称:
1use database_name
美元
要了解如何将全文本搜索应用于MongoDB中的文档,您需要一系列可以过滤的文档。本指南将使用一系列包括几个不同类型的咖啡饮料的名称和描述的样本文档。
1[label Example Cafecito document]
2{
3 "name": "Cafecito",
4 "description": "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam."
5}
该文件包含两个字段:咖啡饮料的名称
和更长的描述
,提供有关饮料及其成分的一些背景信息。
在 MongoDB 壳中运行以下insertMany()
方法,创建一个名为食谱
的集合,并同时将五个样本文档插入其中:
1db.recipes.insertMany([
2 {"name": "Cafecito", "description": "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam."},
3 {"name": "New Orleans Coffee", "description": "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory."},
4 {"name": "Affogato", "description": "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream."},
5 {"name": "Maple Latte", "description": "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup."},
6 {"name": "Pumpkin Spice Latte", "description": "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree."}
7])
此方法将返回新插入对象分配的对象标识符列表:
1[secondary_label Output]
2{
3 "acknowledged" : true,
4 "insertedIds" : [
5 ObjectId("61895d2787f246b334ece911"),
6 ObjectId("61895d2787f246b334ece912"),
7 ObjectId("61895d2787f246b334ece913"),
8 ObjectId("61895d2787f246b334ece914"),
9 ObjectId("61895d2787f246b334ece915")
10 ]
11}
您可以通过在食谱
集合中运行find()
方法来验证文档是否正确插入,而没有参数。
1db.recipes.find()
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }
3. . .
有了样本数据,您可以开始学习如何使用MongoDB的全文本搜索功能。
步骤 2 – 创建文本索引
要开始使用 MongoDB 的全文本搜索功能,您必须在集合中创建文本索引。 Indexes 是专门的数据结构,仅存储从集合中的每个文档中单独存储一个小部分数据。
当用户创建文本索引时,MongoDB 会自动从搜索中删除任何特定语言的 _stop 单词,这意味着MongoDB 会忽略特定语言中最常见的单词(英语中,如a
,an
,the
或this
)
MongoDB 还将在搜索中实施一种形式的 suffix-stemming,这涉及到 MongoDB 识别搜索术语的根部分,并将该根的其他语法形式(通过添加-ing
、-ed
或-er
)来对待为搜索目的的根。
由于这些和其他功能,MongoDB 可以更灵活地支持用自然语言编写的查询,并提供更好的结果。
<$>[注] **注:**本教程专注于英语文本,但MongoDB在使用全文本搜索和文本索引时支持多个语言。
您只能为任何 MongoDB 集合创建一个文本索引,但可以使用多个字段创建该索引. 在我们的示例集合中,每个文档的名称
和描述
字段中都存储有用的文本。
运行以下createIndex()
方法,为两个字段创建文本索引:
1db.recipes.createIndex({ "name": "text", "description": "text" });
对于两个字段,名称和描述,索引类型被设置为文本
,告诉MongoDB创建一个基于这些字段的全文本搜索定制的文本索引。
1[secondary_label Output]
2{
3 "createdCollectionAutomatically" : false,
4 "numIndexesBefore" : 1,
5 "numIndexesAfter" : 2,
6 "ok" : 1
7}
现在你已经创建了索引,你可以使用它发送完整文本的搜索查询到数据库. 在下一步,你将学习如何执行包含单词和多个单词的查询。
步骤 3 – 搜索一个或多个单独单词
也许最常见的搜索问题是搜索包含一个或多个单个单词的文档。
例如,如果您使用任何流行的网页搜索引擎并键入咖啡甜味香味
,您可能并不期望结果将包含这三个单词在这个准确的顺序。
这也是MongoDB在使用文本索引时采用典型的搜索查询的方式,此步骤概述了MongoDB如何用几个例子解释搜索查询。
首先,假设您想在配方中搜索含有香料的咖啡饮料,所以您只使用以下命令搜索香料
一词:
1db.recipes.find({ $text: { $search: "spiced" } });
请注意,在使用全文本搜索时,语法略有不同于常规查询。单个字段名称(如名称
或描述
)不会出现在过滤文件中。相反,查询使用了$text
操作员,告诉MongoDB这个查询旨在使用您之前创建的文本索引。您不需要比这更具体,因为,如您所记得的,一个集合可能只有一个文本索引。
运行此命令后,MongoDB 会生成以下文档列表:
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree." }
3{ "_id" : ObjectId("61895d2787f246b334ece912"), "name" : "New Orleans Coffee", "description" : "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory." }
结果集中有两个文档,两者都包含类似搜索查询的单词.虽然在描述中New Orleans Coffee
文档中确实有spiced
,但在Pumpkin Spice Late
文档中却没有。
尽管如此,它仍然被这个查询返回,由于MongoDB使用了源代码。MongoDB将香料
一词删除为仅仅香料
,在索引中查看香料
,并将其标记为香料
。
现在,假设你特别喜欢精品饮料,试着用两个单词的查询来搜索文件,即精品精品
,以寻找精品咖啡。
1db.recipes.find({ $text: { $search: "spiced espresso" } });
结果列表这一次比以前更长:
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece914"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup." }
3{ "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." }
4{ "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }
5{ "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree." }
6{ "_id" : ObjectId("61895d2787f246b334ece912"), "name" : "New Orleans Coffee", "description" : "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory." }
在搜索查询中使用多个单词时,MongoDB 会执行一个逻辑的OR
操作,因此文档只需要匹配一部分表达式以被包含在结果集中。
<$>[注] 注: 如果您尝试在没有定义文本索引的集合上执行任何全文本搜索查询,MongoDB 将返回一个错误消息:
1[secondary_label Error message]
2Error: error: {
3 "ok" : 0,
4 "errmsg" : "text index required for $text query",
5 "code" : 27,
6 "codeName" : "IndexNotFound"
7}
美元
在此步骤中,您了解如何使用一个或多个单词作为文本搜索查询,MongoDB 如何将多个单词与一个逻辑的OR
操作结合在一起,以及MongoDB 如何执行干扰。
步骤 4 – 搜索完整的句子和使用排除
查看单个单词可能会返回太多结果,或者结果可能不够准确. 在此步骤中,您将使用短语搜索和排除来更准确地控制搜索结果。
假设你有一颗甜蜜的牙齿,外面很热,冰淇淋覆盖的咖啡听起来像一个好吃。
1db.recipes.find({ $text: { $search: "ice cream" } });
数据库将返回两种咖啡食谱:
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." }
3{ "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }
虽然Affogato
文档符合您的期望,但Cafecito
不是用冰淇淋制作的,搜索引擎使用逻辑的OR
操作接受了第二个结果,因为描述中出现了cream
这个词。
要告诉 MongoDB 您正在寻找冰淇淋
作为一个完整的短语,而不是两个单独的单词,请使用以下查询:
1db.recipes.find({ $text: { $search: "\"ice cream\"" } });
注意每个围绕短语的双重引用前面的反射。你正在执行的搜索查询是冰淇淋
,双重引用表示应该准确匹配的短语。
此时,MongoDB 返回一个结果:
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." }
此文档完全匹配搜索术语,单靠奶油
或冰
都不足以作为匹配。
另一个有用的全文本搜索功能是排除编辑器. 为了说明如何做到这一点,首先运行以下查询,以获取集合中基于咖啡饮料的所有列表:
1db.recipes.find({ $text: { $search: "espresso" } });
此查询返回四个文档:
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece914"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup." }
3{ "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." }
4{ "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree." }
5{ "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }
请注意,这些饮料中有两种是含有牛奶的,但假设您想要一杯无牛奶的饮料. 这是一个例子,其中排除可能有用。 在单个查询中,您可以将您想要在结果中显示的单词与您想要排除的单词相结合,通过将您想要排除的单词或短语以减数符号(-
)提前列出。
例如,假设您运行以下查询来搜索不含牛奶的咖啡:
1db.recipes.find({ $text: { $search: "espresso -milk" } });
使用此查询,将从以前返回的结果中排除两个文档:
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream." }
3{ "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }
您也可以排除完整的句子. 要搜索没有冰淇淋的咖啡,您可以在搜索查询中包含-
冰淇淋` 。
1db.recipes.find({ $text: { $search: "espresso -\"ice cream\"" } });
1[secondary_label Output]
2{ "_id" : ObjectId("61d48c31a285f8250c8dd5e6"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup." }
3{ "_id" : ObjectId("61d48c31a285f8250c8dd5e7"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree." }
4{ "_id" : ObjectId("61d48c31a285f8250c8dd5e3"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam." }
现在你已经学会了如何根据由多个单词组成的短语过滤文档,以及如何从搜索结果中排除某些单词和短语,你可以熟悉MongoDB的全文本搜索分数。
步骤5 – 得分结果和分类得分
当一个查询,特别是一个复杂的查询,返回多个结果时,一些文档可能比其他更好的匹配。
全文本搜索引擎通常会为搜索结果分配相关性分数,表示它们对应搜索查询的程度。
再次搜索菜菜
,但这次MongoDB还会返回每个结果的搜索相关性分数。
1db.recipes.find(
2 { $text: { $search: "spiced espresso" } },
3 { score: { $meta: "textScore" } }
4)
投影 { score: { $meta: "textScore" }' 使用了
$meta' 运算符,这是一种特殊类型的投影,从返回的文档中返回特定元数据. 此示例返回了文档 `textScore' 元数据,这是 MongoDB 完整文本搜索引擎内置的功能,其中包含搜索相关性分数。
执行查询后,返回的文档将包含一个名为分数
的新字段,如过滤文件中所述:
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream.", "score" : 0.5454545454545454 }
3{ "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam.", "score" : 0.5384615384615384 }
4{ "_id" : ObjectId("61895d2787f246b334ece914"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup.", "score" : 0.55 }
5{ "_id" : ObjectId("61895d2787f246b334ece912"), "name" : "New Orleans Coffee", "description" : "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory.", "score" : 0.5454545454545454 }
6{ "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree.", "score" : 2.0705128205128203 }
请注意Pumpkin Spice Latte
的分数是多少,这是唯一含有spiced
和espresso
两个字的咖啡饮料。 根据MongoDB的相关性分数,这是该查询中最相关的文档。
要更改这一点,您可以将一个‘sort()’条款添加到查询中,如下:
1db.recipes.find(
2 { $text: { $search: "spiced espresso" } },
3 { score: { $meta: "textScore" } }
4).sort(
5 { score: { $meta: "textScore" } }
6);
排序文档的语法与投影的语法相同.现在,文档列表是相同的,但它们的顺序不同:
1[secondary_label Output]
2{ "_id" : ObjectId("61895d2787f246b334ece915"), "name" : "Pumpkin Spice Latte", "description" : "It wouldn't be autumn without pumpkin spice lattes made with espresso, steamed milk, cinnamon spices, and pumpkin puree.", "score" : 2.0705128205128203 }
3{ "_id" : ObjectId("61895d2787f246b334ece914"), "name" : "Maple Latte", "description" : "A wintertime classic made with espresso and steamed milk and sweetened with some maple syrup.", "score" : 0.55 }
4{ "_id" : ObjectId("61895d2787f246b334ece913"), "name" : "Affogato", "description" : "An Italian sweet dessert coffee made with fresh-brewed espresso and vanilla ice cream.", "score" : 0.5454545454545454 }
5{ "_id" : ObjectId("61895d2787f246b334ece912"), "name" : "New Orleans Coffee", "description" : "Cafe Noir from New Orleans is a spiced, nutty coffee made with chicory.", "score" : 0.5454545454545454 }
6{ "_id" : ObjectId("61895d2787f246b334ece911"), "name" : "Cafecito", "description" : "A sweet and rich Cuban hot coffee made by topping an espresso shot with a thick sugar cream foam.", "score" : 0.5384615384615384 }
Pumpkin Spice Latte
文件出现在第一个结果,因为它具有最高的相关性分数。
根据相关性分类结果可能有帮助,这尤其适用于包含多个单词的查询,其中最合适的文档通常包含多个搜索术语,而不相关的文档可能只包含一个。
结论
通过遵循本教程,您已经熟悉了MongoDB的全文本搜索功能,您创建了一个文本索引,并使用单词和多个单词,完整的短语和排除来写文本搜索查询,您还评估了返回文档的相关性分数,并将搜索结果排序为显示最相关的结果。
请注意,在单个文本索引中,有更多的搜索查询编辑器(如案例和图形敏感性和多种语言支持)可用于更强大的场景,以支持文本搜索应用程序。