我有一个包含两个集合的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);
});
});
答案 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
}
]