如何使用MongoDB聚合来包含来自另一个通过一对多关系链接的集合中的相关文档?
从本质上讲,我想要做的是能够获取问题列表并包含与该问题相关的所有标志。
更新(2016年11月11日):使用下面发布的解决方案解决。
更新(05/07/2016):我已经设法通过使用$unwind, $lookup, $project
等的组合来获取问题列表及其相关标志。更新后的查询如下。
问题(05/07/2016):我只能获取具有嵌套标记的问题。我想获取所有问题,即使他们没有任何标志。
我有两个集合,一个用于内容,一个用于内容标志,如下所示:
{
"_id" : ObjectId("..."),
"slug" : "a-sample-title",
"content" : "Some content.",
"title" : "A Sample Title.",
"kind" : "Question",
"updated" : ISODate("2016-06-08T08:54:26.104Z"),
"isPublished" : true,
"isFeatured" : false,
"flags" : [
ObjectId("<id_of_flag_one>"),
ObjectId("<id_of_flag_two>")
],
"answers" : [
ObjectId("..."),
ObjectId("...")
],
"related" : [],
"isAnswered" : true,
"__v" : 4
}
{
"_id" : ObjectId("..."),
"flaggedBy" : ObjectId("<a_users_id>"),
"type" : "like",
"__v" : 0
}
在上面,一个问题可以有很多标志,一个标志只能有一个问题。我想要做的是在查询问题集时返回问题的所有标志。我尝试使用聚合运行这一点。
以下是我正在使用的更新的查询(05/07/2016)
fetchQuestions: (permission, params) => {
return new Promise((resolve, reject) => {
let query = Question.aggregate([
{
$lookup: {
from: 'users',
localField: 'author',
foreignField: '_id',
as: 'authorObject'
}
},
{
$unwind: '$authorObject'
},
{
$unwind: '$flags'
},
{
$lookup: {
from: 'flags',
localField: 'flags',
foreignField: '_id',
as: 'flagObjects'
}
},
{
$unwind: '$flagObjects'
},
{
$group: {
_id: {
_id: '$_id',
title: '$title',
content: '$content',
updated: '$updated',
isPublished: '$isPublished',
isFeatured: '$isFeatured',
isAnswered: '$isAnswered',
answers: '$answers',
author: '$authorObject'
},
flags: {
$push: '$flags'
},
flagObjects: {
$push: '$flagObjects'
}
}
},
{
$project: {
_id: 0,
_id: '$_id._id',
title: '$_id.title',
content: '$_id.content',
updated: '$_id.updated',
isPublished: '$_id.isPublished',
isFeatured: '$_id.isFeatured',
author: {
fullname: '$_id.author.fullname',
username: '$_id.author.username'
},
flagCount: {
$size: '$flagObjects'
},
answersCount: {
$size: '$_id.answers'
},
flags: '$flagObjects',
wasFlagged: {
$cond: {
if: {
$gt: [
{
$size: '$flagObjects'
},
0
]
},
then: true,
else: false
}
}
}
},
{
$sort: {
updated: 1
}
},
{
$skip: 0
},
// {
// $limit: 110
// }
])
.exec((error, result) => {
if(error) reject(error);
else resolve(result);
});
});
},
我尝试使用其他聚合运算符,例如$unwind
和$group
,但结果集返回的是五个项目或更少,我发现很难理解这些应该如何工作一起来找我需要的东西。
这是我得到的回应,这正是我所需要的。唯一的问题是,如上所述,我只会得到带有标志但不是所有问题的问题。
"questions": [
{
"_id": "5757dd42d0c2ae292f76f11a",
"flags": [
{
"_id": "5774e0a81f2874821f71ace8",
"flaggedBy": "57569d02d0c2ae292f76f0f5",
"type": "concern",
"__v": 0
},
{
"_id": "577a0f5414b834372a6ac772",
"flaggedBy": "5756aa79d0c2ae292f76f0f8",
"type": "concern",
"__v": 0
}
],
"title": "A question for the landing page.",
"content": "This is a question that will appear on the landing page.",
"updated": "2016-06-08T08:54:26.104Z",
"isPublished": true,
"isFeatured": false,
"author": {
"fullname": "Matt Finucane",
"username": "matfin-386829"
},
"flagCount": 2,
"answersCount": 2,
"wasFlagged": true
},
...,
...,
...
]
答案 0 :(得分:5)
看起来我已经找到了这个问题的解决方案,将在下面发布。
我遇到的问题概述如下:
我有一个Questions
的集合,其中包含标题,内容,发布日期等各种字段,位于通常的ObjectID
字段之上。
我有一个与问题相关的单独的Flags
集合。
当为Question
发布标记时,ObjectID
的{{1}}应添加到Flag
附加到flags
的数组字段中1}}文件。
简而言之,Question
不会直接存储在Flags
文档中。对Question
的引用存储为Flag
。
我需要做的是从ObjectID
集合中获取所有项目,并包含相关的标记。
MongoDB聚合框架似乎是理想的解决方案,但是了解它可能有点棘手,特别是在处理Questions
,$group
和$lookup
运算符时
我还应该指出我正在使用$unwind
和Mongoose NodeJS v6.x.x
。
4.4.x
fetchQuestions: (permission, params) => {
return new Promise((resolve, reject) => {
let query = Question.aggregate([
/**
* We need to perform a lookup on the author
* so we can include the user details for the
* question. This lookup is quite easy to handle
* because a question should only have one author.
*/
{
$lookup: {
from: 'users',
localField: 'author',
foreignField: '_id',
as: 'authorObject'
}
},
/**
* We need this so that the lookup on the author
* object pulls out an author object and not an
* array containing one author. This simplifies
* the process of $project below.
*/
{
$unwind: '$authorObject'
},
/**
* We need to unwind the flags field, which is an
* array of ObjectIDs. At this stage of the aggregation
* pipeline, questions will be repeated so for example
* if there are two questions and one of them has two
* flags and the other has four flags, the result set
* will have six items and the questions will be repeated
* the same number of times as the flags they contain.
* The $group function later on will take care of this
* and return only unique questions.
*
* It is important to point out how the $unwind function
* is used here. If we did not specify the preserveNullAndEmptyArrays
* parameter then the only questions returned would be those
* that have flags. Those without would be skipped.
*/
{
$unwind: {
path: '$flags',
preserveNullAndEmptyArrays: true
}
},
/**
* Now that we have the ObjectIDs for the flags from the
* $unwind operation above, we need to perform a lookup on
* the flags collection to get our flags. We return these
* with the variable name 'flagObjects' we can use later.
*/
{
$lookup: {
from: 'flags',
localField: 'flags',
foreignField: '_id',
as: 'flagObjects'
}
},
/**
* We then need to perform another unwind on the 'flagObjects'
* and pass them into the next $group function
*/
{
$unwind: {
path: '$flagObjects',
preserveNullAndEmptyArrays: true
}
},
/**
* The next stage of the aggregation pipeline takes all
* the duplicated questions with their flags and the flagObjects
* and normalises the data. The $group aggregator requires an _id
* property to describe how a question should be unique. It also sets
* up some variables that can be used when it comes to the $project
* stage of the aggregation pipeline.
* the flagObjects property calls on the $push function to add a collection
* of flagObjects that were pulled from the $lookup above.
*/
{
$group: {
_id: {
_id: '$_id',
title: '$title',
content: '$content',
updated: '$updated',
isPublished: '$isPublished',
isFeatured: '$isFeatured',
isAnswered: '$isAnswered',
answers: '$answers',
author: '$authorObject'
},
flagObjects: {
$push: '$flagObjects'
}
}
},
/**
* The $project stage of the pipeline then puts together what the final
* result set should look like when the query is executed. Here we can use
* various Mongo functions to reshape the data and create new attributes.
*/
{
$project: {
_id: 0,
_id: '$_id._id',
title: '$_id.title',
updated: '$_id.updated',
isPublished: '$_id.isPublished',
isFeatured: '$_id.isFeatured',
author: {
fullname: '$_id.author.fullname',
username: '$_id.author.username'
},
flagCount: {
$size: '$flagObjects'
},
answersCount: {
$size: '$_id.answers'
},
flags: '$flagObjects',
wasFlagged: {
$cond: {
if: {
$gt: [
{
$size: '$flagObjects'
},
0
]
},
then: true,
else: false
}
}
}
},
/**
* Then we can sort, skip and limit if needs be.
*/
{
$sort: {
updated: -1
}
},
{
$skip: 0
},
// {
// $limit: 110
// }
]);
query.exec((error, result) => {
if(error) reject(error);
else resolve(result);
});
});
},