I'm refactoring my Mongo database to put users in a separate collection, and can't figure out how to save the result of this updated query so that plan names become user IDs:
db.plans.aggregate([{
$lookup: {
from: "users",
localField: "person",
foreignField: "name",
as: "person"
}
}, {
$lookup: {
from: "users",
localField: "place",
foreignField: "name",
as: "place"
}
}, {
$unwind: {
path: "$person"
}
}, {
$unwind: {
path: "$place"
}
},
{
$set: {
"person": "$person.id",
"place": "$place.id",
}
}
])
For example from this:
{
"_id": "aeaab905-2d8b-4e26-9244-918956496c35",
"id": "aeaab905-2d8b-4e26-9244-918956496c35",
"person": "Alice",
"place": "Bob"
}
to this:
{
"_id": "aeaab905-2d8b-4e26-9244-918956496c35",
"id": "aeaab905-2d8b-4e26-9244-918956496c35",
"person": "a7609095-869e-4800-9fac-fefc373c37a5",
"place": "2bd8f68d-721a-4106-bd7f-951236c3593d"
}
where db.users is:
[{
"_id": "a7609095-869e-4800-9fac-fefc373c37a5",
"id": "a7609095-869e-4800-9fac-fefc373c37a5",
"name": "Alice"
},
{
"_id": "2bd8f68d-721a-4106-bd7f-951236c3593d",
"id": "2bd8f68d-721a-4106-bd7f-951236c3593d",
"name": "Bob"
}]
As Alex Blex pointed out, $merge worked:
db.plans.aggregate([{
$lookup: {
from: "users",
localField: "person",
foreignField: "name",
as: "person"
}
}, {
$lookup: {
from: "users",
localField: "place",
foreignField: "name",
as: "place"
}
}, {
$unwind: {
path: "$person"
}
}, {
$unwind: {
path: "$place"
}
},
{
$set: {
"person": "$person.id",
"place": "$place.id",
}
}, { $merge: { into: "plans", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } }
])
Related
I have a problem with how to lookup nested array, for example i have 4 collections.
User Collection
"user": [
{
"_id": "1234",
"name": "Tony",
"language": [
{
"_id": "111",
"language_id": "919",
"level": "Expert"
},
{
"_id": "111",
"language_id": "920",
"level": "Basic"
}
]
}
]
Language Collection
"language": [
{
"_id": "919",
"name": "English"
},
{
"_id": "920",
"name": "Chinese"
}
]
Job
"job": [
{
"_id": "10",
"title": "Programmer",
"location": "New York"
}
],
CvSubmit Collection
"cvsubmit": [
{
"_id": "11",
"id_user": "1234",
"id_job": "11"
}
]
And my query aggregation is:
db.cvsubmit.aggregate([
{
$lookup: {
from: "user",
localField: "id_user",
foreignField: "_id",
as: "id_user"
}
},
{
$lookup: {
from: "language",
localField: "id_user.language.language_id",
foreignField: "_id",
as: "id_user.language.language_id"
}
},
])
But the result is:
[
{
"_id": "11",
"id_job": "11",
"id_user": {
"language": {
"language_id": [
{
"_id": "919",
"name": "English"
},
{
"_id": "920",
"name": "Chinese"
}
]
}
}
}
]
I want the result like this, also showing all user data detail like name:
[
{
"_id": "11",
"id_job": "11",
"id_user": {
"_id": "1234",
"name": "Tony"
"language": [
{
"_id": "919",
"name": "English",
"Level": "Expert"
},
{
"_id": "920",
"name": "Chinese",
"level": "Basic"
}
]
}
}
]
Mongo Playground link https://mongoplayground.net/p/i0yCucjruey
Thanks before.
$lookup with user collection
$unwind deconstruct id_user array
$lookup with language collection and return in languages field
$map to iterate look of id_user.language array
$reduce to iterate loop of languages array returned from collection, check condition if language_id match then return name
db.cvsubmit.aggregate([
{
$lookup: {
from: "user",
localField: "id_user",
foreignField: "_id",
as: "id_user"
}
},
{ $unwind: "$id_user" },
{
$lookup: {
from: "language",
localField: "id_user.language.language_id",
foreignField: "_id",
as: "languages"
}
},
{
$addFields: {
languages: "$$REMOVE",
"id_user.language": {
$map: {
input: "$id_user.language",
as: "l",
in: {
_id: "$$l._id",
level: "$$l.level",
name: {
$reduce: {
input: "$languages",
initialValue: "",
in: {
$cond: [
{ $eq: ["$$this._id", "$$l.language_id"] },
"$$this.name",
"$$value"
]
}
}
}
}
}
}
}
}
])
Playground
You database structure is not accurate as per NoSQL, there should be max 2 collections, loot of join using $lookup and $unwind will cause performance issues.
I have three collection wanted to fetch records bases on their reference id, I got the result but wanted to transform the data how to make it.
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "users"
}
},
{
$lookup: {
from: "comments",
let: {
p_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$post_id",
"$$p_id"
]
}
}
}
],
as: "comments"
}
},
{
$lookup: {
from: "users",
localField: "comments.sender_id",
foreignField: "_id",
as: "commented_user"
}
},
])
I am getting this result but what I want merge the commented_user into comments mean who has comments fetch the record.
[
{
"_id": ObjectId("5eeb02881982961ada625c7d"),
"commented_user": [
{
"_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"first_name": "James",
"last_name": "Smith",
"timestamp": 1.582106995137e+12
}
],
"comments": [
{
"_id": ObjectId("5eeb08e26fb7f270e4077617"),
"date": 1.592461538923e+12,
"post_id": ObjectId("5eeb02881982961ada625c7d"),
"sender_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"text": "Nice One "
}
],
"created_by": ObjectId("5e4e74eb380054797d9db623"),
"created_users": [],
"date": 1.589441206774e+12,
"title": "Covid19"
}
]
Would better if I get each commented detail under comments like below ?
"comments": [
{
"_id": ObjectId("5eeb08e26fb7f270e4077617"),
"date": 1.592461538923e+12,
"post_id": ObjectId("5eeb02881982961ada625c7d"),
"sender_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"text": "Nice One ",
"commented_user": [
{
"_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"first_name": "James",
"last_name": "Smith",
"timestamp": 1.582106995137e+12
}
],
}
],
Here is mongoplayground https://mongoplayground.net/p/uw0kMTChFa0
You could use $set and $unset or $project to format the outputs
Example with unset or Example with project
Updated Example. You need to unwind the comments array then perform lookup for each comment to get the commented user.
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: {
p_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$post_id",
"$$p_id"
]
}
}
}
],
as: "comments"
}
},
{
$unwind: "$comments"
},
{
$lookup: {
from: "users",
localField: "comments.sender_id",
foreignField: "_id",
as: "commented_user"
}
},
{
$set: {
"comments.commented_user": "$commented_user",
}
},
{
$unset: "commented_user"
}
])
Lookup with three collections re-manage data. I got confuse which to use. Please guide on this
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: {
p_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$post_id",
"$$p_id"
]
}
}
}
],
as: "comments"
}
},
{
$lookup: {
from: "users",
localField: "comments.sender_id",
foreignField: "_id",
as: "commented_user"
}
}
])
There are three collection posts, user, and comments we would like to get who has comment for a post, Commented user should come under comments like this
[
{
"_id": ObjectId("5eeb02881982961ada625c7d"),
"commented_user": [
{
"_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"first_name": "James",
"last_name": "Smith",
"timestamp": 1.582106995137e+12
},
{
"_id": ObjectId("5e4d0973babf2b74ca868f6d"),
"first_name": "Alex",
"last_name": "Jimy",
"timestamp": 1.582106995139e+12
}
],
"comments": [
{
"_id": ObjectId("5eeb08e26fb7f270e4077617"),
"date": 1.592461538924e+12,
"post_id": ObjectId("5eeb02881982961ada625c7d"),
"sender_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"text": "Nice ",
"commented_user": {
"_id": ObjectId("5e4d0973babf2b74ca868f4d"),
"first_name": "James",
"last_name": "Smith",
"timestamp": 1.582106995137e+12
},
},
{
"_id": ObjectId("5eeb08e26fb7f270e4077618"),
"date": 1.592461538923e+12,
"post_id": ObjectId("5eeb02881982961ada625c7d"),
"sender_id": ObjectId("5e4d0973babf2b74ca868f6d"),
"text": "Nice One",
"commented_user": {
"_id": ObjectId("5e4d0973babf2b74ca868f6d"),
"first_name": "Alex",
"last_name": "Jimy",
"timestamp": 1.582106995137e+12
},
}
],
"created_by": ObjectId("5e4e74eb380054797d9db623"),
"created_users": [],
"date": 1.589441206774e+12,
"title": "Covid19"
}
]
here is my attempt https://mongoplayground.net/p/UwRjj-er0K5
Please help on this thanks
You can do something like this:
The strategy is $unwinding the results so we can match per comment and then reconstruct the former structure.
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: {
p_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$post_id",
"$$p_id"
]
}
}
}
],
as: "comments"
}
},
{
$unwind: "$comments"
},
{
$lookup: {
from: "users",
localField: "comments.sender_id",
foreignField: "_id",
as: "commented_user"
}
},
{
$unwind: "$commented_user"
},
{
$addFields: {
comments: {
$mergeObjects: [
"$comments",
{
commented_user: "$commented_user"
}
]
}
}
},
{
$group: {
_id: "$_id",
comments: {
$push: "$comments"
},
created_by: {
$first: "$created_by"
},
created_users: {
$first: "$created_users"
},
date: {
$first: "$date"
},
title: {
$first: "$title"
},
}
}
])
Mongo Playground
Starting with your the code you have attempted, you can just move your second $lookup into the first $lookup's pipeline, then $unwind it
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: { p_id: "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$post_id", "$$p_id"] }
}
},
{ // second level $lookup
$lookup: {
from: "users",
localField: "sender_id",
foreignField: "_id",
as: "commented_user"
}
},
{ // $unwind to get single object instead of array
$unwind: "$commented_user"
}
],
as: "comments"
}
},
])
Mongo Playground
Note for OP: There is discussion to get the latest comment instead. With this approach you can $sort and $limit in the outermost $lookup
db.post.aggregate([
{
$lookup: {
from: "users",
localField: "created_by",
foreignField: "_id",
as: "created_users"
}
},
{
$lookup: {
from: "comments",
let: { p_id: "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$post_id", "$$p_id"] }
}
},
{
$sort: { date: -1 } // or { _id: -1 }
},
{
$limit: 1 // limit to only one element
},
{
$lookup: {
from: "users",
localField: "sender_id",
foreignField: "_id",
as: "commented_user"
}
},
{
$unwind: "$commented_user"
}
],
as: "comments"
}
},
{ // // $unwind to get single object instead of array
$unwind: "$comments"
}
])
Mongo Playground
I have two collections:
// users
{
_id: "5cc7c8773861275845167f7a",
name: "John",
accounts: [
{
"_id": "5cc7c8773861275845167f76",
"name": "Name1",
},
{
"_id": "5cc7c8773861275845167f77",
"name": "Name2",
}
]
}
// transactions
{
"_id": "5cc7c8773861275845167f75",
"_account": "5cc7c8773861275845167f76",
}
Using lookup I want to populate _account field in transactions collection with respective element from users.accounts array.
So, I want the final result as:
{
"_id": "5cc7c8773861275845167f75",
"_account": {
"_id": "5cc7c8773861275845167f76",
"name": "Name1",
},
}
I have already tried using this code:
db.transactions.aggregate([
{
$lookup:
{
from: "users.accounts",
localField: "_account",
foreignField: "_id",
as: "account"
}
}
])
In the result account array comes as empty.
What is the correct way to do it ?
You can use below aggregation with mongodb 3.6 and above
db.transactions.aggregate([
{ "$lookup": {
"from": "users",
"let": { "account": "$_account" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$account", "$accounts._id"] } } },
{ "$unwind": "$accounts" },
{ "$match": { "$expr": { "$eq": ["$$account", "$accounts._id"] } } }
],
"as": "_account"
}},
{ '$unwind': '$_account' }
])
Try with this
I think case 1 is better.
1)-
db.getCollection('transactions').aggregate([
{
$lookup:{
from:"user",
localField:"_account",
foreignField:"accounts._id",
as:"trans"
}
},
{
$unwind:{
path:"$trans",
preserveNullAndEmptyArrays:true
}
},
{
$unwind:{
path:"$trans.accounts",
preserveNullAndEmptyArrays:true
}
},
{$match: {$expr: {$eq: ["$trans.accounts._id", "$_account"]}}},
{$project:{
_id:"$_id",
_account:"$trans.accounts"
}}
])
2)-
db.getCollection('users').aggregate([
{
$unwind:{
path:"$accounts",
preserveNullAndEmptyArrays:true
}
},
{
$lookup:
{
from: "transactions",
localField: "accounts._id",
foreignField: "_account",
as: "trans"
}
},
{$unwind:"$trans"},
{
$project:{
_id:"$trans._id",
_account:"$accounts"
}
}
])
Database structure:
db={
"collection": [
{
"key": 15,
"name": "srk",
},
{
"key": 12,
"name": "suman",
}
],
"other": [
{
"key": 15,
"name": "miki",
"category": "dish"
},
{
"key": 15,
"name": "mira",
"category": "air"
},
{
"key": 15,
"name": "manas",
"category": "air"
},
{
"key": 166,
"name": "sibu",
"category": "dish"
}
]
}
query i am trying:
db.collection.aggregate([
{
$lookup: {
from: "other",
localField: "key",
foreignField: "key",
as: "inventory_docs"
}
},
{
$match: {
key: 15,
{ "inventory_docs":
{ category: 'dish'}
}
}
}
])
I am not getting result here while making query in mongodb.
Please have a look , tell me where i am doing wrong in the query.
I am matching key with 15 from both the collections and category with 'Dish' from foreign collection
Though you have solved your problem self.
but query needs to optimize
if you do Lookup first, you will get data of "key": 12 or all other keys.
just to optimize query :
db.collection.aggregate([
{
$match : { key: 15 }
},
{
$lookup: {
from: "other",
localField: "key",
foreignField: "key",
as: "inventory_docs"
}
},
{
$unwind: "$inventory_docs"
},
{
$match: {
"inventory_docs.category": "dish"
}
}
])
db.collection.aggregate([
{
$lookup: {
from: "other",
localField: "key",
foreignField: "key",
as: "inventory_docs"
}
},
{
$unwind: "$inventory_docs"
},
{
$match: {
key: 15,
"inventory_docs.category": "dish"
}
}
])