MongoDB Aggregation - Fetch data from ObjectId subarray - database

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

Related

Problemm using concat in array mongodb

i have 3 tables
1) Actor: actor_id, first_name, last_name
2) Film: film_id, title
3) Film_Actor: film_id, actor_id
Example document:
_id
:
60aedac769985522a024daca
actor_id
:
"1"
first_name
:
"Penelope"
last_name
:
"Guiness"
I wanto to result not only first_name, but last_name too. I'm facing problem with using concat in $group function.
my FULLY codes:
db.film.aggregate([
{
$lookup: {
from: "film_actor",
localField: "film_id",
foreignField: "film_id",
as: "film_actor"
}
},
{
$lookup: {
from: "actor",
localField: "film_actor.actor_id",
foreignField: "actor_id",
as: "actor"
}
},
{
$group: {
_id: "$film_id",
title: {"$first":"$title"},
name: {$push: "$actor.first_name"}
}
}
]);
Error report:
$concat only supports strings, not array
desired output:
id:"207"
title:"Dangerous Uptown"
name:Array
0:"Penelope Guiness"
1:"Mary Watson"
2:"Ralph Holts"
3:"Spencer Dani"
What you have done is appreciated, I've done some changes in your code
$lookup to join collections. I have started form Flim collection
$unwind to deconstruct the array
$group to reconstruct the array that we already deconstructed, this will
Since we have nested array we need to use $map to loop over them to collect the first name and lastname
The above stage will end up with again nested array, so we use $reduce to loop again and remove inner arrays using $setUnion
remove some duplicate entries, depends on your requirements
Here is the code
db.Film.aggregate([
{
$lookup: {
from: "Film_Actor",
localField: "film_id",
foreignField: "film_id",
as: "join_flim"
}
},
{ "$unwind": "$join_flim" },
{
$lookup: {
from: "Actor",
localField: "join_flim.actor_id",
foreignField: "actor_id",
as: "join_flim.join_actor"
}
},
{
$group: {
_id: "$_id",
title: { $first: "$title" },
join_flim: { $push: "$join_flim" }
}
},
{
"$project": {
title: 1,
actornames: {
$map: {
input: "$join_flim",
as: "f",
in: {
$map: {
input: "$$f.join_actor",
as: "a",
in: {
$concat: [ "$$a.first_name", " ", "$$a.last_name" ]
}
}
}
}
}
}
},
{
"$project": {
title: 1,
actornames: {
"$reduce": {
"input": "$actornames",
"initialValue": [],
"in": {
"$setUnion": [ "$$this", "$$value" ]
}
}
}
}
}
])
Working Mongo playground

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

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

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 }
}
]
}
}
]

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
}
}
]);

MongoDB - How to verify if a nested array item is contained in another nested array item on the same document?

The idea here is to return an array of documents of the users' followers with the information if this user is a friend of that follower or not.
So far I have:
db.getCollection('users').aggregate([
{ $match: { _id: ObjectId("588877d82523b4395039910a") } },
{ $lookup: {
from: 'users',
localField: 'followers',
foreignField: '_id',
as: 's_followers'
}
},
{
$project: {
"s_followers._id": 1,
"s_followers.isFriend": {
$in: ["s_followers.id",
{ $setIntersection: ["$friends", "$followers"] }
]}
}
}
])
But the "s_followers.id" used in the $in operator doesn't seem to retrieve the _id information from the follower, so it always returns false.
When I use a ObjectId directly, I got the result I want:
"s_followers.isFriend": {
$in: [ObjectId("588877d82523b4395039910a"),
{ $setIntersection: ["$friends", "$followers"] }
]}
But I really need this ID to be a reference to the follower _id.
Expected result would be something like:
{
"_id" : ObjectId("588877d82523b4395039910a"),
"s_followers" : [
{
"_id" : ObjectId("5888687e56be8f172844d96f"),
"isFriend" : true
},
{
"_id" : ObjectId("5888ca27d79b8b03949a6e8c"),
"isFriend" : false
}
]
}
Thanks for your help!
UPD: A different approach (maybe easier), would be to use the ID of the user that I have (the one used on $match), but I would still need to get the reference for the follower's follower array
db.getCollection('users').aggregate([
{ $match: { _id: ObjectId("588877d82523b4395039910a") } },
{ $lookup: {
from: 'users',
localField: 'followers',
foreignField: '_id',
as: 's_followers'
}
}, {
$project: {
"firstName": 1,
"s_followers._id": 1,
"s_followers.firstName": 1,
"s_followers.followers": 1,
"s_followers.isFriend": { $in: [ObjectId("588877d82523b4395039910a"), "$s_followers.followers"] }
}
}
])
UPD2: The user data structure (the part that matters)
{
followers: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
friends: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
}
FOR VERSION 3.4.0+
Ok, just got it, I'll post here the code and my understanding of it:
db.getCollection('users').aggregate([
{ $match: { _id: ObjectId("588877d82523b4395039910a") } },
{ $lookup: {
from: 'users',
localField: 'followers',
foreignField: '_id',
as: 's_followers'
}
}, {
$project: {
"firstName": 1,
"s_followers._id": 1,
"s_followers.firstName": 1,
"s_followers.followers": 1,
}
}, {
$unwind: "$s_followers"
}, {
$project: {
"firstName": "$s_followers.firstName",
"isFriend": { $in: [ObjectId("588877d82523b4395039910a"), "$s_followers.followers"] }
}
}
])
My understanding of it:
$match: match the user I'm intended to get the followers of.
$lookup: found each follower detail
$project: select the information I want to return, also get the follower list of each follower
$unwind: create a different document for each follower
With the array unwinded, I can refer to the follower's followers array, and find my object id in it :)
In my example use followers friends list to check current user is friend or not. as {$arrayElemAt:["$s_followers.friends",0]} if want to find in followers then can use "$s_followers.followers"
You can try it.
db.getCollection('user').aggregate([
{ $match: { _id: ObjectId("5714d190e6128b7e7f8d9008") } },
{$unwind:"$followers"},
{ $lookup: {
from: 'user',
localField: 'followers',
foreignField: '_id',
as: 's_followers'
}
},
{$project:{
firstName:1,
s_followers:{$arrayElemAt:["$s_followers",0]},
isFriend:{$cond:[{
$anyElementTrue:{
$map: {"input": {$arrayElemAt:["$s_followers.friends",0]},
"as": "el",
"in": { "$eq": [ "$$el", "$_id" ] }
}
}
},true,false]}
}
},
{$group:{
_id:"$_id",
s_followers:{$push:{_id:"$s_followers._id",isFriend:"$isFriend"}}
}
}
])

Resources