MongoDB $lookup - mongodb

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

Related

MongoDB Aggregate two collections and flatten the second collection

Collection - 1 (users)
{
"_id" : "aandtv10#gmail.com",
"updateTimestamp" : "2022-07-19T11:59:18.029Z",
"userConsent" : null,
"firstName" : "Aand",
"lastName" : "Aa",
"fullName" : "Aand Aa",
"orgRootName" : "Black&Veatch",
"relationType" : "Contractor"
}
Collection -2 (2 documents)
/* 1 */
{
"_id" : ObjectId("62e9296bf06b3bfdc93be0da"),
"lastUpdated" : "2022-08-02T13:40:59.265Z",
"email" : "aandtv10#gmail.com",
"userName" : "Aand Aa",
"docType" : "dose2",
"administeredTimestamp" : "2022-10-02T11:10:55.784Z",
"uploadTimestamp" : "2022-08-02T13:40:59.265Z",
"manufacturer" : "Novavax"
}
/* 2 */
{
"_id" : ObjectId("62e911cf5b6e67c36c4f843e"),
"lastUpdated" : "2022-08-02T12:00:15.201Z",
"email" : "aandtv10#gmail.com",
"userName" : "Aand Aa",
"templateName" : "Covid-Vaccine",
"docType" : "dose1",
"exemptType" : null,
"administeredTimestamp" : "2022-08-23T17:16:20.347Z",
"uploadTimestamp" : "2022-08-02T12:00:15.202Z",
"manufacturer" : "Novavax"
}
Expected Result:
{
"_id" : "aandtv10#gmail.com",
"updateTimestamp" : "2022-07-19T11:59:18.029Z",
"firstName" : "Aand",
"lastName" : "Aa",
"fullName" : "Aand Aa",
"orgRootName" : "Black&Veatch",
"relationType" : "Contractor",
"dose1_administeredTimestamp" : "2022-08-23T17:16:20.347Z",
"dose1_manufacturer" : "Novavax",
"dose2_administeredTimestamp" : "2022-10-02T11:10:55.784Z",
"dose2_manufacturer" : "Novavax"
}
Query Used:
db.users.aggregate([{
"$match": {
"_id": "aandtv10#gmail.com"
}
},{
$lookup: {
from: "documents",
localField: "_id",
foreignField: "email",
as: "documents"
}}, { $unwind: "$documents" },{
"$group": {
"fullName": { $max: "$identity.fullName" },
"_id":"$_id",
"relationship": { $max:"$seedNetwork.relationType" },
"registration_date": { $max:"$seedNetwork.seededTimestamp" },
"vaccination_level": { $max: "" },
"exemption_declination_date": { $max:"N/A" },
"exemption_verification": { $max: "N/A" },
"dose1_date" : { $max: { $cond: { if: {$eq: ["$docType", "dose1" ]}, then: "$administeredTimestamp", else: "<false-case>" } } }
}
}
]);
One option is using $reduce with $arrayToObject:
db.users.aggregate([
{$match: {_id: "aandtv10#gmail.com"}},
{$set: {data: "$$ROOT"}},
{$lookup: {
from: "documents",
localField: "_id",
foreignField: "email",
as: "documents"
}
},
{$project: {
_id: 0,
data: 1,
documents: {
$reduce: {
input: "$documents",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{k: {$concat: ["$$this.docType", "_administeredTimestamp"]},
v: "$$this.administeredTimestamp"},
{k: {$concat: ["$$this.docType", "_manufacturer"]},
v: "$$this.manufacturer"}
]
]
}
}
}
}
},
{$replaceRoot: {newRoot: {$mergeObjects: ["$data", {$arrayToObject: "$documents"}]}}}
])
See how it works on the playground example

MongoDB Aggregate - how to have TWO '$lookup' operations work?

I have the following aggregate query:
db.getCollection('village').aggregate([
{
"$match": { _id: "111" }
},
{
"$lookup": {
from: "character",
localField: "chieftainId",
foreignField: "_id",
as: "chieftain"
},
"$lookup": {
from: "character",
localField: "villagerIds",
foreignField: "_id",
as: "villager"
}
},
{ "$project" : { "villagerIds" : 0}}
])
And this is its result:
{
"_id" : "111",
"name" : "MyVillage",
"chieftainId" : "222",
"reputation" : 0,
"villagers" : [
{
"_id" : "333",
"name" : "Bortan",
"age" : 21,
"bloodlineId" : "7f02191f-90af-406e-87ff-41d5b4387999",
"villageId" : "foovillage",
"professionId" : "02cbb10a-6c0f-4249-a932-3f40e12d32c5"
},
{
"_id" : "444",
"name" : "Blendi",
"age" : 21,
"bloodlineId" : "b3a8ffeb-27aa-4e2e-a8e6-b382554f326a",
"villageId" : "foovillage",
"professionId" : "45dc9350-c84a-491d-a49a-524834dd5773"
}
]
}
As you can see villagerIds has been resolved to villagers. However chieftainId has not been resolved to chieftain. When I omit the second $lookup (with villager) then chieftain is being resolved successfully. It seems that only one $lookup will be effective and not both. Any idea how I can make both work?
Use separate pipeline for both $lookup
db.getCollection('village').aggregate([
{
"$match": { _id: "111" }
},
{
"$lookup": {
from: "character",
localField: "chieftainId",
foreignField: "_id",
as: "chieftain"
}
},
{
"$lookup": {
from: "character",
localField: "villagerIds",
foreignField: "_id",
as: "villager"
}
},
{ "$project" : { "villagerIds" : 0}}
])

$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"
}
}
])

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