join two collections in mongoDB - 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]}
}
}
}
}
])

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

MongoDB $lookup

I have a join collection - and I want to pull back PEOPLE data with their Parents...
How can I do this -
PEOPLE
[
{
"_id" : ObjectId("3a9ccf7de6348936d88b3601"),
"first_name" : "John",
"last_name" : "Doe"
},
{
"_id" : ObjectId("3a9ccf7de6348936d88b3602"),
"first_name" : "Jane",
"last_name" : "Doe"
},
{
"_id" : ObjectId("3a9ccf7de6348936d88b3603"),
"first_name" : "Bobby",
"last_name" : "Doe"
}
]
RELATIONS
[
{
"_id" : ObjectId("5aa9a283e40f140014485116"),
"person_id" : ObjectId("3a9ccf7de6348936d88b3603"),
"parent_id" : ObjectId("3a9ccf7de6348936d88b3601"),
"position": "father"
},
{
"_id" : ObjectId("5aa9a283e40f140014485116"),
"person_id" : ObjectId("3a9ccf7de6348936d88b3603"),
"parent_id" : ObjectId("3a9ccf7de6348936d88b3602"),
"position": "mother"
}
]
I want something like this:
[
{
"_id" : ObjectId("3a9ccf7de6348936d88b3603"),
"first_name" : "Bobby",
"last_name" : "Doe",
"relations: : [
{
"_id" : ObjectId("3a9ccf7de6348936d88b3602"),
"first_name" : "Jane",
"last_name" : "Doe",
"position": "mother"
},
{
"_id" : ObjectId("3a9ccf7de6348936d88b3601"),
"first_name" : "John",
"last_name" : "Doe",
"position": "father"
}
]
}
]
I know I need aggregate and $lookup. but I cant get past the most basic
db.getCollection('people')
.aggregate([
{ $lookup: {
from: 'relations',
localField: 'person_id',
foreignField: '_id',
as: 'relations'
}
}
])
You need to run $lookup twice and second one should have people as a "from" value:
db.people.aggregate([
{
$lookup: {
from: "relations",
localField: "_id",
foreignField: "person_id",
as: "relations"
}
},
{
$lookup: {
from: "People",
localField: "relations._id",
foreignField: "_id",
as: "relations"
}
}
])
Mongo Playground
here's another way to do it using a nested lookup/sub-pipeline. also shows the position of relations.
db.people.aggregate(
[
{
$lookup:
{
from: 'relations',
let: { person_id: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ["$person_id", "$$person_id"] }
}
},
{
$lookup: {
from: "people",
localField: "parent_id",
foreignField: "_id",
as: "person"
}
},
{
$replaceWith: {
$mergeObjects: [{ $arrayElemAt: ["$person", 0] }, "$$ROOT"]
}
},
{
$project: {
_id: "$parent_id",
position: 1,
first_name: 1,
last_name: 1
}
}
],
as: 'relations'
}
}
])
https://mongoplayground.net/p/EJB-1WfanuY

$addFields where condition

I want to add extra field to lookup result but i need a condition here. Every user has a relationship status (local field) and it should be added to lookup result for each user, but It's adding every 'status' to every user.
db.getCollection('relationships').aggregate([
{$match: {
user_id: ObjectId("5d3dbe07ea97db13d5b73195")}
},
{
$lookup:
{
from: 'users',
localField: 'relationship.user_id',
foreignField: '_id',
as: 'userss'
}
},
{
$addFields: {
"userss.status": "$relationship.status"
}
},
])
User Schema
{
"_id" : ObjectId("5d3dbe07ea97db13d5b73195"),
"username" : "dipper",
"email" : "test#test.com"
}
Realtionship Schema
{
"user_id" : ObjectId("5d3dbe07ea97db13d5b73195"),
"relationship" : [
{
"user_id" : ObjectId("5d3dbe17ea97db13d5b73196"),
"status" : 0
},
{
"user_id" : ObjectId("5d3dbe28ea97db13d5b73197"),
"status" : 1
}
]
}
I need a result like:
{
"_id" : ObjectId("5d3dbe07ea97db13d5b73196"),
"username" : "mabel",
"email" : "test#test.com",
"status": 0
}
{
"_id" : ObjectId("5d3dbe07ea97db13d5b73197"),
"username" : "wendy",
"email" : "test#test.com",
"status": 1
}
You need $unwind to get single document per relationship:
db.relationships.aggregate([
{
$match: { user_id: ObjectId("5d3dbe07ea97db13d5b73195") }
},
{
$unwind: "$relationship"
},
{
$lookup: {
from: "users",
localField: "relationship.user_id",
foreignField: "_id",
as: "user"
}
},
{
$unwind: "$user"
},
{
$project: {
_id: "$relationship.user_id",
username: "$user.username",
email: "$user.email",
status: "$relationship.status"
}
}
])

Query to get specific sub-doc using aggregation in 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.

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