Query to get specific sub-doc using aggregation in mongoDB? - mongodb

I'd like to know a query in which I could get a specific sub-doc using it's ObjectId- or even it's "store" name if it's easier- using the aggregation method. I'm pretty sure I can use $project but I'm not sure how to apply that to an ObjectId.
I can use the find method but I need it to be in an aggregation method so mongo will return to me the item's names and not it's ObjectId.
db.stores.aggregate([
{$unwind: '$inventory'},
{$lookup: {
from: 'items',
localField: 'inventory.item',
foreignField: '_id',
as: 'itemsData'
}},
{$unwind: '$itemsData'},
{$group:{
_id: "$_id",
inventory: { $push: "$inventory" },
itemsData: { $push: "$itemsData" }
}}
]).pretty()
I have five stores in my store collection. This query returns all five documents like the code below this, just five times. But I want to specify stores to be returned using it's ObjectId.
{
"_id" : ObjectId("5cc0d6ad1ea502abb28b52cc"),
"inventory" : [
{
"item" : ObjectId("5cbe70bd1ea502abb28b52a4"),
"amount" : 9
},
{
"item" : ObjectId("5cbe70bd1ea502abb28b52a1"),
"amount" : 5
},
{
"item" : ObjectId("5cbe70bd1ea502abb28b52a7"),
"amount" : 2
},
{
"item" : ObjectId("5cbe70bd1ea502abb28b529e"),
"amount" : 5
}
],
"itemsData" : [
{
"_id" : ObjectId("5cbe70bd1ea502abb28b52a4"),
"item" : "beans",
"price" : NumberDecimal("53,75")
},
{
"_id" : ObjectId("5cbe70bd1ea502abb28b52a1"),
"item" : "bananas",
"price" : NumberDecimal("5.00")
},
{
"_id" : ObjectId("5cbe70bd1ea502abb28b52a7"),
"item" : "watermelon",
"price" : NumberDecimal("3.50")
},
{
"_id" : ObjectId("5cbe70bd1ea502abb28b529e"),
"item" : "broccoli",
"price" : NumberDecimal("5.50")
}
]
}
I want the query to return back the code just like the one above, with only one store and not five. I'm not sure how to use $project if it's suitable for this task.

As you said only one store data to be return, so you can use $limit :
db.stores.aggregate([
{$unwind: '$inventory'},
{$lookup: {
from: 'items',
localField: 'inventory.item',
foreignField: '_id',
as: 'itemsData'
}},
{$unwind: '$itemsData'},
{$group:{
_id: "$_id",
inventory: { $push: "$inventory" },
itemsData: { $push: "$itemsData" }
}},
{ $limit : 1}
]).pretty()
now, if you want to return specific store data to be return use $match Query :
db.stores.aggregate([
{ $match : { "_id" : ObjectId("5cc0d6ad1ea502abb28b52cc") }},
{$unwind: '$inventory'},
{$lookup: {
from: 'items',
localField: 'inventory.item',
foreignField: '_id',
as: 'itemsData'
}},
{$unwind: '$itemsData'},
{$group:{
_id: "$_id",
inventory: { $push: "$inventory" },
itemsData: { $push: "$itemsData" }
}}
]).pretty()
Hope, this was helpful.

Related

Populate nested array objects using aggregate in mongodb

Im trying to populate the second nested array after using aggregate $lookup.
This is my original array.
{
"_id" : ObjectId("607da9c0c7cb26384c7810a6"),
"date" : ISODate("2021-04-20T00:00:00.000+0000"),
"clientID" : "601e6dc61766587af8ce76db",
"medications" : [
{
"_id" : ObjectId("6065de3aa95e721f587f7528")
},
]
}
{
from: "medications",
localField: "medications._id",
foreignField: "_id",
as: "medications",
}
after using aggregate $lookup I get this result, but I still have nested array "inventory" that I need to populate. I tried adding another $lookup pipeline, but I don't know how the right way to do that.
{
"_id" : ObjectId("607da9c0c7cb26384c7810a6"),
"date" : ISODate("2021-04-20T00:00:00.000+0000"),
"clientID" : "601e6dc61766587af8ce76db",
"medications" : {
"_id" : ObjectId("6065de3aa95e721f587f7528"),
"medication": "med 1"
"schedule": ["9am", "10pm"]
"inventory": "ObjectId("6076d55ab6aeb947dca85877")"
}
}
This is the inventory item
{
"_id" : ObjectId("6076d55ab6aeb947dca85877"),
"item": "Inventory item 1",
"Qty": "10"
}
Expected Output
{
"_id" : ObjectId("607da9c0c7cb26384c7810a6"),
"date" : ISODate("2021-04-20T00:00:00.000+0000"),
"clientID" : "601e6dc61766587af8ce76db",
"medications" : {
"_id" : ObjectId("6065de3aa95e721f587f7528"),
"medication": "med 1"
"schedule": ["9am", "10pm"]
"inventory": {
"_id" : ObjectId("6076d55ab6aeb947dca85877"),
"item": "Inventory item 1",
"Qty": "10"
}
}
}
You can try this one or alternatively use population.
{
from: "medications",
localField: "medications._id",
foreignField: "_id",
as: "medications",
}
, {
$unwind: {
path: "$medications",
preserveNullAndEmptyArrays: true
}
}, {
$lookup: {
from: "inventory",
localField: "inventory._id",
foreignField: "_id",
as: "medications.inventory",
}
}
You may check the $unwind in the official documentation. Just pay attention to the appropriate localField and foreignField.
use populate() to expand nested references.
schemaModel.populate(result, {path: 'result'});
See more at Mongoose docs

Aggregate with lookup and contcat Strings in MongoDb

I have two collections events & members :
events Schema :
{
name : String,
members: [{status : Number, memberId : {type: Schema.Types.ObjectId, ref: 'members'}]
}
events Sample Doc :
"_id" : ObjectId("5e8b0bac041a913bc608d69d")
"members" : [
{
"status" : 4,
"_id" : ObjectId("5e8b0bac041a913bc608d69e"),
"memberId" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"date" : ISODate("2020-04-06T10:59:56.997Z")
},
{
"status" : 1,
"_id" : ObjectId("5e8b0bf2041a913bc608d6a3"),
"memberId" : ObjectId("5e7e2f048f80b46d786bfd67"),
"date" : ISODate("2020-04-06T11:01:06.463Z")
}
],
members Schema :
{
firstname : String
photo : String
}
members Sample Doc :
[{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"photo" : "/users/5e7dbf5b257e6b18a62f2da9/profile/profile-02b13aef6e.png"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"photo" : "/5e7e2f048f80b46d786bfd67/profile/profile-c79f91aa2e.png"
}]
I made a query with aggregate, and lookup to get populated data of members, and I want to concat the photo fields of the members by a string, but I get an error,
How can I do the concat ?
Query :
db.getCollection('events').aggregate([
{ $match: { _id: ObjectId("5e8b0bac041a913bc608d69d")}},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
"Members.firstname" : 1,
"Members.photo": 1,
//"Members.photo": {$concat:["http://myurl", "$Members.photo"]},
"Members._id" : 1,
},
}
])
Result without the concat :
{
"_id" : ObjectId("5e8b0bac041a913bc608d69d"),
"Members" : [
{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"photo" : "/users/5e7dbf5b257e6b18a62f2da9/profile/profile-02b13aef6e.png"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"photo" : "/5e7e2f048f80b46d786bfd67/profile/profile-c79f91aa2e.png"
}
]
}
Error :
$concat only supports strings, not array
You can do that simply by adding pipeline to $lookup stage
db.events.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d"),
},
},
{
$lookup: {
from: "members",
let: { memberId: "$members.memberId" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$memberId"] } } },
{
$project: {
firstname: 1,
photo: { $concat: ["http://myurl", "$photo"] }
}
}
],
as: "Members",
}
},
/** Optional */
{$project : {Members: 1}}
]);
Test : MongoDB-Playground
the alternative of using a pipeline in the above answer
we may use project and group
db.events.aggregate([
{
$match: { _id: ObjectId("5e8b0bac041a913bc608d69d") }
},
{
$unwind: '$members' // to spread the members array into a stream of documents
},
{
$lookup: {
from: "members",
localField: "members.memberId",
foreignField: "_id",
as: "member"
}
},
{
$unwind: '$member' // each document will have array of only one member, so do this unwind to convert it to an object
},
{
$project: { // do the project here to be able to use the $concat operator
'member._id': 1,
'member.firstname': 1,
'member.photo': 1,
'member.photo': { $concat: ['http://myurl', '$member.photo'] } // now we can use the $concat as member is an object, then member.photo exists
}
},
{
$group: { // do that grouping stage to gather all the members belong to the same document in one array again
_id: '$_id',
Members: {
$addToSet: '$member'
}
}
}
])

Mongoose aggregate Lookup - How to filter by specific id

I am trying to make a aggregate pipeline - $lookup to receive from another
collection only items that are not equal to specific _id
for example :
ClinicsCollection :
{_id:1,name:'some name1'}
{_id:2,name:'some name2'}
{_id:3,name:'some name3'}
BusinessCollection :
{_id:1,name:"some business name",clinics:[1,2,3]}
My aggregate pipeline query :
db.business.aggregate([
{$match: {_id: mongoose.Types.ObjectId(businessId)}},
{$lookup:
{from: "ClinicsCollection", localField: "clinics", foreignField: "_id", as: "clinics"}},
]
I want to filter all clinics that are not equal to specific id number let say _id : 1
expected result :
clinics :[
{_id:2,name:'some name2'}
{_id:3,name:'some name3'}
]
How can i achieve that ?
Thanks
You can use below aggregation with mongodb 3.6 and above
You need to just use $match with the child collection as you do with the parent collection in the first stage.
db.BusinessCollection.aggregate([
{ "$match": { "clinics": { "$type": "array" }}},
{ "$lookup": {
"from": "ClinicsCollection",
"let": { "clinics": "$clinics" },
"pipeline": [
{ "$match": {
"$expr": {
"$and": [
{ "$in": ["$_id", "$$clinics"] },
{ "$not": { "$eq": ["$_id", 1] }}
]
}
}}
],
"as": "clinics"
}}
])
You can try below aggregation for mongodb version below 3.6
db.business.aggregate([
{$match : {_id : 1}},
{$lookup : {from : "clinics", localField : "clinics", foreignField : "_id", as : "clinics"}},
{$addFields : {clinics : {$filter : {input : "$clinics", as : "c", cond : {$ne : ["$$c._id", 1]}}}}}
]).pretty()
result
{ "_id" : 1, "name" : "some business name", "clinics" : [ { "_id" : 2, "name" : "some name2" }, { "_id" : 3, "name" : "some name3" } ] }

join two collections in mongoDB

I have below two collections
db.sample.find().pretty()
{
"_id" : ObjectId("5930093eb3aaa7c02d4cbcdc"),
"name" : "Ashish",
"posts" : [
{
"_id" : ObjectId("59301c39028afaf3450e2444"),
"post" : ObjectId("59301c39028afaf3450e2885")
},
{
"_id" : ObjectId("59301c39028afaf3450e2445"),
"post" : ObjectId("59301c39028afaf3450e2889")
}
]
}
and other one
db.posts.find().pretty()
{ "_id" : ObjectId("59301c39028afaf3450e2885"), "title" : "test1" }
{ "_id" : ObjectId("59301c50028afaf3450e2889"), "title" : "test2" }
I want to join these two based on matching posts._id & sample.post._id value.
& create structure showing "Title" value as below:
In short create structure which shows post liked by each user.
"name" : "Ashish",
"posts" : [
{
"post" : ObjectId("59301c39028afaf3450e2889"),
"title" :"test2"
},
{
"post" : ObjectId("59301c39028afaf3450e2885"),
"title" :"test1"
},
we can join two collection using $lookup some thing like this
db.sample.aggregate([
{
$lookup:
{
from: "posts",
localField: "posts.post",
foreignField: "_id",
as: "samplepost"
}
}
])
https://docs.mongodb.com/v3.2/reference/operator/aggregation/lookup/
You can test it
db.sample.aggregate([
{$unwind: "$posts"},
{
$lookup: {
from: "posts",
localField: "posts.post",
foreignField: "_id",
as: "post"
}
},
{
$group: {
_id: "$_id",
name: {$first: "$name"},
posts: {
$push: {
post: {$arrayElemAt: ["$post._id", 0]},
title: {$arrayElemAt: ["$post.title", 0]}
}
}
}
}
])

How to $slice results of foreign collection documents array when mongodb $lookup is used

Here is my code
AbcSchema.aggregate([
{ $match: query },
{
$lookup: { from: 'xyz', localField: '_id', foreignField: 'place_id', as: 'xyzArray' }
}
])
Right now Im getting this result :
{
_id : "abc1",
abcfield1 : "...",
abcfield2 : "...",
xyzArray : [{_id : "xyz1", place_id : "abc1", xyzfield1 : "..."},
{_id : "xyz2", place_id : "abc1", xyzfield1 : "..."},
{_id : "xyz3", place_id : "abc1", xyzfield1 : "..."},
...] //all matching results
}
So now lets say I want only 2 documents in xyzArray, then how can I achieve that?
My requirement is to get limit the 'xyzArray' length to 'n' .
Here is the dynamic query. You can change the number inside the $slice to get the required number of array elements.
db.AbcSchema.aggregate([
{ $match: {_id : "abc1"} },
{
$unwind: "$_id"
},
{
$lookup: { from: 'xyz', localField: '_id', foreignField: 'place_id', as: 'xyzArray' }
},
{ $project: { abcfield1 : 1,
abcfield2 : 1,
xyzArrayArray: { $slice: ["$xyzArray", 2] }
}
}
]).pretty();
One possible solution using $project and $arrayElemAt
db.AbcSchema.aggregate([
{ $match: {_id : "abc1"} },
{
$unwind: "$_id"
},
{
$lookup: { from: 'xyz', localField: '_id', foreignField: 'place_id', as: 'xyzArray' }
},
{ $project: { abcfield1 : 1,
abcfield2 : 1,
firstxyzArray: { $arrayElemAt: [ "$xyzArray", 0 ] },
secondxyzArray: { $arrayElemAt: [ "$xyzArray", 1 ] }
}
}
]).pretty();
Sample Output:-
{
"_id" : "abc1",
"abcfield1" : "11",
"abcfield2" : "22",
"firstxyzArray" : {
"_id" : "xyz1",
"place_id" : "abc1",
"xyzfield1" : "xyzf1"
},
"secondxyzArray" : {
"_id" : "xyz2",
"place_id" : "abc1",
"xyzfield1" : "xyzf1"
}
}