Mongodb 根据计算值对集合进行排序

Mongodb 根据计算值对集合进行排序,mongodb,mongoose,mongodb-query,aggregation-framework,Mongodb,Mongoose,Mongodb Query,Aggregation Framework,我有一个mongo数据库,包含两个集合: module.exports = mongoose.model('Article', { title : String, text : String, topics : Array, category : String }); module.exports = mongoose.model('User', { email : String, password : String,

我有一个mongo数据库,包含两个集合:

module.exports = mongoose.model('Article', {
    title : String,
    text : String,
    topics : Array,
    category : String
});

module.exports = mongoose.model('User', {
    email        : String,
    password     : String,
    topics       : [{
                        _id: false,
                        topic: String,
                        interest: {type: Number, default: 0}
                    }]
});
“文章主题”字段包含文章主题,而“用户主题”字段包含与用户相关的主题

我想做一个查询,返回按字段排序的文章,该字段是文章的主题数组和给定ID的用户(当前用户)的主题数组的线性组合

例如,如果文章主题为: [“食物”、“比萨饼”、“意大利面”] 用户主题为: [{主题:“汽车”,兴趣:2},{主题:“比萨饼”,兴趣:5},{主题:“意大利面”,兴趣:1}]

项目字段=5+1=6,因为比萨饼和意大利面重合

那么我想在那个领域下订单

我怎么做

我想得到这样的东西:

Users.findById(req.user._id).exec(function (err, user) {
            Article.aggregate([
                {
                    // projectField = sum user.topics.interest for any user.topics.topic in topics
                },
                { $sort : { projectField : -1} }
            ], function (err, articles) {
                console.log(articles);
            });
        });

您基本上需要通过
.aggregate()
将其输入,然后根据计算值进行排序

本质上是这样。您已经在内存中有选择的<代码>用户>代码>对象,但是我们会考虑它是一个选择,并继续从承诺:

User.findById(userId).then(user =>

  Arictle.aggregate([
    { "$addFields": {
      "score": {
        "$sum": {
          "$map": {
            "input": user.topics,
            "as": "u",
            "in": {
              "$cond": {
                "if": { "$setIsSubset": [ ["$$u.topic"], "$topics" ] },
                "then": "$$u.interest",
                "else": 0
              }
            }
          }
        }
      }
    }},
    { "$sort": { "score": -1 } }
  ])

).then( results => {
  // work with results here
});
因此,本质上我们从
用户
中提供“数组”,并将其作为参数提供给,它坦率地接受“任何数组”,并且不需要成为文档本身的一部分

迭代每个项目时,将与当前
文章中的
“主题”
进行比较,以查看
用户提供的数组中当前的
“主题”
是否匹配。这种比较是通过,记住将奇异值包装在一个数组中进行比较

当它匹配时,我们提供
兴趣
值,否则
0
。然后将“映射”数组馈送到,您将获得一个“分数”,然后可以在后一阶段对其进行排序

从技术上讲,您可以将输入提供给from,而不是使用inside来确定输出,但是这样做的语法“更简洁一些”,因此避免使用“交换”值

当一切都完成后,你只需在计算字段上,这就是你的结果

如果需要,添加和阶段,或者更好的是完成所有计算和“排序”之后的“分页”,以实现结果的“分页”。但基本过程就在这里

注意:管道阶段将是最佳的使用方式,因为您只需要提供“新”字段,并将其附加到文档中。如果您的MongoDB版本不支持此管道阶段,则只需替换为,注意您必须明确指定文档中要返回的每个字段,并添加新的计算字段。但在实现的逻辑中绝对没有其他区别


完整列表 生成结果:

Mongoose: articles.remove({}, {})
Mongoose: users.remove({}, {})
Mongoose: users.insert({ email: 'bill@example.com', _id: ObjectId("5970969d3248a329766d5e72"), topics: [ { topic: 'pizza', interest: 5 }, { topic: 'pasta', interest: 1 } ], __v: 0 })
{
  "__v": 0,
  "email": "bill@example.com",
  "_id": "5970969d3248a329766d5e72",
  "topics": [
    {
      "topic": "pizza",
      "interest": 5
    },
    {
      "topic": "pasta",
      "interest": 1
    }
  ]
}
Mongoose: articles.insert({ title: 'test', _id: ObjectId("5970969d3248a329766d5e73"), topics: [ 'food', 'pizza', 'pasta' ], __v: 0 })
{
  "__v": 0,
  "title": "test",
  "_id": "5970969d3248a329766d5e73",
  "topics": [
    "food",
    "pizza",
    "pasta"
  ]
}
Mongoose: users.findOne({ _id: ObjectId("5970969d3248a329766d5e72") }, { fields: {} })
Mongoose: articles.aggregate([ { '$addFields': { score: { '$sum': { '$map': { input: [ { interest: 5, topic: 'pizza' }, { interest: 1, topic: 'pasta' } ], as: 'u', in: { '$cond': { if: { '$setIsSubset': [ [ '$$u.topic' ], '$topics' ] }, then: '$$u.interest', else: 0 } } } } } } }, { '$sort': { score: -1 } } ], {})
[
  {
    "_id": "5970969d3248a329766d5e73",
    "title": "test",
    "topics": [
      "food",
      "pizza",
      "pasta"
    ],
    "__v": 0,
    "score": 6
  }
]

获取一篇文章,从中获取主题,聚合用户的主题。如果您遇到任何特殊问题,请显示代码。我通过添加我的天真方法更新了问题。@AlexBlex实际上,从
用户
中简单地
$map
数组,而不是“过滤”任何内容,会更有意义。至少这是我对它的看法和我在这里使用的方法。@NeilLunn,是的,不错。应该使用最小的调整。@AlexBlex什么调整?已经给出了完全有效的答案。这里没有什么可以改变的。完美的卓越。我掌握了这个概念。我仍然无法理解为什么会出现这样的错误:“MongoError:无法识别的表达式“$setIsSubSet”“”,即使我的版本(db.version())是3.4.2,并且在文档中说它受版本>=2的支持。6@GabrielePicco这是我的一个“打字错误”,自从你复制了代码后就被修正了。它是
$setIsSubset
,就像它在答案的其余部分所说的那样。@GabrielEpico为您添加了完整的列表和输出。当我把它们作为答案提交时,我确实知道这些东西是有效的。我经常举一个我自己运行的例子,再现你在问题中提出的相同条件。就像这里,我的错我控制不了!谢谢,你的答案很完美!
Mongoose: articles.remove({}, {})
Mongoose: users.remove({}, {})
Mongoose: users.insert({ email: 'bill@example.com', _id: ObjectId("5970969d3248a329766d5e72"), topics: [ { topic: 'pizza', interest: 5 }, { topic: 'pasta', interest: 1 } ], __v: 0 })
{
  "__v": 0,
  "email": "bill@example.com",
  "_id": "5970969d3248a329766d5e72",
  "topics": [
    {
      "topic": "pizza",
      "interest": 5
    },
    {
      "topic": "pasta",
      "interest": 1
    }
  ]
}
Mongoose: articles.insert({ title: 'test', _id: ObjectId("5970969d3248a329766d5e73"), topics: [ 'food', 'pizza', 'pasta' ], __v: 0 })
{
  "__v": 0,
  "title": "test",
  "_id": "5970969d3248a329766d5e73",
  "topics": [
    "food",
    "pizza",
    "pasta"
  ]
}
Mongoose: users.findOne({ _id: ObjectId("5970969d3248a329766d5e72") }, { fields: {} })
Mongoose: articles.aggregate([ { '$addFields': { score: { '$sum': { '$map': { input: [ { interest: 5, topic: 'pizza' }, { interest: 1, topic: 'pasta' } ], as: 'u', in: { '$cond': { if: { '$setIsSubset': [ [ '$$u.topic' ], '$topics' ] }, then: '$$u.interest', else: 0 } } } } } } }, { '$sort': { score: -1 } } ], {})
[
  {
    "_id": "5970969d3248a329766d5e73",
    "title": "test",
    "topics": [
      "food",
      "pizza",
      "pasta"
    ],
    "__v": 0,
    "score": 6
  }
]