How do I perform a mongoDB lookup and then merge results into a single data set? - mongodb

Hi I am looking to join data from two tables
What I am looking to do is create a "friend" relationship between two users using a lookup and find out how many emails each friend (if any sent)
here is a mongo DB playground... I am close, I just can't figure out how to get the 'other users' email information
I should only see emails from a's friends (b and c) and NOT any emails sent from a
This playground almost does what I want...
https://mongoplayground.net/p/KKocPm3fzEv
Here is the input for the above playground
db={
"users": [
{
"_id": "a",
"email": "a#test.com",
},
{
"_id": "b",
"email": "b#test.com",
},
{
"_id": "c",
"email": "c#test.com",
}
],
"friends": [
{
"userId": "a",
"otherUserId": "b"
},
{
"userId": "a",
"otherUserId": "c"
},
],
"emailsSent": [
{
"userId": "a",
"number": "25"
},
{
"userId": "b",
"number": "3"
},
]
}
Here is the output from the above playground
[
{
"_id": "a",
"a_myfriends": [
{
"_id": ObjectId("5a934e000102030405000002"),
"otherUserId": "b",
"userId": "a"
},
{
"_id": ObjectId("5a934e000102030405000003"),
"otherUserId": "c",
"userId": "a"
}
],
"email": "a#test.com",
"emailaddr": [
{
"_id": "b",
"email": "b#test.com"
},
{
"_id": "c",
"email": "c#test.com"
}
],
"emailsent": [
{
"_id": ObjectId("5a934e000102030405000001"),
"number": "3",
"userId": "b"
}
]
}
]
There are three arrays of information now... how do I join them all together so each entry in the array is only for 'that' friend?
this is what I'd like to end up with
{
"_id": "a",
"a_myfriends": [
{
"otherUserId": "b",
"email": "b#test.com",
"number": "3"
},
{
"otherUserId": "c",
"email": "c#test.com"
}
]
}
NOTE: I tried concatenating unions from this article, but I think it's not working due to the disparity of IDs for the user (eg _id and userId)
MongoDB: Combine data from multiple collections into one..how?

I think you can reduce the number of collections => reduce the $lookups and data will look more simple
How about this schema of 1 collection?
Even if someone has too many friends like 200.000, you could have an extra field {"extra_friends" "_id"}, and rarely use the a secondary friends collections going rarely max to 2 collections.
users=
[
{
"_id": "a",
"email": "a#test.com",
"emails-send" 25,
"friends" ["b" "c"]
},
{
"_id": "b",
"email": "b#test.com",
"emails-send" 3,
"friends" [....]
},
{
"_id": "c",
"email": "c#test.com",
"emails-send" 0
"friends" [....]
}
]
Query
(for your schema, produces the expected data)
a series of $lookup and $unwind
from users
-to friends (get friends)
-to users (get friends email)
-to emailsSent (get number of emails for those that sended)
some $set in the middle to keep the schema simple
group by _id back and combine those friends
*query could become smaller a bit, but this way its easy to follow, stage by stage and see what is happening
Test code here
db.users.aggregate([
{
"$match": {
"$expr": {
"$eq": [
"$_id",
"a"
]
}
}
},
{
"$lookup": {
"from": "friends",
"localField": "_id",
"foreignField": "userId",
"as": "myfriends"
}
},
{
"$unwind": {
"path": "$myfriends"
}
},
{
"$lookup": {
"from": "users",
"localField": "myfriends.otherUserId",
"foreignField": "_id",
"as": "myfriendsEmails"
}
},
{
"$unwind": {
"path": "$myfriendsEmails"
}
},
{
"$set": {
"myfriends.email": "$myfriendsEmails.email"
}
},
{
"$unset": [
"myfriendsEmails",
"myfriends._id",
"myfriends.userId"
]
},
{
"$lookup": {
"from": "emailsSent",
"localField": "myfriends.otherUserId",
"foreignField": "userId",
"as": "friendsEmails"
}
},
{
"$unwind": {
"path": "$friendsEmails",
"preserveNullAndEmptyArrays": true
}
},
{
"$set": {
"myfriends.number": "$friendsEmails.number"
}
},
{
"$unset": [
"friendsEmails"
]
},
{
"$group": {
"_id": "$_id",
"email": {
"$first": "$email"
},
"friends": {
"$push": "$myfriends"
}
}
}
])

Related

How to replace array of object containing ids with the data using MongoDB aggregation

I am having a collection which contains the data like the following and want to have the desirable output which I have mentioned below.
db={
collectionA: [
{
"id": ObjectId("63b7c24c06ebe7a8fd11777b"),
"uniqueRefId": "UUID-2023-0001",
"products": [
{
"productIndex": 1,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
ObjectId("63b7c2fd06ebe7a8fd117781")
]
},
{
"productIndex": 2,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
ObjectId("63b7c2fd06ebe7a8fd117781"),
ObjectId("63b7c12706ebe7a8fd117778")
]
},
{
"productIndex": 3,
"productCategory": "",
"productOwners": ""
}
]
}
],
collectionB: [
{
"_id": ObjectId("63b7c2fd06ebe7a8fd117781"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
},
{
"_id": ObjectId("63b7c12706ebe7a8fd117778"),
"fullname": "Carry Minatti",
"email": "carry.minatty#pp.com"
},
]
}
Desirable Output = [
{
"id": ObjectId("507f1f77bcf86cd799439011"),
"uniqueRefId": "UUID-2023-0001",
"products": [
{
"productIndex": 1,
"productCategory": ObjectId('614g2f77bff86cd755439021'),
"productOwners": [
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41acd"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
}
]
},
{
"productIndex": 2,
"productCategory": ObjectId('614g2f77bff86cd755439021'),
"productOwners": [
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41acd"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
},
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41ace"),
"fullname": "Carry Minatti",
"email": "carry.minatty#pp.com"
}
]
},
{
"productIndex": 3,
"productCategory": "",
"productOwners": ""
}
]
}
]
In the collectionA we are having other documents as well, its not just one document.
Similarly for collectionB we are having other documents too.
How we can get this desirable output?
I am expecting the mongodb query for getting this solution.
I have implemented the lookup like the following
db.collectionA.aggregate([
{
"$lookup": {
"from": "collectionB",
"localField": "products.productOwners",
"foreignField": "_id",
"as": "inventory_docs"
}
}
])
You can try this:
db.collectionA.aggregate([
{
"$unwind": "$products"
},
{
"$lookup": {
"from": "collectionB",
"localField": "products.productOwners",
"foreignField": "_id",
"as": "products.productOwners"
}
},
{
"$group": {
"_id": {
id: "$id",
uniqueRefId: "$uniqueRefId"
},
"products": {
"$push": "$products"
}
}
},
{
"$project": {
id: "$_id.id",
uniqueRefId: "$_id.uniqueRefId",
products: 1,
_id: 0
}
}
])
Playground link.
In this query, we do the following:
First we unwind the products array, using $unwind.
Then we calculate productOwners, using $lookup.
Then we group the unwinded elements, using $group.
Finally we, project the desired output using $project.

Update data in mongo db with condition of another collection

I have 2 collection in mongodb: Account, Information.
Account:
{
"_id": {
"$oid": "6348dc197a7b552660170d8b"
},
"username": "12345",
"password": "123dsgfdsgdfsg",
"email": "1243",
"role": "123",
"_infoid": {
"$oid": "6348dc197a7b552660170d8a"
}
}
Information:
{
"_id": {
"$oid": "6348dc197a7b552660170d8a"
},
"avatar": "hello",
"name": "Abcd",
"phonenumber": "012345678",
"address": "abcd"
}
I wanna change "phonenumber" to "123" but I just have "_id" of Account. Can I change it with aggregation pipeline?
Does this seem what you try to achieve?
// populate database with test data
use("test_db")
db.account.insertOne({
"_id": "6348dc197a7b552660170d8b",
"username": "12345",
"password": "123dsgfdsgdfsg",
"email": "1243",
"role": "123",
"_infoid": "6348dc197a7b552660170d8a"
})
db.information.insertOne({
"_id": "6348dc197a7b552660170d8a",
"avatar": "hello",
"name": "Abcd",
"phonenumber": "012345678",
"address": "abcd"
})
// define some test variables to use
let some_account_id = "6348dc197a7b552660170d8b"
let new_phone_number = "+9876543210"
// change data
db.account.aggregate([
{
$match: {_id: some_account_id}
},
{
$addFields: {phonenumber: new_phone_number}
},
{
$project: {phonenumber: 1, _id: "$_infoid"}
},
{
$merge:{
into: "information",
whenNotMatched: "fail",
}
}
])
// show final results
db.information.find()
Result:
[
{
"_id": "6348dc197a7b552660170d8a",
"avatar": "hello",
"name": "Abcd",
"phonenumber": "+9876543210",
"address": "abcd"
}
]
You don't need to duplicate _id, findOneAndUpdate() can be executed for this use case.
The definition of it is:
db.collection.findOneAndUpdate( filter, update, options )
which updates a single document based on the filter and sort criteria.
Below you can refer to the link for more details:
https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndUpdate/
.
Performa a $lookup and perform some wrangling to your desired format. Finally do a $merge to update to the collection Information
db.Information.aggregate([
{
"$lookup": {
"from": "Account",
"localField": "_id",
"foreignField": "_infoid",
"pipeline": [
{
"$project": {
role: 1
}
}
],
"as": "AccountLookup"
}
},
{
"$unwind": "$AccountLookup"
},
{
$set: {
phonenumber: "$AccountLookup.role"
}
},
{
$unset: "AccountLookup"
},
{
"$merge": {
"into": "Information",
"on": "_id",
"whenMatched": "merge",
"whenNotMatched": "discard"
}
}
])
Here is the Mongo Playground for your reference.

I want to aggregate data array inside another array in mongodb

I want to aggregate MongoDB documents which is having arrays inside of an array. my document was like the below.
{
"_id": "6257e31d11a9d5231c05c084",
"name": "Test Name 1",
"phone": "1234567891",
"visits": [
{
"_id": "6257e31d11a9d5231c05c069",
"date": "2-7-2021",
"samples": [
"6257f8855197613b641d494e",
....
],
"products_detailed": [
"5d725cd2c4ded7bcb480eab2",
.....
]
},
...........
]
}
and I want to get the output line below
{
"_id": "6257e31d11a9d5231c05c084",
"name": "Test Name 1",
"phone": "1234567891",
"visits": [
{
"_id": "6257e31d11a9d5231c05c069",
"date": "2-7-2021",
"samples": [
{
"_id": "6257f8855197613b641d494e",
"product_name": "Samor",
"price": 250
},
........
],
"products_detailed": [
{
"_id": "5d725cd2c4ded7bcb480eab2",
"product_name": "Pahad",
"price": 100
},
............
]
},
.........................
]
}
how can I get like this? I tried to use $lookup & group to get the output, but I am not getting the output as required me.
Since you have a list of visits on each document, one way to go is to $unwind and then $group at the end, like this:
db.Main.aggregate([
{
$unwind: "$visits"
},
{
"$lookup": {
"from": "Samples",
"localField": "visits.samples",
"foreignField": "_id",
"as": "samples"
}
},
{
"$lookup": {
"from": "Product Detailed",
"localField": "visits.products_detailed",
"foreignField": "_id",
"as": "products_detailed"
}
},
{
$project: {
name: 1,
phone: 1,
"visits._id": 1,
"visits.date": 1,
"visits.products_detailed": "$products_detailed",
"visits.samples": "$samples"
}
},
{
$group: {
_id: 0,
name: {$first: "$name"},
visits: {$push: "$visits"}
}
}
])
As you can see on the playground, on your data sample it will return:
[
{
"_id": 0,
"name": "Test Name 1",
"visits": [
{
"_id": "6257e31d11a9d5231c05c069",
"date": "2-7-2021",
"products_detailed": [
{
"_id": "5d725cd2c4ded7bcb480eab2",
"price": 100,
"product_name": "Pahad"
}
],
"samples": [
{
"_id": "6257f8855197613b641d494e",
"price": 250,
"product_name": "Samor"
}
]
}
]
}
]

Mongodb aggregation lookup to add field in each array with condition

I have 3 collections.
User:
{
"_id":ObjectId("60a495cdd4ba8b122899d415"),
"email":"br9#gmail.com",
"username":"borhan"
}
Panel:
{
"_id": ObjectId("60a495cdd4ba8b122899d417"),
"name": "borhan",
"users": [
{
"role": "admin",
"joined": "2021-05-19T04:35:47.474Z",
"status": "active",
"_id": ObjectId("60a495cdd4ba8b122899d418"),
"user": ObjectId("60a495cdd4ba8b122899d415")
},
{
"role": "member",
"joined": "2021-05-19T04:35:47.474Z",
"status": "active",
"_id": ObjectId("60a49600d4ba8b122899d41a"),
"user": ObjectId("60a34e167958972d7ce6f966")
}
],
}
Team:
{
"_id":ObjectId("60a495e0d4ba8b122899d419"),
"title":"New Teams",
"users":[
ObjectId("60a495cdd4ba8b122899d415")
],
"panel":ObjectId("60a495cdd4ba8b122899d417")
}
I want to receive a output from querying Panel colllection just like this:
{
"_id": ObjectId("60a495cdd4ba8b122899d417"),
"name": "borhan",
"users": [
{
"role": "admin",
"joined": "2021-05-19T04:35:47.474Z",
"status": "active",
"_id": ObjectId("60a495cdd4ba8b122899d418"),
"user": ObjectId("60a495cdd4ba8b122899d415"),
"teams":[
{
"_id":ObjectId("60a495e0d4ba8b122899d419"),
"title":"New Teams",
"users":[
ObjectId("60a495cdd4ba8b122899d415")
],
"panel":ObjectId("60a495cdd4ba8b122899d417")
}
]
},
{
"role": "member",
"joined": "2021-05-19T04:35:47.474Z",
"status": "active",
"_id": ObjectId("60a49600d4ba8b122899d41a"),
"user": ObjectId("60a34e167958972d7ce6f966")
}
],
}
I mean i want to add teams field (which is array of teams that user is existed on it) to each user in Panel collection
Here is my match query in mongoose to select specific panel:
panel_model.aggregate([
{
$match: {
users: {
$elemMatch: {user: ObjectId("60a495cdd4ba8b122899d415"), role:"admin"}
}
}
},
])
Is it possible to get my output with $lookup or $addFields aggregations?
You need to join all three collections,
$unwind to deconstruct the array
$lookup there are two kind of lookups which help to join collections. First I used Multiple-join-conditions-with--lookup, and I used standrad lookup to join Users and Teams collections.
$match to match the user's id
$expr - when you use $match inside lookup, u must use it.
$set to add new fields
$group to we already destructed using $unwind. No we need to restructure it
here is the code
db.Panel.aggregate([
{ $unwind: "$users" },
{
"$lookup": {
"from": "User",
"let": { uId: "$users.user" },
"pipeline": [
{
$match: {
$expr: {
$eq: [ "$_id", "$$uId" ]
}
}
},
{
"$lookup": {
"from": "Team",
"localField": "_id",
"foreignField": "users",
"as": "teams"
}
}
],
"as": "users.join"
}
},
{
"$set": {
"users.getFirstElem": {
"$arrayElemAt": [ "$users.join", 0 ]
}
}
},
{
$set: {
"users.teams": "$users.getFirstElem.teams",
"users.join": "$$REMOVE",
"users.getFirstElem": "$$REMOVE"
}
},
{
"$group": {
"_id": "$_id",
"name": { "$first": "name" },
"users": { $push: "$users" }
}
}
])
Working Mongo playground
Note : Hope the panel and user collections are in 1-1 relationship. Otherwise let me know

How to add embedded field with matching documents

I'm using Python with pymongo to query from the database.
I have 3 different collections:
1st:
# Projects collection
{
"_id": "A",
},
{
"_id": "B",
},
{
"_id": "C"
},
..
2nd:
# Episodes collection
{
"_id": "A/Episode01",
"project": "A",
"name": "Episode01"
},
{
"_id": "A/Episode02",
"project": "A",
"name": "Episode02"
},
{
"_id": "B/Episode01",
"project": "B",
"name": "Episode01"
},
..
3rd:
# Sequences collection
{
"_id": "A/Episode01/Sequence01",
"project": "A",
"episode": "Episode01",
"name": "Sequence01"
},
{
"_id": "A/Episode02/Sequence02",
"project": "A",
"episode": "Episode02",
"name": "Sequence02"
},
{
"_id": "B/Episode01/Sequence01",
"project": "B",
"episode": "Episode01",
"name": "Sequence01"
},
..
I want to use aggregate to query project A and get all of its corresponding episodes and sequences like this:
{
"_id": "A",
"episodes":
[
{
"_id": "A/Episode01",
"project": "A",
"name": "Episode01",
"sequences":
[
{
"_id": "A/Episode01/Sequence01",
"project": "A",
"episode": "Episode01",
"name": "Sequence01"
},
]
},
{
"_id": "A/Episode02",
"project": "A",
"name": "Episode02",
"sequences":
[
{
"_id": "A/Episode02/Sequence02",
"project": "A",
"episode": "Episode02",
"name": "Sequence02"
},
]
},
]
}
I can get as far as getting the proper episodes, but I'm not sure how to add an embed field for any matching sequences. Is it possible to do this all in a single pipeline query?
Right now my query is looking like this:
[
{"$match": {
"_id": "A"}
},
{"$lookup": {
"from": "episodes",
"localField": "_id",
"foreignField": "project",
"as": "episodes"}
},
{"$group": {
"_id": {
"_id": "$_id",
"episodes": "$episodes"}
}}
]
You can do like following
use $match to match the document
use uncorrelated queries to join two collection. But normal joining also possible as you have written. This is easier when we get some complex situations.
Mongo script is given below
[
{
"$match": {
"_id": "A"
}
},
{
$lookup: {
from: "Episodes",
let: {
id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$project",
"$$id"
]
}
}
},
{
$lookup: {
from: "Sequences",
let: {
epi: "$name"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$episode",
"$$epi"
]
}
}
}
],
as: "sequences"
}
}
],
as: "episodes"
}
}
]
Working Mongo playground
Update 01
Using standard lookup
[
{
"$match": {
"_id": "A"
}
},
{
"$lookup": {
"from": "Episodes",
"localField": "_id",
"foreignField": "project",
"as": "episodes"
}
},
{
$unwind: "$episodes"
},
{
"$lookup": {
"from": "Sequences",
"localField": "episodes.name",
"foreignField": "episode",
"as": "episodes.sequences"
}
},
{
$group: {
_id: "$episodes._id",
episodes: {
$addToSet: "$episodes"
}
}
}
]
Working Mongo playground