Referencing root _id in aggregate lookup match expression not working - mongodb

This is my first experience using aggregate pipeline. I'm not able to get a "$match" expression to work inside the pipeline. If I remove the "_id" match, I get every document in the collection past the start date, but once I add the $eq expression, it returns empty.
I read a lot of other examples and tried many different ways, and this seems like it is correct. But the result is empty.
Any suggestions?
let now = new Date()
let doc = await Team.aggregate([
{ $match: { created_by: mongoose.Types.ObjectId(req.params.user_oid)} },
{ $sort: { create_date: 1 } },
{ $lookup: {
from: 'events',
let: { "team_oid": "$team_oid" },
pipeline: [
{ $addFields: { "team_oid" : { "$toObjectId": "$team_oid" }}},
{ $match: {
$expr: {
$and: [
{ $gt: [ "$start", now ] },
{ $eq: [ "$_id", "$$team_oid" ] }
]
},
}
},
{
$sort: { start: 1 }
},
{
$limit: 1
}
],
as: 'events',
}},
{
$group: {
_id: "$_id",
team_name: { $first: "$team_name" },
status: { $first: "$status" },
invited: { $first: "$invited" },
uninvited: { $first: "$uninvited" },
events: { $first: "$events.action" },
dates: { $first: "$events.start" } ,
team_oid: { $first: "$events.team_oid" }
}
}])
Example Docs (added by request)
Events:
_id:ObjectId("60350837c57b3a15a414d265")
invitees:null
accepted:null
sequence:7
team_oid:ObjectId("60350837c57b3a15a414d263")
type:"Calendar Invite"
action:"Huddle"
status:"Questions Issued"
title:"Huddle"
body:"This is a Huddle; you should receive new questions 5 days befor..."
creator_oid:ObjectId("5ff9e50a206b1924dccd691e")
start:2021-02-26T07:00:59.999+00:00
end:2021-02-26T07:30:59.999+00:00
__v:0
Team:
_id:ObjectId("60350837c57b3a15a414d263")
weekly_schedule:1
status:"Live"
huddle_number:2
reminders:2
active:true
created_by:ObjectId("5ff9e50a206b1924dccd691e")
team_name:"tESTI"
create_date:2021-02-23T13:50:47.172+00:00
__v:0

This is just a guess since you don't have schema in your question. But it looks like your have some of your _ids mixed up. Where you are currently trying to $match events whose _id is equal to a team_oid. Rather than the event's team_oid field being equal to the current 'team' _id.
I'm pretty confident this will produce the correct output. If you post any schema or sample docs I will edit it.
https://mongoplayground.net/p/5i1w2Ii7KCR
let now = new Date()
let doc = await Team.aggregate([
{ $match: { created_by: mongoose.Types.ObjectId(req.params.user_oid)} },
{ $sort: { create_date: 1 } },
{ $lookup: {
from: 'events',
// Set tea_oid as the current team _id
let: { "team_oid": "$_id" },
pipeline: [
{ $match: {
$expr: {
$and: [
{ $gt: [ "$start", now ] },
// Match events whose 'team_oid' field matches the 'team' _id set above
{ $eq: [ "$team_oid", "$$team_oid" ] }
]
},
}
},
{
$sort: { start: 1 }
},
{
$limit: 1
}
],
as: 'events',
}},
{
$group: {
_id: "$_id",
team_name: { $first: "$team_name" },
status: { $first: "$status" },
invited: { $first: "$invited" },
uninvited: { $first: "$uninvited" },
events: { $first: "$events.action" },
dates: { $first: "$events.start" } ,
team_oid: { $first: "$events.team_oid" }
}
}])

Related

Mongo db transaction query

I'm trying to create a mongodb query to see which invoices are paid or not. I would like to add few things to the outcome like:
virtualAmount : original amount - (sum of all creditnotes)
total paid amount : sum of all transactions where delete is false
paid (true/false) : if virtualAmount - total paid amount is 0
I have created mongo playground:
https://mongoplayground.net/p/0OyK_bOZu9X
Anyone know if this is possible?
How to still have the original object when using group?
Is it also possible to create this result with mongoose?
by $unwind array and $group it.
db.collection.aggregate(
[{
$match: {
_id: '62b46391be7c618aa5c9bf86'
}
}, {
$set: {
'transactions': {
$filter: {
'input': '$transactions',
'as': 'item',
'cond': { $eq: ['$$item.deleted', false] }
}
},
}
}, {
$unwind: {
path: '$transactions'
}
}, {
$group: {
_id: '_id',
'total-paid-amount': { $sum: '$transactions.amount.value'},
//keep to next stage
'creditnote': {$first: '$creditnote'},
'original-amount': {$first: '$amount.value'}
}
}, {
$unwind: {
path: '$creditnote'
}
}, {
$group: {
_id: '_id',
'sum-all-creditnotes': {$sum: '$creditnote.amount.value'},
//keep to next stage
'total-paid-amount': {$first: '$total-paid-amount'},
'original-amount': {$first: '$original-amount'}
}
}, {
$addFields: {
'virtual-amount': {$subtract: ['$original-amount','$sum-all-creditnotes']}
}
}, {
$addFields: {
paid: {$eq: [{$subtract: ['$virtual-amount','$total-paid-mount']},0]}
}
}]
result
{
"_id" : "_id",
"sum-all-creditnotes" : 1000,
"total-paid-amount" : 1000,
"original-amount" : 3370,
"virtual-amount" : 2370,
"paid" : false
}
EDIT
or easy way without group
db.collection.aggregate(
[{
$match: {
_id: '62b46391be7c618aa5c9bf86'
}
}, {
$set: {
'transactions': {
$filter: {
'input': '$transactions',
'as': 'item',
'cond': { $eq: ['$$item.deleted', false] }
}
},
}
}, {
$project:
{
'total-paid-amount': { $sum: '$transactions.amount.value' },
'sum-all-creditnotes': { $sum: '$creditnote.amount.value' },
'original-amount': '$amount.value'
}
}, {
$addFields: {
'virtual-amount': { $subtract: ['$original-amount', '$sum-all-creditnotes'] }
}
}, {
$addFields: {
'paid': { $eq: [{ $subtract: ['$virtual-amount', '$total-paid-mount'] }, 0] }
}
}])

mongoDB lookups upto 4-5 levels with arrays

I am trying to create aggregate code which can join multiple collections and fetches data based on the condition
My collections looks like the following
Users Collection(Example Document)
{
"_id":{"$oid":"6137038058d4632824c84872"},
"first_name":"test",
"last_name":".",
"email":"valid#gmail.com",
"enrolled_courses":[
{
"completed_lessons":[],
"completed":false,
"_id": {"$oid":"6142d1c76d657428903420db"},
"course_id":{"$oid":"614033510a61aa1f3c8b94ff"}
},
{
"completed_lessons":[],
"completed":false,
"_id":{"$oid":"6156d686dcbc58503c7ec679"},
"course_id":{"$oid":"61486e57460b5a422c6e63ca"}
},
{
"completed_lessons":["616d181e62cb265db07d9273","616d182e62cb265db07d9293"],
"completed":false,
"_id":{"$oid":"616d2424149936380c186c2d"},
"course_id":{"$oid":"616d105662cb265db07d91cf"}
}
]
}
Courses Collection(Example Document)
{
"_id":{"$oid":"6135f735b399f03ba03d74ce"},
"type":1,
"enrolled_users":["61374040c27237b1ed87480b"],
"is_deleted":0,
"status":1,
"company_id":{"$oid":"6135f6c9b399f03ba03d74a0"},
"course":"Course 1"
}
Modules Collection(Example Document)
{
"_id":{"$oid":"616d17fd62cb265db07d9240"},
"type":1,
"is_always_available":false,
"is_deleted":0,
"status":1,
"company_id":{"$oid":"6135f6c9b399f03ba03d74a0"},
"course_id":{"$oid":"616d105662cb265db07d91cf"},
"module_name":"module 3",
"due_date":{"$date":"2021-10-18T00:00:00.000Z"}
}
Lessons Collection(Example Document)
{
"_id":{"$oid":"616d184962cb265db07d92cb"},
"lessson_image":null,
"status":1,
"is_deleted": 0,
"module_id":{"$oid":"616d17e462cb265db07d921f"},
"lessson_name":"Lesson 3"
}
I want to fetch details of all the courses user have enrolled and fetch all course's modules and its lessons
My expected output should be like following -
[
{
//CourseDetail 1 Keys,
modules: [
{
//Module Details
lessons: [{
//lessons of this module
}]
},
{
//Module Details
lessons: [{
//lessons of this module
}]
}
]
},
{
//CourseDetail 2 Keys,
modules: [
{
//Module Details
lessons: [{
//lessons of this module
}]
},
{
//Module Details
lessons: [{
//lessons of this module
}]
}
]
}
]
My current query is following, what it currently does is it groups the modules and its lessons together and bind it to single course object. The fetching of modules and lessons is perfect as per my requirement but course array contains only 1 single object and it holds list of all the modules from other courses also.
Current Query Code
let courseDetails = await users.aggregate([
{
$match: { _id: mongoose.Types.ObjectId(user._id), }
},
{$unwind:"$enrolled_courses"},
{
$lookup: {
from: 'courses',
localField: "enrolled_courses.course_id",
foreignField: "_id",
as: "courseDetails"
}
},
{ $unwind: { path: "$courseDetails", preserveNullAndEmptyArrays: true}},
{
$lookup: {
from: "modules",
let: { currentCourseId: "$courseDetails._id"},
pipeline: [{
$match: {
$expr: {
$and: [
{ $eq: ["$course_id", "$$currentCourseId"]},
{ $eq: ["$status", 1]},
{ $eq: ["$is_deleted", 0]},
{
$or: [
{ $eq: ["$is_always_available", true] },
{ $gte: ["$due_date", convertedDate]}
]
}
]
}
}
},
{
$project: {
_id: 1,
module_name: 1,
company_id: 1,
course_id: 1
}
}],
as: "moduleList"
}
},
{ $unwind: { path: "$moduleList", preserveNullAndEmptyArrays: true }},
{
$lookup: {
from: "lessons",
let: { curentModuleId: "$moduleList._id"},
pipeline: [{
$match: {
$expr: {
$and: [
{ $eq: ["$status", 1] },
{ $eq: ["$is_deleted", 0]},
{ $eq: ["$module_id", "$$curentModuleId"]}
]
}
}
}, {
$project: {
_id: 1,
lessson_name: 1,
module_id: 1
}
}],
as: "moduleList.lessons"
}
},
{
$group: {
_id: "$_id",
course_id: { $first: "$enrolled_courses.course_id"},
course_name: { $first: "$courseDetails.course"},
completed_lessons: { $first: "$enrolled_courses.completed_lessons"},
current_lessons: { $first: "$enrolled_courses.current_lessons"},
completed: { $first: "$enrolled_courses.completed"},
enrolled_users_count: {
$first: "$courseDetails.enrolled_users"
},
modules: {
$push: "$moduleList"
}
}
},
{
$project: {
course_id: 1,
course_name: 1,
completed_lessons: 1,
current_lessons: 1,
completed: 1,
enrolled_users_count: {
$size: "$enrolled_users_count"
},
modules: 1
}
},
{
$group: {
_id: "$_id",
course_id: { $first: "$course_id"},
course_name: { $first: "$course_name"},
completed_lessons: { $first: "$completed_lessons"},
current_lessons: { $first: "$current_lessons"},
completed: { $first: "$completed"},
enrolled_users_count: { $first: "$enrolled_users_count"},
modules: { $first: "$modules"}
}
}
]);

Mongodb How To Aggregate But Only Include One Result Per Date Field?

I'm trying to aggregate results but I need to include only 1 result per date/day. Here's how my query currently looks:
.aggregate([
{
$lookup: {
from: 'community_stats',
as: 'stats',
let: { id: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$community', '$$id'] },
},
},
{ $sort: { date: -1 } },
{ $limit: 5 },
],
},
},
])
Which returns an array like this:
stats: [
{
_id: new ObjectId("613d42f20e8023815b8f0ad3"),
community: new ObjectId("6138ef0a00895c22c6cd3b68"),
data: [Object],
date: 2021-09-11T23:59:46.998Z
},
{
_id: new ObjectId("613b784e564ee6ad3e2dbb14"),
community: new ObjectId("6138ef0a00895c22c6cd3b68"),
data: [Object],
date: 2021-09-10T15:22:54.764Z
},
{
_id: new ObjectId("6139f9487e4c964ef9dafd11"),
community: new ObjectId("6138ef0a00895c22c6cd3b68"),
data: [Object],
date: 2021-09-09T12:08:40.198Z
},
{
_id: new ObjectId("6139f6eac570e66aa60a8b5f"),
community: new ObjectId("6138ef0a00895c22c6cd3b68"),
data: [Object],
date: 2021-09-09T11:58:34.463Z
}
]
Does anybody know how this can be done?
You just want to add a $group stage where you group the matches by date, like so:
db.collection.aggregate([
{
$lookup: {
from: "community_stats",
as: "stats",
let: {
id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$community",
"$$id"
]
}
}
},
{
$group: {
_id: {
year: {
"$year": "$date"
},
month: {
"$month": "$date"
},
day: {
"$dayOfMonth": "$date"
},
},
first: {
$first: "$$ROOT"
}
}
},
{
"$replaceRoot": {
"newRoot": "$first"
}
},
{
$sort: {
date: -1
}
},
{
$limit: 5
}
]
}
}
])
Mongo Playground
In my example is used $first to select the first document from each day, you can however use different operators / logic if it needs to be changed.

$match in lookup stage without removing local documents in mongoDB

Right now I have a lookup stage that matches a collection of answers with their respective questions. The problem I'm having is that on the $match if the question does not having a matching answer document in the lookup collection it is removed from the results of the aggregation. How can I avoid this?
{
$lookup: {
from: 'groupansphotos',
let: { question_id: '$questions._id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$_id', '$$question_id'] },
},
},
{ $unwind: '$answers' },
{ $match: { 'answers.reported': { $lt: 1 } } },
{ $sort: { 'answers.helpful': -1 } },
{ $limit: 2 },
{
$project: {
_id: 0,
k: { $toString: '$answers._id' }, // k
v: '$$ROOT.answers', // v
},
},
],
as: 'answers',
},
},

$match in aggregate don't return data in mongodb

I have three tables below is the structure like below
I'm looking to get a result like below
"type1": [ -- type from Accounts collection
{
"_id": "5e97e9a224f62f93d5x3zz46", -- _id from Accounts collection
"locs": "sampleLocks 1", -- field from Accounts collection
"solutions": "sample solutions 1", -- field from Accounts collection
"Clause": "clause 1" -- field from AccountsDesc collection
},
{
"_id": "5e97e9a884f62f93d5x3zz46",
"locs": "sampleLocks2",
"solutions": "sample solutions2",
"Clause": "clause2"
}
],
"type2": [
// same data construction as of type1 above
]
_id, locks, solution to be coming from Accounts collection
Clause field to be coming from AccountsDesc collection
accounts_id is kind of a foreign key in AccountsDesc coming from Account
competitor_id is kind of a foreign key in AccountsDesc coming from Competitor
Below is what my query looks like
db.accountDesc.aggregate([
{
$match : {accounts_Id : "123456"}, active: true}
},
{
$lookup: {
from: 'accounts',
pipeline: [{ $match: { type: { $in: ["type1, type2, type3"] } } }],
as: 'accountsData'
}
},
{
$group: {
_id: "$accountsData.type",
data: {
$push: {_id: "$accountsData._id", clause: "$clause", locs: "$type.locs", solutions: "$type.solutions"}
}
}
},
{
$group: {
_id: null,
data: {
$push: {
k: {
$toString: '$_id'
},
v: '$data'
}
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: '$data'
}
}
}
])
Issues related with the query -
$match : {accountId : "123456"}, active: true} -- No data is returned if i use match on AccountsDesc collection
cant set localField, foriegnField if im using pipeline, then how the mapping will happen like a LEFT join.
clause: "$clause" don't get the value of this field in the response
As we discussed in chat, you want RIGHT OUTER JOIN for your aggregation.
Try the query below:
db.User_Promo_Map.aggregate([
{
$match: {
user_Id: ObjectId("5e8c1180d59de1704ce68112")
}
},
{
$lookup: {
from: "promo",
pipeline: [
{
$match: {
active: true,
platform: {
$in: [
"twitch",
"youtube",
"facebook"
]
}
}
}
],
as: "accountsData"
}
},
{
$unwind: "$accountsData"
},
{
$group: {
_id: "$accountsData.platform",
data2: {
$addToSet: {
amount: "$amount",
promo_Id: "$promo_Id"
}
},
data: {
$addToSet: {
_id: "$accountsData._id",
format: "$accountsData.format",
description: "$accountsData.description"
}
}
}
},
{
$addFields: {
data: {
$map: {
input: "$data",
as: "data",
in: {
"_id": "$$data._id",
"description": "$$data.description",
"format": "$$data.format",
amount: {
$reduce: {
input: "$data2",
initialValue: "$$REMOVE",
in: {
$cond: [
{
$eq: [
"$$this.promo_Id",
"$$data._id"
]
},
"$$this.amount",
"$$value"
]
}
}
}
}
}
}
}
},
{
$group: {
_id: null,
data: {
$push: {
k: {
$toString: "$_id"
},
v: "$data"
}
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$data"
}
}
}
])
MongoPlayground