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
Related
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" } }
])
I'm trying to set up a query on the mongo that searches in 3 different collections.
Document of client:
{
"_id": ObjectId("1a")
"razaosocial:"32423424",
"prepository": [
{
"$ref": "prepository",
"$id": ObjectId("2a")
}
]
}
Document of prepository:
{
"_id": ObjectId("2a")
"name:"Jonh",
"prepository": {
"$ref": "representative",
"$id": ObjectId("3a")
}
}
Document of representative:
{
"_id": ObjectId("3a")
"name:"Josh"
}
I'm doing it this way, but it doesn't return anything:
db.clients.aggregate(
[
{
$lookup: {
from: 'prepository',
localField: 'prepository',
foreignField: 'id',
as: 'prepository'
}
},
{ $unwind: "$prepository" },
{ $lookup: {
from: 'representative',
localField: 'id',
foreignField: 'prepository._id',
as: 'prepository.repre'
}
},
{ $group: {
_id: "$_id",
client: { $first: "$razaosocial" },
users: {
$push: "$prepository"
}
} }
])
I'm trying to return
{
"_id": "1a"
"razaosocial:"32423424",
"prepository": [
{
"_id": "2a"
"name:"Jonh",
"representative": {
"_id": "3a"
"name:"Josh"
}
}
]
}
I am grateful for any help
You can use nested lookup,
$lookup with prepository collection
$match with prepository's id
$lookup with representative collection
$unwind deconstruct representative array
db.client.aggregate([
{
$lookup: {
from: "prepository",
as: "prepository",
let: { prepository: "$prepository" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$prepository"] } } },
{
$lookup: {
from: "representative",
localField: "representative",
foreignField: "_id",
as: "representative"
}
},
{ $unwind: "$representative" }
]
}
}
])
Playground
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"
}
])
I'm trying to filter by date range with match.
This is what I currently have setup:
const initReport = await Report.aggregate(
[
{
"$lookup": {
from: Like.collection.name,
localField: "like",
foreignField: "_id",
as: "like"
}
}
{
"$lookup": {
from: Player.collection.name,
localField: "player",
foreignField: "_id",
as: "player"
}
},
{
"$unwind": {
path: "$player",
preserveNullAndEmptyArrays: true
}
},
{
"$lookup": {
from: Author.collection.name,
localField: "author",
foreignField: "_id",
as: "author"
}
},
{
"$lookup": {
from: Team.collection.name,
localField: "player.team",
foreignField: "_id",
as: "player.team"
}
},
{
"$unwind": {
path: "$player.team",
preserveNullAndEmptyArrays: true
}
},
{
"$lookup": {
from: League.collection.name,
localField: "player.team.league",
foreignField: "_id",
as: "player.team.league"
}
}
{
$match: {
"createdAt": { $lte: '2020-07-23T16:37:29.710Z', $gte: '2017-07-23T16:37:29.710Z' }
}
},
{
$group : {
_id : "$_id",
comment: { "$first": "$comment" },
title: { "$first": "$title" },
summary: { "$first": "$summary" },
analysis: { "$first": "$analysis" },
source_title: { "$first": "$source_title" },
source_link: { "$first": "$source_link" },
author: { "$first": "$author" },
like: { "$first": "$like" },
player: { "$first": "$player" },
createdAt: { "$first": "$createdAt" }
}
}
]
)
This returns an empty array.
I feel like I'm missing obvious, but I'm not sure what it is.
I've even tried to delete all of the other code outside of the aggregation, except for the date with the date, and it still does not work.
In the code provided $lte and $gte are evaluating strings rather than dates. You'll find matches if you compare date objects instead.
$match: {
"createdAt": { $lte: new Date('2020-07-23T16:37:29.710Z'), $gte: new Date('2017-07-23T16:37:29.710Z') }
}
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"
}
}
])