Node.js 非空字段的MongoDB/Mongoose重量记录

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 } }

我有一个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" }
{ "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你似乎在问这个问题。这只是一个“铸造”结果中的文档的问题。一个答案的价格是两个,因为这相当简单。