我有两个集合,我们称它们为Cats和Parties,有以下模式:
猫
{ name: String }聚会
{ date: Date, attendants: [{ cat: { ref: 'Cat' }, role: String }] }其中,role象征着其他一些属性,比如,参加会议的猫是否是VIP成员。
现在我想要一份所有猫的名单(即使是那些从来没有参加过任何聚会的可怜的猫咪),而对于每一只猫,我想要一张它曾经为至少一个派对扮演的角色的清单。此外,我希望这个完整的列表按照(每只猫)最后一次参加派对的date排序,而那些从未参加过任何派对的猫都是最后一个。
这给我带来了以下问题:
Parties上的阿格格把从未参加过派对的小猫排除在外。Cats上进行聚合是错误的,因为我不能让猫参加$lookup聚会,因为该信息位于子文档数组中。我目前拥有的管道给了我所有的猫,他们至少参加了一次聚会,列出了他们的角色,但没有按照上次参加的派对进行排序。事实上,我可以忍受把从未参加过派对的猫排除在外,但是分类对我来说是至关重要的:
Party.aggregate([
{ $unwind: '$attendants' },
{ $project: { role: '$attendants.role', cat: '$attendants.cat' } },
{
$group: {
_id: '$cat',
roles: { $addToSet: '$role' }
}
},
{
$lookup: {
from: 'cats',
localField: '_id',
foreignField: '_id',
as: 'cat'
}
},
{ $unwind: '$cat' },
// (*)
{ $addFields: { 'cat.roles': '$roles' } },
{ $replaceRoot: { newRoot: '$cat' } }
])我目前的想法基本上是在(*)的外部加入,添加猫参加过的聚会的列表,$project表示派对日期,然后$group使用$max获取最新的日期。然后,我可以$unwind,现在,一个元素数组和$sort在它的最后。
问题是在mongo,AFAIK中不存在正确的外部连接,而且我不知道如何在管道中获得每只猫的派对列表。
为了澄清,预期的输出应该类似于
[
{
"_id": "59982d3c7ca25936f8c327c8",
"name": "Mr. Kitty",
"roles": ["vip", "birthday cat"],
"dateOfLastParty": "2017-06-02"
},
{
"_id": "59982d3c7ca25936f8c327c9",
"name": "Snuffles",
"roles": ["best looking cat"],
"dateOfLastParty": "2017-06-01"
},
...
{
"_id": "59982d3c7ca25936f8c327c4",
"name": "Sad Face McLazytown",
"roles": [],
"dateOfLastParty": null
},
]发布于 2017-08-25 11:49:42
如前所述,您希望“cat”因此使用Cat模型,并执行实际上是$lookup固有的“左外部连接”,而不是要求来自相反集合的“右外部连接”,因为此时MongoDB不可能使用“右外部连接”。
作为一个“左连接”,它也要实用得多,因为您希望“cat”作为您的主要输出源。当链接到"Party“时,唯一要考虑的是,每个"Cat”都列在一个数组中,因此您将得到整个文档。因此,所需要做的就是在$lookup之后的“后置处理”中,您只需为当前cat的匹配条目“过滤”数组内容。
幸运的是,我们使用$arrayElemAt和$indexOfArray获得了很好的特性,这使得我们能够进行精确的提取:
let kitties = await Cat.aggregate([
{ '$lookup': {
'from': Party.collection.name,
'localField': '_id',
'foreignField': 'attendants.cat',
'as': 'parties'
}},
{ '$replaceRoot': {
'newRoot': {
'$let': {
'vars': {
'parties': {
'$map': {
'input': '$parties',
'as': 'p',
'in': {
'date': '$$p.date',
'role': {
'$arrayElemAt': [
'$$p.attendants.role',
{ '$indexOfArray': [ '$$p.attendants.cat', '$_id' ] }
]
}
}
}
}
},
'in': {
'_id': '$_id',
'name': '$name',
'roles': '$$parties.role',
'dateOfLastParty': { '$max': '$$parties.date' }
}
}
}
}}
]);因此,我的“最优”处理的概念实际上在这里使用了$replaceRoot,因为您可以在$let语句下定义整个文档。我这样做的原因是,我们可以从以前的"parties"中获取$lookup数组的输出,并对每个条目进行整形,为当前的"kitty“提取匹配的"role"数据。实际上,我们可以自己做一个变量。
创建“数组变量”的原因是,我们可以使用$max将“最大/最后”日期属性提取为“单数”,并将“角色”值提取为“数组”。这使得很容易定义您想要的字段。
因为它是从Cat开始的“左加入”,那么那些错过了所有各方的可怜的小猫仍然存在,并且仍然有理想的输出。
两个聚合管道阶段。还有什么可以更简单的!
作为一份完整清单:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/catparty',
options = { useMongoClient: true };
const catSchema = new Schema({
name: String
});
const partySchema = new Schema({
date: Date,
attendants: [{
cat: { type: Schema.Types.ObjectId, ref: 'Cat' },
role: String
}]
});
const Cat = mongoose.model('Cat', catSchema);
const Party = mongoose.model('Party', partySchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean collections
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
var cats = await Cat.insertMany(
['Fluffy', 'Snuggles', 'Whiskers', 'Socks'].map( name => ({ name }) )
);
cats.shift();
cats = cats.map( (cat,idx) =>
({ cat: cat._id, role: (idx === 0) ? 'Host' : 'Guest' })
);
log(cats);
let party = await Party.create({
date: new Date(),
attendants: cats
});
log(party);
let kitties = await Cat.aggregate([
{ '$lookup': {
'from': Party.collection.name,
'localField': '_id',
'foreignField': 'attendants.cat',
'as': 'parties'
}},
{ '$replaceRoot': {
'newRoot': {
'$let': {
'vars': {
'parties': {
'$map': {
'input': '$parties',
'as': 'p',
'in': {
'date': '$$p.date',
'role': {
'$arrayElemAt': [
'$$p.attendants.role',
{ '$indexOfArray': [ '$$p.attendants.cat', '$_id' ] }
]
}
}
}
}
},
'in': {
'_id': '$_id',
'name': '$name',
'roles': '$$parties.role',
'dateOfLastParty': { '$max': '$$parties.date' }
}
}
}
}}
]);
log(kitties);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})();以及示例输出:
[
{
"_id": "59a00d9528683e0f59e53460",
"name": "Fluffy",
"roles": [],
"dateOfLastParty": null
},
{
"_id": "59a00d9528683e0f59e53461",
"name": "Snuggles",
"roles": [
"Host"
],
"dateOfLastParty": "2017-08-25T11:44:21.903Z"
},
{
"_id": "59a00d9528683e0f59e53462",
"name": "Whiskers",
"roles": [
"Guest"
],
"dateOfLastParty": "2017-08-25T11:44:21.903Z"
},
{
"_id": "59a00d9528683e0f59e53463",
"name": "Socks",
"roles": [
"Guest"
],
"dateOfLastParty": "2017-08-25T11:44:21.903Z"
}
]您应该能够看到这些“角色”值实际上是如何成为一个包含更多数据的数组的。如果您需要它成为“唯一列表”,那么只需使用$setDifference包装,如下所示:
'roles': { '$setDifference': [ '$$parties.role', [] ] },这也包括在内
https://stackoverflow.com/questions/45879348
复制相似问题