我有一个 MongoDB 文档集合。我已经为特定字段分配了权重,但我需要将具有任何非空名称的记录加权到顶部。我不想按名称排序,我只是希望有名称的记录出现在没有名称的记录之前。
示例架构:
new Schema({
slug: {
type: String,
index: {unique: true, dropDups: true}
},
name: String,
body: {
type: String,
required: true
}
});
索引示例:
MySchema.index({
name:'text',
body:'text'
}, {
name: 'best_match_index',
weights: {
name: 10,
body: 1
}
});
查找查询:
MyModel.find( criteria, { score : { $meta: 'textScore' } })
.sort({ score : { $meta : 'textScore' } })
.skip(offset)
.limit(per_page)
最佳答案
如果我理解你的意思,你所说的是给定的文件如下:
{ "name" : "term", "body" : "unrelated" }
{ "name" : "unrelated", "body" : "unrelated" }
{ "body" : "term" }
{ "body" : "term term" }
{ "name" : "unrelated", "body" : "term" }
正常搜索“term”会产生如下结果:
{ "name" : "term", "body" : "unrelated", "score" : 11 }
{ "body" : "term term", "score" : 1.5 }
{ "body" : "term", "score" : 1.1 }
{ "name" : "unrelated", "body" : "term", "score" : 1.1 }
但是您想要的是将最后一个条目作为第二个条目。
为此,您需要将另一个字段“动态”投影到“权重”,您将在其中使用聚合框架:
MyModel.aggregate([
{ "$match": {
"$text": { "$search": "term" }
}},
{ "$project": {
"slug": 1,
"name": 1,
"body": 1,
"textScore": { "$meta": "textScore" },
"nameScore": {
"$cond": [
{ "$ne": [{ "$ifNull": [ "$name", "" ] }, ""] },
1,
0
]
}
}},
{ "$sort": { "nameScore": -1, "textScore": -1 } },
{ "$skip": offset },
{ "$limit": per_page }
],function(err,results) {
if (err) throw err;
console.log( results );
})
这会将带有“名称”字段的项目放在没有“名称”字段的项目之上:
{ "name" : "term", "body" : "unrelelated", "textScore" : 11, "nameScore" : 1 }
{ "name" : "unrelated", "body" : "term", "textScore" : 1.1, "nameScore" : 1 }
{ "body" : "term term", "textScore" : 1.5, "nameScore" : 0 }
{ "body" : "term", "textScore" : 1.1, "nameScore" : 0 }
本质上, $ifNull
三元中的 $cond
运算符测试“name”字段是否存在,然后在存在的情况下返回 1,在不存在的情况下返回 0。
这会传递到 $sort
管道,您的排序首先在“nameScore”上,将这些项目 float 到顶部,然后在“textScore”上。
聚合管道有自己的 $skip
和 $limit
实现,用于分页。
这本质上与 .find()
实现中的操作集相同,包括“匹配”、“项目”、“排序”、“跳过”和“限制”。因此,处理方式实际上没有什么区别,只是对结果有更多的控制。
使用“skip”和“limit”实际上并不是最有效的解决方案,但有时您会遇到困难,例如在需要提供“页码”的情况下。 但是,如果您可以摆脱它并且只需要向前移动,那么您可以尝试将最后看到的“textScore”和“seen_ids”列表跟踪到一定程度的粒度,具体取决于“textScore”值的分布方式是。这些可以作为“跳过”结果的替代方法传入:
MyModel.aggregate([
{ "$match": {
"$text": { "$search": "term" }
}},
{ "$project": {
"slug": 1,
"name": 1,
"body": 1,
"textScore": { "$meta": "textScore" },
"nameScore": {
"$cond": [
{ "$ne": [{ "$ifNull": [ "$name", "" ] }, ""] },
1,
0
]
}
}},
{ "$match": {
"_id": { "$nin": seen_ids }
"textScore": { "$gte": last_score },
}},
{ "$sort": { "nameScore": -1, "textScore": -1 } },
{ "$limit": page_size }
])
这里唯一稍微不幸的是,textScore 的 $meta
还无法暴露给初始 $match
操作,这将有助于缩小结果范围,而无需首先运行 $project
。
因此,实际上您无法进行与专用 $geoNear
运算符等操作相同的完全优化,但文本版本或允许前一个语句会很好。
您可能会注意到,从 .aggregate()
选项返回的对象只是原始 JavaScript 对象,而不是从 .find()
等操作返回的 Mongoose“文档”对象。这是“设计使然”,这里的主要原因是,由于聚合框架允许您“操作”结果文档,因此不能保证这些文档实际上与您最初查询的模式中的文档相同.
由于您并没有真正按照预期目的“更改”或“重新塑造”文档,因此现在它只是退回到您的代码来执行 mongoose 在幕后自动执行的操作,并将每个原始结果“转换”到标准“类型”。
此列表通常应该显示您需要做什么:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect("mongodb://localhost/test");
var testSchema = new Schema({
name: String,
body: { type: String, required: true },
textScore: Number,
nameScore: Number
},{
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
testSchema.virtual('favourite').get(function() {
return "Fred";
});
var Test = mongoose.model( "Test", testSchema, "textscore" );
Test.aggregate([
{ "$match": {
"$text": { "$search": "term" }
}},
{ "$project": {
"name": 1,
"body": 1,
"textScore": { "$meta": "textScore" },
"nameScore": {
"$cond": [
{ "$ne": [{ "$ifNull": [ "$name", "" ] }, "" ] },
1,
0
]
}
}},
{ "$sort": { "nameScore": -1, "textScore": -1 } },
],function(err,result) {
if (err) throw err;
result = result.map(function(doc) {
return new Test( doc );
});
console.log( JSON.stringify( result, undefined, 4 ));
process.exit();
});
其中包括输出中的“虚拟”字段:
[
{
"_id": "53d1a9b501e1b6c73aed2b52",
"name": "term",
"body": "unrelelated",
"favourite": "Fred",
"id": "53d1a9b501e1b6c73aed2b52"
},
{
"_id": "53d1ae1a01e1b6c73aed2b56",
"name": "unrelated",
"body": "term",
"favourite": "Fred",
"id": "53d1ae1a01e1b6c73aed2b56"
},
{
"_id": "53d1ada301e1b6c73aed2b55",
"body": "term term",
"favourite": "Fred",
"id": "53d1ada301e1b6c73aed2b55"
},
{
"_id": "53d1ad9e01e1b6c73aed2b54",
"body": "term",
"favourite": "Fred",
"id": "53d1ad9e01e1b6c73aed2b54"
}
]
关于node.js - 具有非空字段的 MongoDB/Mongoose 权重记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24946079/