我有两个集合,让我们称之为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
使用{{ 1}}获取最新日期。然后我可以$max
现在是单元素数组,最后是$unwind
。
问题是mongo,AFAIK中不存在右外连接,我不知道如何在管道中获得每只猫的派对列表。
澄清一下,预期的输出应该是
$sort
答案 0 :(得分:1)
如上所述,您希望“猫”使用Cat
模型并执行$lookup
实际固有的“左外连接”,而不是要求“右外连接”来自对方的集合,因为此时MongoDB无法实现“右外连接”。
它作为“左连接”也更实用,因为你想要“猫”作为你的主要输出来源。链接到“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
语句下定义整个文档。我这样做的原因是我们可以从前一个$lookup
获取"parties"
数组输出并重新整形每个条目,为当前的“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', [] ] },
这也包括在内