how to transform data with 3 collection and $looup - mongodb

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

Related

Update Mongo collection fields with values from other collection

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

mongodb $lookup - suppress non-matching documents

I only want to see matching documents, i.e. only T3 in the example below. I can find the matching documents between lotterytickets (many documents) and lotterydrawing (only a few documents).
How can I filter out the non-matching documents? Basically, I'd not like to see documents with the condition drawnticket==[], but I haven't found the conditional code to apply.
Any help would be appreciated. Thank you in advance
Configuration:
db={
"lotteryticket": [
{
"_id": ObjectId("6021ce0cb4d2c2b4f24c3a2e"),
"ticket": "T1",
"player": "Alice"
},
{
"_id": ObjectId("6021ce0cb4d2c2b4f24c3a2f"),
"ticket": "T2",
"player": "Bob"
},
{
"_id": ObjectId("6021ce0cb4d2c2b4f24c3a33"),
"ticket": "T3",
"player": "Charles"
}
],
"lotterydrawing": [
{
"_id": ObjectId("63309480b749b733c087b758"),
"ticket": "T3"
},
{
"_id": ObjectId("63309480b749b733c087b759"),
"ticket": "T9"
},
{
"_id": ObjectId("63309480b749b733c087b75a"),
"ticket": "T77"
}
]
}
Query:
db.lotteryticket.aggregate([
{
$lookup: {
from: "lotterydrawing",
localField: "ticket",
foreignField: "ticket",
as: "drawnticket",
}
}
])
Result:
[
{
"_id": ObjectId("6021ce0cb4d2c2b4f24c3a2e"),
"drawnticket": [],
"player": "Alice",
"ticket": "T1"
},
{
"_id": ObjectId("6021ce0cb4d2c2b4f24c3a2f"),
"drawnticket": [],
"player": "Bob",
"ticket": "T2"
},
{
"_id": ObjectId("6021ce0cb4d2c2b4f24c3a33"),
"drawnticket": [
{
"_id": ObjectId("63309480b749b733c087b758"),
"ticket": "T3"
}
],
"player": "Charles",
"ticket": "T3"
}
]
https://mongoplayground.net/p/bYcLEzrF5QT
Add a match stage, to filter stages with the empty drawn tickets. Like this:
db.lotteryticket.aggregate([
{
$lookup: {
from: "lotterydrawing",
localField: "ticket",
foreignField: "ticket",
as: "drawnticket",
}
},
{
"$match": {
$expr: {
"$gt": [
{
$size: "$drawnticket"
},
0
]
}
}
}
])
Playground.
try this query
db.lotteryticket.aggregate([
{
$lookup: {
from: "lotterydrawing",
localField: "ticket",
foreignField: "ticket",
as: "drawnticket"
}
},
{
"$match": {
drawnticket: {
$exists: true,
$ne: []
}
}
}
])
Playground.

MongoDB - How to aggregate with deeply nested arrays

I have the following MongoDB structure:
Division Collection:
{
"_id": ObjectId("5b28cab902f28e18b863bd36"),
"name": "Premier League",
...
"teams": [
ObjectId("5b28cab902f28e18b863bd01"),
ObjectId("5b28cab902f28e18b863bd02"),
ObjectId("5b28cab902f28e18b863bd03"),
...
]
...
},
...
Teams Collection:
{
"_id": ObjectId("5b28cab902f28e18b863bd01"),
"name": "Liverpool",
...
"players": [
ObjectId('5b23tmb902f28e18b863bd01'),
ObjectId('5b23tmb902f28e18b863bd02'),
ObjectId('5b23tmb902f28e18b863bd03'),
...
]
...
},
...
Players Collection:
{
"_id": ObjectId("5b2b9a8bbda339352cc39ec1"),
"name": "Mohamed Salah",
"nationality": [
ObjectId("5b23cn1902f28e18b863bd01"),
ObjectId("5b23cn2902f28e18b863bd02"),
],
...
},
...
Countries Collection:
{
"_id": ObjectId("5b23cn1902f28e18b863bd01"),
"name": "England",
...
},
{
"_id": ObjectId("5b23cn2902f28e18b863bd02"),
"name": "Egypt",
...
},
...
How to get a result, which is below, using MongoDB aggregation ($lookup, $pipeline, etc):
{
"divisions": [
{
"_id": ObjectId("5b28cab902f28e18b863bd36"),
"name": "Premier League",
...
"teams": [
{
"_id": ObjectId("5b28cab902f28e18b863bd01"),
"name": "Liverpool",
...
"players": [
{
"_id": ObjectId("5b23tmb902f28e18b863bd01"),
"name": "Mohamed Salah",
"nationality": [
{
"_id": ObjectId("5b23cn2902f28e18b863bd02"),
"name": "Egypt",
...
},
{
"_id": ObjectId("5b23cn1902f28e18b863bd01"),
"name": "England",
...
}
]
...
},
...
]
},
...
]
},
{
"_id": ObjectId("5b28cab902f28e18b863bd37"),
"name": "Championship",
...
},
...
]
}
I manage to make a first-level merge:
db.divisions.aggregate([
{
$lookup: {
from: 'teams',
localField: 'teams',
foreignField: '_id',
as: 'teams'
}
},
])
and then I ran into difficulties, so I would be very grateful if someone could help me with this issue.
You need multi-level nested $lookup with pipeline.
db.division.aggregate([
{
$lookup: {
from: "teams",
let: {
teams: "$teams"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$_id",
"$$teams"
]
}
}
},
{
$lookup: {
from: "players",
let: {
players: "$players"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$_id",
"$$players"
]
}
}
},
{
$lookup: {
from: "countries",
localField: "nationality",
foreignField: "_id",
as: "nationality"
}
}
],
as: "players"
}
}
],
as: "teams"
}
}
])
Sample Mongo Playground
Maybe someone will be useful. Data also can be merged using the populate method:
db.divisions.find(_id: division_id).populate(
{
path: 'teams',
populate: {
path: 'players',
populate: {
path: 'nationality'
}
}
}
)

How to aggregate nested lookup array in mongoose?

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.

how to push data into array mongodb

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