node.js - 具有非空字段的 MongoDB/Mongoose 权重记录

标签 node.js mongodb mongoose aggregation-framework

我有一个 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/

相关文章:

mongodb - Mongoose JS promise ?或者如何管理批量保存

javascript - MongoDB 更新功能不适用于 Mongoose

mysql - Redis/Memcached/MongoDB(或任何 NoSQL 系统)是否支持 MySQL 的 ON DUPLICATE KEY UPDATE?

java - MongoDB 中基于日期排序

Mongoose 聚合和有条件求和

请求未返回正确值的 Javascript 函数

windows - 如何在 Windows 启动时添加 mongoDB 服务?

node.js - npm install windows-build-tools failed 这可能是无害的

javascript - react redux 更新状态 onClick 函数与多个 Action

node.js - 如何在 Promise 中使用异步函数?