How to use aggregate $lookup in nested data from mongoDB? - database

I have collections like this:
// tasks
[
{_id: '123', _user: '345', _solutions: ['567', '678'] }
]
// solutions
[
{ _id: '567', _task: '123', _user: '345' },
{ _id: '678', _task: '123', _user: '345' }
]
// users
[
{ _id: '345', name: 'Tom' }
]
With this code:
await db
.collection<Task>('tasks')
.aggregate([
{ $match: { _id: id } },
{
$lookup: {
from: 'solutions',
// I guess here should be pipeline
localField: '_solutions',
foreignField: '_id',
as: '_solutions',
},
},
{ $lookup: { from: 'users', localField: '_user', foreignField: '_id', as: '_user' } },
])
I have result as below:
task = {
_id: 5e14e877fa42402079e38e44,
_solutions: [
{
_id: 5e15022ccafcb4869c153e61,
_task: 5e14e877fa42402079e38e44,
_user: 5e007403fd4ca4f47df69913, <-- this should be userObject instead
},
{
_id: 5e164f31cafcb4869c153e62,
_task: 5e14e877fa42402079e38e44,
_user: 5e007403fd4ca4f47df69913, <-- this should be userObject instead
}
],
_user: [
{
_id: 5e007403fd4ca4f47df69913,
_solutions: [Array],
_tasks: [Array],
}
]
}
and I don't know how to $lookup into _solutions._user - so instead of objectId I will have exact user object.

You can run $lookup with custom pipeline for outer lookup and the regular one for users:
db.tasks.aggregate([
{
$match: {
_id: "123"
}
},
{
$lookup: {
from: "solutions",
let: { solutions: "$_solutions" },
pipeline: [
{ $match: { $expr: { $in: [ "$_id", "$$solutions" ] } } },
{
$lookup: {
from: "users",
localField: "_user",
foreignField: "_id",
as: "_user"
}
},
{ $unwind: "$_user" }
],
as: "_solutions",
}
}
])
Mongo Playground

Related

MongoDB Aggregation - Fetch data from ObjectId subarray

I have these collections:
author
[
{
_id: "63c242130b17d5516e0cb499",
author_name:'Vyom',
book_ids:["63c242330b17d5516e0cb49a","63c242410b17d5516e0cb49b"]
}
]
book
[
{
_id:"63c242330b17d5516e0cb49a",
author_id:'63c242130b17d5516e0cb499',
book_name:'True Love',
genere:'horror'
},
{
_id:"63c242410b17d5516e0cb49b",
author_id:'63c242130b17d5516e0cb499',
book_name:'Monster Strike',
genere:'romance'
},
]
I want to fetch details of books in author collection aggregation if book_ids exists.
For this I tried as:
db.author.aggregate([
{
$match: {
_id: ObjectId("63c242130b17d5516e0cb499")
}
},
{
$lookup:{
from: 'book',
localField: '_id',
foreignField: 'author_id',
as: 'book_details'
}
},
{
$addFields:{
book_info: {
$map: {
input: '$book_details'
as: 'el'
in: {
$match: {_id:ObjectId('$$el._id')},
$paroject: {book_name: 1},
}
}
}
}
}
])
But it throws:
Unrecognized error: '$match'.
Expected O/P:
[
{
_id: "63c242130b17d5516e0cb499",
author_name:'Vyom',
book_ids:["63c242330b17d5516e0cb49a","63c242410b17d5516e0cb49b"],
book_info: [
{
_id:"63c242330b17d5516e0cb49a",
book_name:'True Love',
},
{
_id:"63c242410b17d5516e0cb49b",
book_name:'Monster Strike',
}
]
}
]
Is there any other way to loop and get details? I tried looking for other solutions but was unable to find.
I don't see why you need a $set stage to format the element in the book_details array for the book_info field.
You can use $lookup with pipeline to join both collections and format the array of documents returned.
db.author.aggregate([
{
$match: {
_id: ObjectId("63c242130b17d5516e0cb499")
}
},
{
$lookup: {
from: "book",
localField: "book_ids",
foreignField: "_id",
as: "book_info",
pipeline: [
{
$project: {
_id: 1,
book_name: 1
}
}
]
}
}
])
Demo # Mongo Playground
db.authors.aggregate([
{
$lookup: {
from: "books",
localField: "book_ids",
foreignField: "_id",
as: "books"
}
},
{
$match: { "books": { $ne: [] } }
},
{
$project: {
_id: 1,
author_name: 1,
books: {
_id: 1,
author_id: 1,
book_name: 1,
genre: 1
}
}
}
])
This pipeline will give you the result where all the authors' book details will be there if they have any books

Mongo Aggregation with nested array of Id´s

I'm having a Model which is structured similar to this:
{
"_id": ObjectId("5c878c5c18a4ff001b981zh5"),
"books": [
ObjectId("5d963a7544ec1b122ab2ddc"),
ObjectId("5d963be01f663d168f8ea4dc"),
ObjectId("5d963bcb1f663d168f8ea2f4"),
ObjectId("5d963bdf1f663d16858ea7c9"),
}
Now I want to use the aggregation framework to get a list of only the populated books, like:
{ _id: ObjectId("5d963a7544ec1b122ab2ddc"), title: ...., ... },
..
.aggregate([
{
$lookup: {
from: 'books',
let: { books: '$books' },
pipeline: [{ $match: { $expr: { _id: { $in: ['_id', '$$books'] } } } }],
as: 'bookInfos'
}
},
{ $unwind: '$bookInfos' },
{ $replaceRoot: { newRoot: '$bookInfos' } }
])
I am not too sure about your question, but I think this might be what you're looking for.
So this query worked for me:
{
$match: {
_id: user._id,
},
},
{
$lookup: {
from: "books",
localField: "books",
foreignField: "_id",
as: "booksInfo",
},
},
{ $unwind: "$booksInfo" },
{
$replaceRoot: {
newRoot: "$booksInfo",
},
},
Thanks #zishone. Somehow your query returned all the books available in the db and not only the ones from the User Model, but it works as desired when looking up the documents with local and foreignField.

Lookup VS Lookup with pipeline MongoDB (Performace & How it internally works)

I'm making a blog and have an query about which would give me better performace, simple lookup or lookup with pipeline because sometime simple lookup gave me fast result and sometime pipleline lookup. So, I am bit confused now which one to use or where to use. Suppose I have 2 collection, user and comment collection.
// Users Collection
{
_id: "MONGO_OBJECT_ID",
userName: "Web Alchemist"
}
// Comments Collection
{
_id: "MONGO_OBJECT_ID",
userId: "USER_MONGO_OBJECT_ID",
isActive: "YES", // YES or NO
comment: "xyz"
}
Now I want to Lookup from users collection to comments, which one would be better for this. I made two query which giving me same result.
[
{
$match: { _id: ObjectId("5d68c019c7d56410cc33b01a") }
},
{
$lookup: {
from: "comments",
as: "comments",
localField: "_id",
foreignField: "userId"
}
},
{
$unwind: "$comments"
},
{
$match: {
"comments.isActive": "YES"
}
},
{ $limit: 5},
{
_id: 1, userName: 1, comments: { _id: "$comments._id", comment: "$comments.comment"}
},
{
$group: {
_id: "$_id",
userName: { '$first': '$userName' },
comments: { $addToSet: "comments"}
}
}
]
OR
[
{
$match: { _id: ObjectId("5d68c019c7d56410cc33b01a") }
},
{
$lookup: {
from: "comments",
as: "comments",
let: { userId: "$_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$userId', '$$userId'] },
{ $eq: ['$isActive', 'YES'] }
]
}
}
},
{ limit: 5 },
{
$project: { _id: 1, comment: 1 }
}
]
}
}
]

MongoDB Aggregate ObjectId inside Array of embedded Documents

I have seen a ton of question about the $lookup aggregator for arrays of ObjectIds but I can't seem to find anything about when the ObjectId is inside an array of embedded documents.
I have the follow document inside a mongodb database:
_id: ObjectId('...')
Alarms: [
{
Gateway: ObjectId('...')
Port: 1
},
{
Gateway: ObjectId('...')
Port: 2
}
]
I would like to have the following:
_id: ObjectId('...')
Alarms [
{
Gateway: ...(Gateway Object),
Port: 1
},
{
Gateway: ...(Gateway Object),
Port: 2
}
]
I have tried the following with no success:
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.Gateway'
}
But this gives me the following result:
_id: ObjectId('...')
Alarms [
{
Gateway: {
...(Gateway Object)
}
Port: 1
}
]
Please try the below queries :
If you don't want the object which doesn't have match in Gateway collection exist in Alarms array in final result :
db.Alarms.aggregate([{ $unwind: '$Alarms' }, {
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.Gateway'
}
}, { $match: { 'Alarms.Gateway': { $ne: [] } } },
{ $addFields: { 'Alarms.Gateway': { $arrayElemAt: ['$Alarms.Gateway', 0] } } },
{ $group: { _id: '$_id', Alarms: { $push: '$Alarms' } } }
])
Test : MongoDB-Playground
Otherwise, if you want all objects in Alarms array to be returned irrespective of whether there is a match in Gateway or not :
db.Alarms.aggregate([{ $unwind: '$Alarms' }, {
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.GatewayObj'
}
}, { $addFields: { 'Alarms.Gateway': { $cond: [{ $ne: ['$Alarms.GatewayObj', []] }, { $arrayElemAt: ['$Alarms.GatewayObj', 0] }, '$Alarms.Gateway'] } } },
{ $project: { 'Alarms.GatewayObj': 0 } },
{ $group: { _id: '$_id', Alarms: { $push: '$Alarms' } } }
])
Test : MongoDB-Playground
Difference between two queries would be one will return below object in Alarms array (Vs) one don't.
{
"Gateway": ObjectId("5e2b5425d02e05b6940de2fb"),
"Port": 2
}

get sum conditional with mongodb

I need to get the sells sum, just with the idCategory, any idea?
I have this 3 schemas in mongondb
category = [{"id":1,"name":"cat1"}, {"id":2,"name":"cat2"}]
product = [{"id":1,"name":"product1", "catId":1}, {"id":2,"name":"product2", "catId":2}]
sells = [{"id":1,"value":80, "productId":1, status:'active'}, {"id":2,"value":90, "productId":2, status:'Inactive'}]
MongoDB collections are not actual schemas, also, I'm assuming:
You're interpreting them as arrays (although they aren't), so you mean each of your Collections have 2 documents inside of them.
You want the sum of sales grouped by product category.
If those are the cases, what you want is a MongoDB Aggregate which you would run on your "sells" collection, "join" with the "product" collection and group by category id.
The base aggregate to do so would be along the following lines:
sells.aggregate([
{
$lookup: {
from: "product",
localField: "productId",
foreignField: "id",
as: "ProductData"
}
},
{
$unwind: "$ProductData"
},
{
$group: {
_id: "$ProductData.catId",
total: { $sum: "$value" }
}
}
]);
If you also want to fetch the Category name after aggregating, all you need to do is insert another $lookup at the end of the pipeline joining with category collection:
sells.aggregate([
{
$lookup: {
from: "product",
localField: "productId",
foreignField: "id",
as: "ProductData"
}
},
{
$unwind: "$ProductData"
},
{
$group: {
_id: "$ProductData.catId",
total: { $sum: "$value" }
}
},
{
$lookup: {
from: "category",
localField: "_id",
foreignField: "id",
as: "CategoryData"
}
},
{
$unwind: "$CategoryData"
},
{
$project: {
name: "$CategoryData.name",
total: 1
}
}
]);
EDIT (adding new case request on comment):
db.getCollection('product').aggregate([
{
$lookup: {
from: "sells",
localField: "id",
foreignField: "productId",
as: "SalesData"
}
},
{
$unwind:
{
path: "$SalesData",
preserveNullAndEmptyArrays: true
}
},
{
$project: {
catId: 1,
value: "$SalesData.value"
}
},
{
$group: {
_id: "$catId",
total: { $sum: "$value" }
}
},
{
$lookup: {
from: "category",
localField: "_id",
foreignField: "id",
as: "CategoryData"
}
},
{
$unwind: "$CategoryData"
},
{
$project: {
name: "$CategoryData.name",
total: 1
}
}
]);

Resources