按计算值

时间:2017-07-20 10:36:57

标签: 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,
    topics       : [{
                        _id: false,
                        topic: String,
                        interest: {type: Number, default: 0}
                    }]
});

文章主题字段包含文章主题,而用户主题字段包含具有用户相关兴趣的主题。

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

例如,如果文章主题是: [“食物”,“披萨”,“意大利面”] 和用户主题是: [{topic:“car”,interest:2},{topic:“pizza”,interest:5},{topic:“pasta”,interest:1}]

projectField = 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);
            });
        });

1 个答案:

答案 0 :(得分:1)

您基本上需要通过.aggregate()提供此信息,然后对计算出的值进行排序。

所以从本质上讲。您已经在内存中拥有所选的User对象,但我们会将其视为选择并继续承诺:

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

基本上我们提供"数组"来自User,并将其作为$map的参数,坦率地说是"任何数组"并且不需要成为文档本身的一部分。

在迭代每个项目时,会与当前"topics"中的Article进行比较,以查看"topic"中当前User是否匹配提供的数组。该比较由$setIsSubset完成,记住将奇异值包装在数组中以进行比较。

如果匹配,我们会提供interest值,或者0。 "映射"然后将数组输入$sum,得到一个"得分"然后可以在后期进行分类。

从技术上讲,您可以从$map向[{3}}提供输入,而不是使用$filter来确定输出,但这样做的语法是"一点点更简洁"所以它避免使用$cond来交换"价值观。

完成所有操作后,您只需在计算字段上$cond即可获得结果。

如果需要,可以添加$sort$skip个阶段,或者更好的是$limit""所有的计算和"排序"完成了#14; paging"结果但基本过程就在这里。

  

注意: $match管道阶段是最佳选择,因为您只需要提供" new"字段,它附加到文档。如果您的MongoDB版本不支持此管道阶段,那么只需替换为$addFields,注意您必须明确指定要在其中返回的每个字段文档,添加了新的计算字段。但实施的逻辑绝对没有其他区别。

完整列表

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug', true);

const uri = 'mongodb://localhost/interests',
      options = { useMongoClient: true };

const articleSchema = new Schema({
  title: String,
  text: String,
  topics: [String],
  category: String
});

const Article = mongoose.model('Article', articleSchema);

const userSchema = new Schema({
  email: String,
  password: String,
  topics: [{
    _id: false,
    topic: String,
    interest: { type: Number, default: 0 }
  }]
});

const User = mongoose.model('User', userSchema);

function log(data) {
  console.log(JSON.stringify(data,undefined,2))
}


// Main program
(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

    await Promise.all(
      Object.keys(conn.models).map( m => conn.models[m].remove({}))
    );


    // Create Some Data
    let user = await User.create({
      email: 'bill@example.com',
      topics: [
        { topic: 'pizza', interest: 5 },
        { topic: 'pasta', interest: 1 }
      ]
    });
    log(user);

    let article = await Article.create({
      title: 'test',
      topics: ["food","pizza","pasta"]
    });
    log(article);

    // Fetch and aggregate

    user = await User.findById(user._id);

    let results = await Article.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 } }
    ]);
    log(results);

  } catch (e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})();

产生结果:

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