Let's say i have 2 collections
// Post collection:
{
"_id": "61f7a933b209737de4cc2657",
"author": "61d30188cf93e83e08d14112",
"title": "Some title",
"createts": 1643620659355
}
// User collection:
{
"_id": "61d30188cf93e83e08d14112",
"nickname": "Bob",
"link": "bobspage"
}
And i need to get this result
{
"_id": "61f7a933b209737de4cc2657",
"author": {
"_id": "61d30188cf93e83e08d14112",
"nickname": "Bob",
"link": "bobspage"
},
"title": "Some title",
"createts": 1643620659355
}
How can i make a request with aggregation, which will display this output ?
Simply use $lookup in an aggregation query:
First $lookup, which generate a field called author which is an array with all values joined.
To get author as an object get the first result for the array using $arrayElemAt.
db.post.aggregate([
{
"$lookup": {
"from": "user",
"localField": "author",
"foreignField": "_id",
"as": "author"
}
},
{
"$addFields": {
"author": {
"$arrayElemAt": [
"$author",
0
]
}
}
}
])
Example here
Related
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.
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"
}
}
}
])
I have the following two collections:
{
"organizations": [
{
"_id": "1",
"name": "foo",
"users": { "1": "admin", "2": "member" }
},
{
"_id": "2",
"name": "bar",
"users": { "1": "admin" }
}
],
"users": [
{
"_id": "1",
"name": "john smith"
},
{
"_id": "2",
"name": "bob johnson"
}
]
}
The following query works to merge the users into members when I just use an array of the user ids to match, however, the users prop is an object.
{
"collection": "organizations",
"command": "aggregate",
"query": [
{
"$lookup": {
"from": "users",
"localField": "users",
"foreignField": "_id",
"as": "members"
}
}
]
}
What I'm hoping to do is lookup by id then create a members array from the results with the user object including the role (value of the users objects:
{
"_id": "1",
"name": "foo",
"users": {
"1": "admin",
"2": "member"
},
"members": [
{
"_id": "1",
"name": "john smith",
"role": "admin"
},
{
"_id": "2",
"name": "bob johnson",
"role": "user"
}
]
}
Here's the sandbox I have setup: https://mongoplayground.net/p/yhRpeRvJf3u
You really need to change your schema design, this will cause the performance on retrieving data,
$addFields to add new field usersArray convert users object to array using $objectToArray, the format will be k(key) and v(value),
$lookup to join users collection, set localField name to usersArray.k
$addFields, remove usersArray field using $$REMOVE,
$map iterate loop of members array and $reduce to iterate loop of usersArray and get matching role as per _id and merge current fields and role field using $mergeObjects
db.organizations.aggregate([
{
$addFields: {
usersArray: {
$objectToArray: "$users"
}
}
},
{
"$lookup": {
"from": "users",
"localField": "usersArray.k",
"foreignField": "_id",
"as": "members"
}
},
{
$addFields: {
usersArray: "$$REMOVE",
members: {
$map: {
input: "$members",
as: "m",
in: {
$mergeObjects: [
"$$m",
{
role: {
$reduce: {
input: "$usersArray",
initialValue: "",
in: { $cond: [{ $eq: ["$$this.k", "$$m._id"] }, "$$this.v", "$$value"] }
}
}
}
]
}
}
}
}
}
])
Playground
First of all, the problem with your query is you want to use a KEY to do the $lookup, then the members field always gonna be empty.
You are trying to use users as local field, but users is an object, so you need the key (users.1, users.2, ... )
To do this you need to use $objectToArray, which create an object array with two fields: k and v for key and value. So now, you can $lookup with the field users.k.
To get the query you need $unwind before $lookup because you also want the users filed into the new document.
With the new object created using $objectToArray, you can do $unwind to get the values in differents documents. And then $lookup to get the "join".
Here, localField uses the value k created by $objectToArray (the object key).
After that, $set to add the field with the role and $group again into one document.
Ive used _id to get the values without changes between stages, and into members push the members in each collection.
And then, $project to output the values you want. In this case, tha calues "stored" into _id and the array members in "one level" using $reduce.
So, the query you need I think is this:
db.organizations.aggregate([
{
"$match": {
"_id": "1"
}
},
{
"$set": {
"usersArray": {
"$objectToArray": "$users"
}
}
},
{
"$unwind": "$usersArray"
},
{
"$lookup": {
"from": "users",
"localField": "usersArray.k",
"foreignField": "_id",
"as": "members"
}
},
{
"$set": {
"members.role": "$usersArray.v"
}
},
{
"$group": {
"_id": {
"_id": "$_id",
"users": "$users",
"name": "$name"
},
"members": {
"$push": "$members"
}
}
},
{
"$project": {
"members": {
"$reduce": {
"input": "$members",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
},
"users": "$_id.users",
"name": "$_id.name",
"_id": "$_id._id"
}
}
])
Example here
I want to get another datatable field along with response data, as like join ($lookup) in mongodb, but its giving empty output of join table. Here is my code..
Releases.aggregate([
{ "$sort": { "release_date": -1 } },
{ "$limit": 10 },
{ "$lookup": {
"from": "companies", // actual schema name Company
"localField": "company_id",
"foreignField": "_id",
"as": "companyinfo"
} },
])
its giving the empty array companyinfo, and in the schema I create " Company " but in database, its automatic change it with " companies "
here is sample document
{
"_id": {
"$oid": "xxxxxxxxxxxx"
},
"company_id": "xxxxxxxxxxxxxxxxx",
"heading": "testing",
"description": "testing",
"key_point": "filename",
"release_date": "2018-12-01T00:00:00.000Z",
"datetime": {
"$date": "2018-12-03T14:53:57.031Z"
},
"__v": 0,
}
and I want output fields {heading, description, key_point, release_date, companyinfo.company_email} only, any idea ? Thanks in advance
Here is the problem, I am unable to get the following result. Please look into the piece of json and help me out.
This is my data:
[
{
"user_id": "65asdfksadjfk3u4",
"lat": 23.4343,
"long": 15.2382
}
]
Currently my result is:
[
{
"_id": "65asdfksadjfk3u4",
"name": "Srini",
"age": 26,
"some other key": "some other values"
}
]
I need to get the collection from the user_id and add it to the same array object. As you can notice both lat and long are being removed in my current result.
[
{
"_id": "65asdfksadjfk3u4",
"name": "Srini",
"age": 26,
"some other keys": "some other values",
"lat": 23.4343,
"long": 15.2382
}
]
You can append the $lookup stage to join the current pipeline results with the users collections by the user_id fields and then use $mergeObjects in the $replaceRoot to merge the joined documents from users and the current results:
db.collection.aggregate([
/* current pipeline here */
{ "$lookup": {
"from": "users",
"localField": "_id",
"foreignField": "user_id",
"as": "user"
} },
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "$arrayElemAt": [ "$user", 0 ] },
"$$ROOT"
]
}
} },
{ "$project": { "user": 0, "user_id": 0 } }
]);