使用权重在猫鼬中搜索
Using weights for searching in mongoose
所以我已经通读了 this,但对于如何处理这个问题仍然有些困惑。
我的模型包含各种字段,包括字符串、数字和布尔值。
$text 好像只能接受字符串。
如果我想进行如下搜索怎么办:
model.find({petsAllowed:true, rooms:4, house:"townhouse"}).sort()
因此让它搜索 mongodb 中与我输入的内容匹配的所有不同条目,并根据条目与输入字段的接近程度对其进行排序。
我知道猫鼬支持这个所以我不想依赖插件。
这是我想要的结果:
[
Document 1 (most closely matched with the input):
{petsAllowed:true, rooms:4, house:"townhouse"},
Document 2: {petsAllowed:false, rooms:4, house:"townhouse"},
Document 3: {petsAllowed:true, rooms:5, house:"townhouse"},
Document 4: {petsAllowed:false, rooms:3, house:"townhouse"}
]
为了 "weight" 响应,基本原则是您必须确定结果的哪些部分对您正在执行的搜索更重要,并且基本上按照重要性的顺序提供适当的分数由于您的规则而产生的结果。
这确实是 MongoDB 的东西,而不是外部编码的东西,因为您需要在服务器上分析结果,尤其是当您考虑 "paging" 之类的加权结果时很多。要在服务器上执行此操作,您需要 .aggregate()
方法。
通过这个工作,我在等待您的输入时已经有了自己的数据样本,但它仍然可以作为示例。考虑到这个初始样本。
{ "petsAllowed" : true, "rooms" : 5, "type" : "townhouse" }
{ "petsAllowed" : false, "rooms" : 4, "type" : "house" }
{ "petsAllowed" : true, "rooms" : 4, "type" : "townhouse" }
{ "petsAllowed" : false, "rooms" : 4, "type" : "townhouse" }
{ "petsAllowed" : true, "rooms" : 2, "type" : "townhouse" }
{ "petsAllowed" : true, "rooms" : 3, "type" : "townhouse" }
{ "petsAllowed" : true, "rooms" : 4, "type" : "house" }
所以这还包括一个 "type",我们也将在比赛中 "fuzzy" 而不仅仅是确定 "exact" 比赛。使用聚合管道并根据您的输入设置逻辑基本上是这样的:
var roomsWanted = 4,
exact = "townhouse",
types = [];
// Some logic to get the "fuzzy" values
var fuzzy = [/house/]
// Combine exact and fuzzy
types.push(exact);
fuzzy.forEach(function(fuzz) {
types.push(fuzz);
});
// Perform the query
db.houses.aggregate([
// Match items you want and exclude others
{ "$match": {
"type": { "$in": types },
"$or": [
{ "rooms": { "$gte": roomsWanted } },
{ "rooms": roomsWanted - 1 }
]
}},
// Calculate a score
{ "$project": {
"petsAllowed": 1,
"rooms": 1,
"type": 1,
"score": {
"$add": [
// Exact match is higher than the fuzzy ones
// Fuzzy ones score lower than other possible matches
{ "$cond": [
{ "$eq": [ "$type", "townhouse" ] },
20,
2
]},
// When petsAllowed is true you want a weight
{ "$cond": [
"$petsAllowed",
10,
0
]},
// Score depending on the roomsWanted
{ "$cond": [
{ "$eq": [ "$rooms", roomsWanted ] },
5,
{ "$cond": [
{ "$gt": [ "$rooms", roomsWanted ] },
4,
{ "$cond": [
{ "$eq": [ "$rooms", roomsWanted - 1 ] },
3,
0
]}
]}
]}
]
}
}},
{ "$sort": { "score": -1 } },
])
然后您得到的结果按生成的 "score" 排序,如下所示:
{ "petsAllowed" : true, "rooms" : 4, "type" : "townhouse", "score" : 35 }
{ "petsAllowed" : true, "rooms" : 5, "type" : "townhouse", "score" : 34 }
{ "petsAllowed" : true, "rooms" : 3, "type" : "townhouse", "score" : 33 }
{ "petsAllowed" : false, "rooms" : 4, "type" : "townhouse", "score" : 25 }
{ "petsAllowed" : true, "rooms" : 4, "type" : "house", "score" : 17 }
{ "petsAllowed" : false, "rooms" : 4, "type" : "house", "score" : 7 }
将其分解为这里发生的事情,第一件事是我自己的决定,我可能想要 "type" 中包含 "house" 的任何内容以及任何 "exact matches"选择的类型。这是确定这一点的任意逻辑,但关键是我们将在这个例子中考虑两者。
当然搜索会想过滤掉任何你真的不想要的东西,所以有一个 $match
pipeline stage to do this. The $in
运算符用于匹配 "type" 到精确的 "townhouse" 术语或 /house/
的可能正则表达式匹配。那是因为我也想要它,你的里程可能会因你真正想做的事情而有所不同。
还有一个条件就是找房间数。同样,这里的任意决定是我将同时考虑具有四个或更多房间的任何东西,因此 **$gte**
条件。我还想考虑比要求的空间少一个的东西。又是武断的逻辑,但只是为了证明您在需要时所做的事情的意义。
在 $match
完成后 "filtering",您将结果移至 $project
阶段。这里的要点是您需要计算出的 "score" 值,但在使用此管道阶段时,您还必须指定 所有 要 return 的字段。
这里是您对 "weight" 应用于条件的一些选择。 $add
operator will "sum" results that are given as it's arguments, which are in turn produced by the $cond
或 "conditional" 运算符。
这是一个 "ternary" 运算符,因为它将逻辑 "if" 条件计算为第一个参数,然后 return 是 true
第二个参数或false
第三个参数。像任何三元一样,当您想测试 "nest" false
参数中的运算符的不同条件以便 "flow through" 它们时。
一旦 "score" 被确定,你 $sort
结果按最大 "score" 的顺序排在第一位。
可以通过添加 $skip
and $limit
pipeline stages at the end of the pipeline, or by more involved "forward paging" by keeping the last value(s) seen and excluding those from the results looking for a "score" $lte
最后看到的 "score" 以传统形式实现分页。这本身就是另一个话题,但这完全取决于哪种分页概念最适合您的应用程序。
当然,对于像"petsAllowed"这样的一些逻辑,您只希望那些条件在实际对您想要的选择标准有效时计算权重。聚合管道语法和所有 MongoDB 查询语法的部分优点在于,无论语言实现如何,它基本上只是 "data structure" 的表示。因此,您可以根据输入的要求 "build" 管道阶段,就像代码中的任何数据结构一样。
这些是原则,但当然一切都是有代价的,计算这些权重 "on the fly" 不仅仅是一个可以在索引中查找值的简单查询。
所以我已经通读了 this,但对于如何处理这个问题仍然有些困惑。
我的模型包含各种字段,包括字符串、数字和布尔值。
$text 好像只能接受字符串。
如果我想进行如下搜索怎么办:
model.find({petsAllowed:true, rooms:4, house:"townhouse"}).sort()
因此让它搜索 mongodb 中与我输入的内容匹配的所有不同条目,并根据条目与输入字段的接近程度对其进行排序。
我知道猫鼬支持这个所以我不想依赖插件。
这是我想要的结果:
[
Document 1 (most closely matched with the input):
{petsAllowed:true, rooms:4, house:"townhouse"},
Document 2: {petsAllowed:false, rooms:4, house:"townhouse"},
Document 3: {petsAllowed:true, rooms:5, house:"townhouse"},
Document 4: {petsAllowed:false, rooms:3, house:"townhouse"}
]
为了 "weight" 响应,基本原则是您必须确定结果的哪些部分对您正在执行的搜索更重要,并且基本上按照重要性的顺序提供适当的分数由于您的规则而产生的结果。
这确实是 MongoDB 的东西,而不是外部编码的东西,因为您需要在服务器上分析结果,尤其是当您考虑 "paging" 之类的加权结果时很多。要在服务器上执行此操作,您需要 .aggregate()
方法。
通过这个工作,我在等待您的输入时已经有了自己的数据样本,但它仍然可以作为示例。考虑到这个初始样本。
{ "petsAllowed" : true, "rooms" : 5, "type" : "townhouse" }
{ "petsAllowed" : false, "rooms" : 4, "type" : "house" }
{ "petsAllowed" : true, "rooms" : 4, "type" : "townhouse" }
{ "petsAllowed" : false, "rooms" : 4, "type" : "townhouse" }
{ "petsAllowed" : true, "rooms" : 2, "type" : "townhouse" }
{ "petsAllowed" : true, "rooms" : 3, "type" : "townhouse" }
{ "petsAllowed" : true, "rooms" : 4, "type" : "house" }
所以这还包括一个 "type",我们也将在比赛中 "fuzzy" 而不仅仅是确定 "exact" 比赛。使用聚合管道并根据您的输入设置逻辑基本上是这样的:
var roomsWanted = 4,
exact = "townhouse",
types = [];
// Some logic to get the "fuzzy" values
var fuzzy = [/house/]
// Combine exact and fuzzy
types.push(exact);
fuzzy.forEach(function(fuzz) {
types.push(fuzz);
});
// Perform the query
db.houses.aggregate([
// Match items you want and exclude others
{ "$match": {
"type": { "$in": types },
"$or": [
{ "rooms": { "$gte": roomsWanted } },
{ "rooms": roomsWanted - 1 }
]
}},
// Calculate a score
{ "$project": {
"petsAllowed": 1,
"rooms": 1,
"type": 1,
"score": {
"$add": [
// Exact match is higher than the fuzzy ones
// Fuzzy ones score lower than other possible matches
{ "$cond": [
{ "$eq": [ "$type", "townhouse" ] },
20,
2
]},
// When petsAllowed is true you want a weight
{ "$cond": [
"$petsAllowed",
10,
0
]},
// Score depending on the roomsWanted
{ "$cond": [
{ "$eq": [ "$rooms", roomsWanted ] },
5,
{ "$cond": [
{ "$gt": [ "$rooms", roomsWanted ] },
4,
{ "$cond": [
{ "$eq": [ "$rooms", roomsWanted - 1 ] },
3,
0
]}
]}
]}
]
}
}},
{ "$sort": { "score": -1 } },
])
然后您得到的结果按生成的 "score" 排序,如下所示:
{ "petsAllowed" : true, "rooms" : 4, "type" : "townhouse", "score" : 35 }
{ "petsAllowed" : true, "rooms" : 5, "type" : "townhouse", "score" : 34 }
{ "petsAllowed" : true, "rooms" : 3, "type" : "townhouse", "score" : 33 }
{ "petsAllowed" : false, "rooms" : 4, "type" : "townhouse", "score" : 25 }
{ "petsAllowed" : true, "rooms" : 4, "type" : "house", "score" : 17 }
{ "petsAllowed" : false, "rooms" : 4, "type" : "house", "score" : 7 }
将其分解为这里发生的事情,第一件事是我自己的决定,我可能想要 "type" 中包含 "house" 的任何内容以及任何 "exact matches"选择的类型。这是确定这一点的任意逻辑,但关键是我们将在这个例子中考虑两者。
当然搜索会想过滤掉任何你真的不想要的东西,所以有一个 $match
pipeline stage to do this. The $in
运算符用于匹配 "type" 到精确的 "townhouse" 术语或 /house/
的可能正则表达式匹配。那是因为我也想要它,你的里程可能会因你真正想做的事情而有所不同。
还有一个条件就是找房间数。同样,这里的任意决定是我将同时考虑具有四个或更多房间的任何东西,因此 **$gte**
条件。我还想考虑比要求的空间少一个的东西。又是武断的逻辑,但只是为了证明您在需要时所做的事情的意义。
在 $match
完成后 "filtering",您将结果移至 $project
阶段。这里的要点是您需要计算出的 "score" 值,但在使用此管道阶段时,您还必须指定 所有 要 return 的字段。
这里是您对 "weight" 应用于条件的一些选择。 $add
operator will "sum" results that are given as it's arguments, which are in turn produced by the $cond
或 "conditional" 运算符。
这是一个 "ternary" 运算符,因为它将逻辑 "if" 条件计算为第一个参数,然后 return 是 true
第二个参数或false
第三个参数。像任何三元一样,当您想测试 "nest" false
参数中的运算符的不同条件以便 "flow through" 它们时。
一旦 "score" 被确定,你 $sort
结果按最大 "score" 的顺序排在第一位。
可以通过添加 $skip
and $limit
pipeline stages at the end of the pipeline, or by more involved "forward paging" by keeping the last value(s) seen and excluding those from the results looking for a "score" $lte
最后看到的 "score" 以传统形式实现分页。这本身就是另一个话题,但这完全取决于哪种分页概念最适合您的应用程序。
当然,对于像"petsAllowed"这样的一些逻辑,您只希望那些条件在实际对您想要的选择标准有效时计算权重。聚合管道语法和所有 MongoDB 查询语法的部分优点在于,无论语言实现如何,它基本上只是 "data structure" 的表示。因此,您可以根据输入的要求 "build" 管道阶段,就像代码中的任何数据结构一样。
这些是原则,但当然一切都是有代价的,计算这些权重 "on the fly" 不仅仅是一个可以在索引中查找值的简单查询。