MongoDB 与 objects 嵌套数组的聚合
MongoDB aggregation with nested arrays of objects
我正在努力从 Mongo 数据库中获取一些聚合数据。我有以下 collections:
餐厅:
{
"_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
"name" : "Pizzaria Don Juan",
"active" : true,
"users" : [
{
"_id" : ObjectId("5e10fc2adc147a373c312144")
},
{
"_id" : ObjectId("5e11ff8003eb832ef84342a6")
}
],
"socialMedias" : [
{
"_id" : ObjectId("5e1008943330ad05d4e1867c"),
"url" : "https://instagram/jetpizzas"
},
{
"_id" : ObjectId("5e10089a3330ad05d4e1867d"),
"url" : "https://facebook.com/jetpizzas"
}
],
"branches" : [
{
"name" : "Teste"
},
{
"name" : "Teste 2"
}
],
"sections" : [
{
"name" : "Bebidas"
}
],
"__v" : 0
}
{
"_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
"name" : "Pizza Ruth",
"active" : true,
"users" : [ ],
"socialMedias" : [ ],
"branches" : [ ],
"sections" : [ ],
"__v" : 0
}
{
"_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
"name" : "Feijão de Corda",
"active" : true,
"users" : [ ],
"socialMedias" : [ ],
"branches" : [ ],
"sections" : [ ],
"__v" : 0
}
用户
{
"_id" : ObjectId("5e10fc2adc147a373c312144"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : 2,
"active" : true,
"username" : "contato@pizzariadonjuan.com.br",
"password" : "a$xhmw83QXbMvSqmrKAUYn.O4fOxboEyVkVB0DGkSsJUOp7K4bYQkCm",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
"__v" : 0
}
{
"_id" : ObjectId("5e11ff8003eb832ef84342a6"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : 2,
"active" : true,
"username" : "sac@pizzariadonjuan.com.br",
"password" : "a$wby3cs89jyO0HUbEiGLKye0jOB3U295zzIsu8xGJ4wnQtw5jcvSZO",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
"__v" : 0
}
{
"_id" : ObjectId("5e11ff9c03eb832ef84342a7"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : 2,
"active" : true,
"username" : "juan@pizzariadonjuan.com.br",
"password" : "a$nEM3RxEjYbI77R9vOWUrMOGeHFDmdZqVKUNtTLuKZVLNQBQqIbew.",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-05T15:24:12.456Z"),
"__v" : 0
}
个人资料
{
"_id" : ObjectId("5e0ea5f6832df0473cacacda"),
"number" : 1,
"name" : "Cliente",
"__v" : 0
}
{
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
}
{
"_id" : ObjectId("5e0ea607832df0473cacacdc"),
"number" : 0,
"name" : "Admin",
"__v" : 0
}
和社交媒体:
{
"_id" : ObjectId("5e1008943330ad05d4e1867c"),
"name" : "Instagram",
"__v" : 0
}
{
"_id" : ObjectId("5e10089a3330ad05d4e1867d"),
"name" : "Facebook",
"__v" : 0
}
{
"_id" : ObjectId("5e1009043330ad05d4e1867f"),
"name" : "LinkedIn",
"__v" : 0
}
我的目标是获取与餐厅 object 相关的所有 object。使用以下代码:
db.restaurants.aggregate([
{ $lookup: { from: "users", localField: "users._id", foreignField: "_id", as: "foundUsers" } },
{$group: {
'_id': '$_id',
'name': { "$first": "$name" },
'active': { "$first": "$active" },
users: { $push: '$foundUsers' },
branches: { "$first": "$branches" },
sections: { "$first": "$sections" },
socialMedias: { "$first": "$socialMedias" }
}
},
{$unwind: '$users'},
{ $unset: 'users.password' },
{ $lookup: { from: "profiles", localField: "users.profile", foreignField: "number", as: "profile" } },
{ $addFields: { 'users.profile': { $arrayElemAt: ['$profile', 0] } } },
{ $unset: 'profile' },
{ $lookup: { from: "socialmedias", localField: "socialMedias._id", foreignField: "_id", as: "socialMedia" } },
{ $addFields: { 'socialMedias.name': { $arrayElemAt: ['$socialMedia.name', 0] } } },
{$group: {
'_id': '$_id',
'name': { "$first": "$name" },
'active': { "$first": "$active" },
users: { $first: '$users' },
branches: { "$first": "$branches" },
sections: { "$first": "$sections" },
socialMedias: { "$first": "$socialMedias" }
}
}
])
我明白了:
{
"_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
"name" : "Feijão de Corda",
"active" : true,
"users" : [ ],
"branches" : [ ],
"sections" : [ ],
"socialMedias" : [ ]
}
{
"_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
"name" : "Pizza Ruth",
"active" : true,
"users" : [ ],
"branches" : [ ],
"sections" : [ ],
"socialMedias" : [ ]
}
{
"_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
"name" : "Pizzaria Don Juan",
"active" : true,
"users" : [
{
"_id" : ObjectId("5e10fc2adc147a373c312144"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : {
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
},
"active" : true,
"username" : "contato@pizzariadonjuan.com.br",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
"__v" : 0
},
{
"_id" : ObjectId("5e11ff8003eb832ef84342a6"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : {
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
},
"active" : true,
"username" : "sac@pizzariadonjuan.com.br",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
"__v" : 0
}
],
"branches" : [
{
"name" : "Teste"
},
{
"name" : "Teste 2"
}
],
"sections" : [
{
"name" : "Bebidas"
}
],
"socialMedias" : [
{
"_id" : ObjectId("5e1008943330ad05d4e1867c"),
"url" : "https://instagram/jetpizzas",
"name" : "Instagram"
},
{
"_id" : ObjectId("5e10089a3330ad05d4e1867d"),
"url" : "https://facebook.com/jetpizzas",
"name" : "Instagram"
}
]
}
请注意,嵌套数组 socialMedias
的社交媒体名称值错误(重复的“Instagram”名称,对于 Instagram 应该是一条记录,对于 Facebook 应该是另一条记录)。即使我尝试从餐厅 collection 展开社交媒体数组,结果 returns 只有餐厅 object 具有社交媒体值。
关于如何正确执行此操作的任何线索?
将 $lookup
结果与现有数组合并的方式是这里的一个问题。你不能 运行:
{ $addFields: { 'socialMedias.name': { $arrayElemAt: ['$socialMedia.name', 0] } } },
因为您将始终获得第一个数组元素。您需要合并两个数组,而不是使用 $map , $filter and $mergeObjects:
{
$addFields: {
socialmedias: {
$map: {
input: "$socialMedias",
as: "sm",
in: {
$mergeObjects: [
"$$this",
{
$arrayElemAt: [ { $filter: { input: "$socialmedias", cond: { $eq: [ "$$sm.number", "$$this._id" ] } } }, 0 ]
}
]
}
}
}
}
}
您还需要为 user.profile
应用此方法,因为当前的解决方案容易出错。
非常感谢您的帮助。我试过这个查询:
db.restaurants.aggregate([
{ $lookup: { from: "users", localField: "users._id", foreignField: "_id", as: "foundUsers" } },
{$group: {
'_id': '$_id',
'name': { "$first": "$name" },
'active': { "$first": "$active" },
users: { $push: '$foundUsers' },
branches: { "$first": "$branches" },
sections: { "$first": "$sections" },
socialMedias: { "$first": "$socialMedias" }
}
},
{$unwind: '$users'},
{ $unset: 'users.password' },
{ $lookup: { from: "profiles", localField: "users.profile", foreignField: "number", as: "profile" } },
{ $addFields: { 'users.profile': { $arrayElemAt: ['$profile', 0] } } },
{ $unset: 'profile' },
{ $lookup: { from: "socialmedias", localField: "socialMedias._id", foreignField: "_id", as: "foundSocialMedia" } },
{
$addFields: {
socialMedias: {
$map: {
input: "$socialMedias",
as: "sm",
in: {
$mergeObjects: [
"$$sm",
{
$arrayElemAt: [
{
$filter: {
input: "$foundSocialMedia",
cond: {
$eq: [
"$$sm._id",
"$$this._id"
]
}
}
},
0
]
}
]
}
}
}
}
},
{ $unset: 'foundSocialMedia' },
])
我得到了想要的结果:
{
"_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
"name" : "Feijão de Corda",
"active" : true,
"users" : [ ],
"branches" : [ ],
"sections" : [ ],
"socialMedias" : [ ]
}
{
"_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
"name" : "Pizza Ruth",
"active" : true,
"users" : [ ],
"branches" : [ ],
"sections" : [ ],
"socialMedias" : [ ]
}
{
"_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
"name" : "Pizzaria Don Juan",
"active" : true,
"users" : [
{
"_id" : ObjectId("5e10fc2adc147a373c312144"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : {
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
},
"active" : true,
"username" : "contato@pizzariadonjuan.com.br",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
"__v" : 0
},
{
"_id" : ObjectId("5e11ff8003eb832ef84342a6"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : {
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
},
"active" : true,
"username" : "sac@pizzariadonjuan.com.br",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
"__v" : 0
}
],
"branches" : [
{
"name" : "Teste"
},
{
"name" : "Teste 2"
}
],
"sections" : [
{
"name" : "Bebidas"
}
],
"socialMedias" : [
{
"_id" : ObjectId("5e1008943330ad05d4e1867c"),
"url" : "https://instagram/jetpizzas",
"name" : "Instagram",
"__v" : 0
},
{
"_id" : ObjectId("5e10089a3330ad05d4e1867d"),
"url" : "https://facebook.com/jetpizzas",
"name" : "Facebook",
"__v" : 0
}
]
}
我正在努力从 Mongo 数据库中获取一些聚合数据。我有以下 collections:
餐厅:
{
"_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
"name" : "Pizzaria Don Juan",
"active" : true,
"users" : [
{
"_id" : ObjectId("5e10fc2adc147a373c312144")
},
{
"_id" : ObjectId("5e11ff8003eb832ef84342a6")
}
],
"socialMedias" : [
{
"_id" : ObjectId("5e1008943330ad05d4e1867c"),
"url" : "https://instagram/jetpizzas"
},
{
"_id" : ObjectId("5e10089a3330ad05d4e1867d"),
"url" : "https://facebook.com/jetpizzas"
}
],
"branches" : [
{
"name" : "Teste"
},
{
"name" : "Teste 2"
}
],
"sections" : [
{
"name" : "Bebidas"
}
],
"__v" : 0
}
{
"_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
"name" : "Pizza Ruth",
"active" : true,
"users" : [ ],
"socialMedias" : [ ],
"branches" : [ ],
"sections" : [ ],
"__v" : 0
}
{
"_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
"name" : "Feijão de Corda",
"active" : true,
"users" : [ ],
"socialMedias" : [ ],
"branches" : [ ],
"sections" : [ ],
"__v" : 0
}
用户
{
"_id" : ObjectId("5e10fc2adc147a373c312144"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : 2,
"active" : true,
"username" : "contato@pizzariadonjuan.com.br",
"password" : "a$xhmw83QXbMvSqmrKAUYn.O4fOxboEyVkVB0DGkSsJUOp7K4bYQkCm",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
"__v" : 0
}
{
"_id" : ObjectId("5e11ff8003eb832ef84342a6"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : 2,
"active" : true,
"username" : "sac@pizzariadonjuan.com.br",
"password" : "a$wby3cs89jyO0HUbEiGLKye0jOB3U295zzIsu8xGJ4wnQtw5jcvSZO",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
"__v" : 0
}
{
"_id" : ObjectId("5e11ff9c03eb832ef84342a7"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : 2,
"active" : true,
"username" : "juan@pizzariadonjuan.com.br",
"password" : "a$nEM3RxEjYbI77R9vOWUrMOGeHFDmdZqVKUNtTLuKZVLNQBQqIbew.",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-05T15:24:12.456Z"),
"__v" : 0
}
个人资料
{
"_id" : ObjectId("5e0ea5f6832df0473cacacda"),
"number" : 1,
"name" : "Cliente",
"__v" : 0
}
{
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
}
{
"_id" : ObjectId("5e0ea607832df0473cacacdc"),
"number" : 0,
"name" : "Admin",
"__v" : 0
}
和社交媒体:
{
"_id" : ObjectId("5e1008943330ad05d4e1867c"),
"name" : "Instagram",
"__v" : 0
}
{
"_id" : ObjectId("5e10089a3330ad05d4e1867d"),
"name" : "Facebook",
"__v" : 0
}
{
"_id" : ObjectId("5e1009043330ad05d4e1867f"),
"name" : "LinkedIn",
"__v" : 0
}
我的目标是获取与餐厅 object 相关的所有 object。使用以下代码:
db.restaurants.aggregate([
{ $lookup: { from: "users", localField: "users._id", foreignField: "_id", as: "foundUsers" } },
{$group: {
'_id': '$_id',
'name': { "$first": "$name" },
'active': { "$first": "$active" },
users: { $push: '$foundUsers' },
branches: { "$first": "$branches" },
sections: { "$first": "$sections" },
socialMedias: { "$first": "$socialMedias" }
}
},
{$unwind: '$users'},
{ $unset: 'users.password' },
{ $lookup: { from: "profiles", localField: "users.profile", foreignField: "number", as: "profile" } },
{ $addFields: { 'users.profile': { $arrayElemAt: ['$profile', 0] } } },
{ $unset: 'profile' },
{ $lookup: { from: "socialmedias", localField: "socialMedias._id", foreignField: "_id", as: "socialMedia" } },
{ $addFields: { 'socialMedias.name': { $arrayElemAt: ['$socialMedia.name', 0] } } },
{$group: {
'_id': '$_id',
'name': { "$first": "$name" },
'active': { "$first": "$active" },
users: { $first: '$users' },
branches: { "$first": "$branches" },
sections: { "$first": "$sections" },
socialMedias: { "$first": "$socialMedias" }
}
}
])
我明白了:
{
"_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
"name" : "Feijão de Corda",
"active" : true,
"users" : [ ],
"branches" : [ ],
"sections" : [ ],
"socialMedias" : [ ]
}
{
"_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
"name" : "Pizza Ruth",
"active" : true,
"users" : [ ],
"branches" : [ ],
"sections" : [ ],
"socialMedias" : [ ]
}
{
"_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
"name" : "Pizzaria Don Juan",
"active" : true,
"users" : [
{
"_id" : ObjectId("5e10fc2adc147a373c312144"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : {
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
},
"active" : true,
"username" : "contato@pizzariadonjuan.com.br",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
"__v" : 0
},
{
"_id" : ObjectId("5e11ff8003eb832ef84342a6"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : {
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
},
"active" : true,
"username" : "sac@pizzariadonjuan.com.br",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
"__v" : 0
}
],
"branches" : [
{
"name" : "Teste"
},
{
"name" : "Teste 2"
}
],
"sections" : [
{
"name" : "Bebidas"
}
],
"socialMedias" : [
{
"_id" : ObjectId("5e1008943330ad05d4e1867c"),
"url" : "https://instagram/jetpizzas",
"name" : "Instagram"
},
{
"_id" : ObjectId("5e10089a3330ad05d4e1867d"),
"url" : "https://facebook.com/jetpizzas",
"name" : "Instagram"
}
]
}
请注意,嵌套数组 socialMedias
的社交媒体名称值错误(重复的“Instagram”名称,对于 Instagram 应该是一条记录,对于 Facebook 应该是另一条记录)。即使我尝试从餐厅 collection 展开社交媒体数组,结果 returns 只有餐厅 object 具有社交媒体值。
关于如何正确执行此操作的任何线索?
将 $lookup
结果与现有数组合并的方式是这里的一个问题。你不能 运行:
{ $addFields: { 'socialMedias.name': { $arrayElemAt: ['$socialMedia.name', 0] } } },
因为您将始终获得第一个数组元素。您需要合并两个数组,而不是使用 $map , $filter and $mergeObjects:
{
$addFields: {
socialmedias: {
$map: {
input: "$socialMedias",
as: "sm",
in: {
$mergeObjects: [
"$$this",
{
$arrayElemAt: [ { $filter: { input: "$socialmedias", cond: { $eq: [ "$$sm.number", "$$this._id" ] } } }, 0 ]
}
]
}
}
}
}
}
您还需要为 user.profile
应用此方法,因为当前的解决方案容易出错。
非常感谢您的帮助。我试过这个查询:
db.restaurants.aggregate([
{ $lookup: { from: "users", localField: "users._id", foreignField: "_id", as: "foundUsers" } },
{$group: {
'_id': '$_id',
'name': { "$first": "$name" },
'active': { "$first": "$active" },
users: { $push: '$foundUsers' },
branches: { "$first": "$branches" },
sections: { "$first": "$sections" },
socialMedias: { "$first": "$socialMedias" }
}
},
{$unwind: '$users'},
{ $unset: 'users.password' },
{ $lookup: { from: "profiles", localField: "users.profile", foreignField: "number", as: "profile" } },
{ $addFields: { 'users.profile': { $arrayElemAt: ['$profile', 0] } } },
{ $unset: 'profile' },
{ $lookup: { from: "socialmedias", localField: "socialMedias._id", foreignField: "_id", as: "foundSocialMedia" } },
{
$addFields: {
socialMedias: {
$map: {
input: "$socialMedias",
as: "sm",
in: {
$mergeObjects: [
"$$sm",
{
$arrayElemAt: [
{
$filter: {
input: "$foundSocialMedia",
cond: {
$eq: [
"$$sm._id",
"$$this._id"
]
}
}
},
0
]
}
]
}
}
}
}
},
{ $unset: 'foundSocialMedia' },
])
我得到了想要的结果:
{
"_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
"name" : "Feijão de Corda",
"active" : true,
"users" : [ ],
"branches" : [ ],
"sections" : [ ],
"socialMedias" : [ ]
}
{
"_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
"name" : "Pizza Ruth",
"active" : true,
"users" : [ ],
"branches" : [ ],
"sections" : [ ],
"socialMedias" : [ ]
}
{
"_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
"name" : "Pizzaria Don Juan",
"active" : true,
"users" : [
{
"_id" : ObjectId("5e10fc2adc147a373c312144"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : {
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
},
"active" : true,
"username" : "contato@pizzariadonjuan.com.br",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
"__v" : 0
},
{
"_id" : ObjectId("5e11ff8003eb832ef84342a6"),
"isExpired" : false,
"isBlocked" : false,
"loginTentatives" : 0,
"profile" : {
"_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
"number" : 2,
"name" : "Restaurante",
"__v" : 0
},
"active" : true,
"username" : "sac@pizzariadonjuan.com.br",
"email" : "",
"phone" : "",
"createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
"__v" : 0
}
],
"branches" : [
{
"name" : "Teste"
},
{
"name" : "Teste 2"
}
],
"sections" : [
{
"name" : "Bebidas"
}
],
"socialMedias" : [
{
"_id" : ObjectId("5e1008943330ad05d4e1867c"),
"url" : "https://instagram/jetpizzas",
"name" : "Instagram",
"__v" : 0
},
{
"_id" : ObjectId("5e10089a3330ad05d4e1867d"),
"url" : "https://facebook.com/jetpizzas",
"name" : "Facebook",
"__v" : 0
}
]
}