Node.js 非空字段的MongoDB/Mongoose重量记录
我有一个MongoDB文档集。我已经为特定字段分配了权重,但我需要将具有任何非空名称的记录的权重分配到顶部。我不想按名称排序,我只希望有名称的记录出现在没有名称的记录之前 一个示例模式:Node.js 非空字段的MongoDB/Mongoose重量记录,node.js,mongodb,mongoose,aggregation-framework,Node.js,Mongodb,Mongoose,Aggregation Framework,我有一个MongoDB文档集。我已经为特定字段分配了权重,但我需要将具有任何非空名称的记录的权重分配到顶部。我不想按名称排序,我只希望有名称的记录出现在没有名称的记录之前 一个示例模式: new Schema({ slug: { type: String, index: {unique: true, dropDups: true} }, name: String, body: { type: String, required: true } }
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" }
{ "name" : "term", "body" : "unrelated", "score" : 11 }
{ "body" : "term term", "score" : 1.5 }
{ "body" : "term", "score" : 1.1 }
{ "name" : "unrelated", "body" : "term", "score" : 1.1 }
对“术语”的正常搜索将产生如下结果:
{ "name" : "term", "body" : "unrelated" }
{ "name" : "unrelated", "body" : "unrelated" }
{ "body" : "term" }
{ "body" : "term term" }
{ "name" : "unrelated", "body" : "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 }
基本上,三元内的运算符测试“name”字段的存在,然后在存在时返回1,在不存在时返回0
这将被传递到管道中,您的排序在“nameScore”上,首先将这些项浮动到顶部,然后浮动到“textScore”
聚合管道有自己的和实现,用于分页
这基本上与.find()
实现中的操作集相同,带有“匹配”、“项目”、“排序”、“跳过”和“限制”。所以在处理过程中没有什么不同,只是对结果有了更多的控制
使用“skip”和“limit”并不是最有效的解决方案,但有时您会被它卡住,例如在需要提供“页面编号”的情况下。
但是,如果你能侥幸逃脱,并且只需要向前移动,那么你可以尝试跟踪最后一次看到的“textScore”和“seen_id”列表到一定的粒度级别,这取决于“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的for还不能暴露于初始操作,这将有助于缩小结果范围,而无需先运行
因此,实际上,您不能像使用专用运算符那样进行相同的完整的优化,但是使用文本版本或允许使用前一条语句会更好
您可能会注意到,从
.aggregate()
选项返回的对象只是原始JavaScript对象,而不是从.find()
等操作返回的Mongoose“document”对象。这是“设计的”,这里的主要原因是,由于聚合框架允许您“操作”生成的文档,因此无法保证这些文档实际上与您最初查询的模式中的文档相同
由于您并没有真正按照预期的目的“更改”或“重新塑造”文档,因此现在只能依靠您的代码来完成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"
}
]
除了在
{name:-1,score:{$meta:'textScore'}
上排序外,我没能走多远。所以,祝你好运!@JustinCase这个问题的答案是使用聚合框架的“投影”功能,本质上你提供了一个额外的字段来“加权”排序。非常详细的答案。你每天都会学到一些新东西($ifNull)!@JustinCase本质上是$的聚合“评估”版本存在
,但它不只是一个测试,而是返回现有字段或替代结果。非常适合生成“非空”数组,因此,$unwind
语句不会爆炸,也不会对不存在的字段使用其他常规默认值。这似乎可行,我需要进行更多测试,但我注意到模式中的虚拟字段不再有效-我需要做些额外的事情吗?@helion3虚拟字段,例如mongoose模式中的虚拟字段?我收集您的信息你想引用或返回它吗?这就是你要问的吗?@helion3你似乎在问这个问题。这只是一个“铸造”结果中的文档的问题。一个答案的价格是两个,因为这相当简单。