pass mongo dollar sign aggregation var to date object in javascript - javascript

im trying to perform this aggregation in a mongo $lookup operation where i want to pass a variable down to a date object.
Wondering if this is possible
deal_cliff: new Date(new Date("$integrated").getTime() + 540 * 86400000),
as you can tell the problem is that "$integrated" is being used as a string and not an actual date since its a mongo dollar sign var.
any kind of help would be appreciated.
full code:
$lookup: {
from: 'deals',
// set variables that can be used in the pipeline below
let: {
group_id: '$parentGroupId',
internal_comm: { $toDecimal: '$internalCommission.amount' },
dealer_id: dealerId,
deal_cliff: new Date(new Date('$integrated').getTime() + 540 * 86400000), <---- Problem here
booking_provider: '$provider',
booking_booked_on: '$bookedOn'
},
pipeline: [
{
$match: {
$expr: {
$eq: ['$groupId', '$$group_id']
}
}
},
{ $unwind: '$dealers' },
...(userType === USER_TYPES.BDM && dealerId
? [
{
$match: {
$expr: {
$eq: ['$dealers.dealerId', '$$dealer_id']
}
}
}
]
: []),
{
$addFields: {
dealerComm: {
$cond: [
// If booking provider not Trivago & booking <= cliff date
{
$and: [
{ $ne: ['$$booking_provider', "Trivago"] },
{ $lte: ['$$booking_booked_on', "$$deal_cliff"] }
]
},
// do
{
$multiply: [
'$dealers.effort',
'$$internal_comm',
'$dealers.repCommission'
]
},
// else return
0
]
}
}
},
{
$group: {
_id: '$_id',
totalDealerComm: {
$sum: {
$cond: {
if: '$dealerComm',
then: { $toDecimal: '$dealerComm' },
else: 0
}
}
}
}
}
],
as: 'deal'
}

Related

MongoDB - Generating dynamic $or using pipeline variable?

hoping someone can help as I am truly stuck!
I have this query
SwapModel.aggregate([
{
$match: {
organisationId: mongoose.Types.ObjectId(organisationId),
matchId: null,
matchStatus: 0,
offers: {
$elemMatch: {
from: { $lte: new Date(from) },
to: { $gte: new Date(to) },
locations: { $elemMatch: { $eq: location } },
types: { $elemMatch: { $eq: type } },
},
},
//problem is HERE
$or: {
$map: {
input: "$offers",
as: "offer",
in: {
from: { $gte: new Date("$$offer.from") },
to: { $lte: new Date("$$offer.to") },
location: { $in: "$$offer.locations" },
type: { $in: "$$offer.types" },
},
},
},
},
},
{ ...swapUserLookup },
{ $unwind: "$matchedUser" },
{ $sort: { from: 1, to: 1 } },
]);
I'm trying to use the results of the $match document to generate an array for $or. My data looks like this:
[{
_id: ObjectId("id1"),
from: ISODate("2023-01-21T06:30:00.000Z"),
to: ISODate("2023-01-21T18:30:00.000Z"),
matchStatus: 0,
matchId: null,
userId: ObjectId("ddbb8f3c59cf13467cbd6a532"),
organisationId: ObjectId("246afaf417be1cfdcf55792be"),
location: "Chertsey",
type: "DCA",
offers: [{
from: ISODate("2023-01-23T05:00:00.000Z"),
to: ISODate("2023-01-24T07:00:00.000Z"),
locations: ["Chertsey", "Walton"],
types: ["DCA", "SRV"],
}]
}, {
_id: ObjectId("id2"),
from: ISODate("2023-01-23T06:30:00.000Z"),
to: ISODate("2023-01-23T18:30:00.000Z"),
matchStatus: 0,
matchId: null,
userId: ObjectId("d6f10351dd8cf3462e3867f56"),
organisationId: ObjectId("246afaf417be1cfdcf55792be"),
location: "Chertsey",
type: "DCA",
offers: [{
from: ISODate("2023-01-21T05:00:00.000Z"),
to: ISODate("2023-01-21T07:00:00.000Z"),
locations: ["Chertsey", "Walton"],
types: ["DCA", "SRV"],
}]
}]
I want the $or to match all documents that have the corresponding from/to/location/type as the current document - the idea is two shifts that could be swapped
If the offers are known (passed as an array to the function calling aggregate), I can do this with:
$or: offers.map((x) => ({
from: { $gte: new Date(x.from) },
to: { $lte: new Date(x.to) },
location: { $in: x.locations },
type: { $in: x.types },
}))
BUT I want to be able to do this in an aggregation pipeline when the offers will only be known from the current document, $offers
Is this possible? I've tried $in, $map, $lookup, $filter, $getField but can't get it right and can't get anything from Google as it thinks I want $in (which is the opposite of what I need).
I'm pretty new to MongoDB and am probably approaching this completely wrong but I'd really appreciate any help!
Edit: expected output is simply an array of matching documents, so passing document id1 to the function would return an array with id2 in, because each document is compatible with the other
///expected output, from and to are between an offer in id1's from and to, similarly types/locations are compatible
{
_id: ObjectId("id2"),
from: ISODate("2023-01-23T06:30:00.000Z"),
to: ISODate("2023-01-23T18:30:00.000Z"),
matchStatus: 0,
matchId: null,
userId: ObjectId("d6f10351dd8cf3462e3867f56"),
organisationId: ObjectId("246afaf417be1cfdcf55792be"),
location: "Chertsey",
type: "DCA",
offers: [{
from: ISODate("2023-01-21T05:00:00.000Z"),
to: ISODate("2023-01-21T07:00:00.000Z"),
locations: ["Chertsey", "Walton"],
types: ["DCA", "SRV"],
}]
You can perform self-lookup with your criteria set in the sub-pipeline.
db.collection.aggregate([
{
$match: {
organisationId: "organisationId1",
matchId: null,
matchStatus: 0
}
},
{
$unwind: "$offers"
},
{
"$lookup": {
"from": "collection",
"let": {
offersFrom: "$offers.from",
offersTo: "$offers.to",
offersLocation: "$offers.locations",
offersType: "$offers.types"
},
"pipeline": [
{
$match: {
$expr: {
$and: [
{
$gte: [
"$from",
"$$offersFrom"
]
},
{
$lte: [
"$to",
"$$offersTo"
]
},
{
"$in": [
"$location",
"$$offersLocation"
]
},
{
"$in": [
"$type",
"$$offersType"
]
},
]
}
}
}
],
"as": "selfLookup"
}
},
{
"$unwind": "$selfLookup"
},
{
"$replaceRoot": {
"newRoot": "$selfLookup"
}
}
])
Mongo Playground

MongoDB - How to combine findOne (in array) with aggregate

I currently have a Mongo query that looks like this:
const user = await User.findOne({ userId }).lean() || []
const contributions = await Launch.aggregate([
{ $sort: { addedAt: -1 } },
{ $limit: 10 },
{
$match: {
_id: { $in: user.contributions }
}
},
{
$addFields: {
activity: 'contribution',
launchName: '$name',
launchId: '$_id',
date: '$addedAt',
content: '$description'
}
}
])
But instead of having two different Mongo queries (findOne and aggregate), how can I combine them into one query?
I tried this but it just errors out immediately in the lookup part:
const contributions = await Launch.aggregate([
{ $sort: { addedAt: -1 } },
{ $limit: 10 },
{
$lookup: {
from: 'user',
let: { id: $user.contributions },
pipeline: [
{ $match: { $expr: { $in: [$_id, $$user.contributions] } } }
],
localField: '_id',
foreignField: 'userId',
as: 'user'
}
},
{
$addFields: {
activity: 'contribution',
launchName: '$name',
launchId: '$_id',
date: '$addedAt',
content: '$description'
}
}
])
I've never used the pipeline option so a little confused onn how to fix this problem?
Enclose these $user.contributions, $_id with quotes in order to make the query valid.
Since you declare the id variable with the value of user.contributions. You should use the variable with $$id instead of $$user.contributions.
I don't think the localField and foreignField are needed as you are mapping/joining with pipeline.
Your aggregation query should be looked as below:
const contributions = await Launch.aggregate([
{ $sort: { addedAt: -1 } },
{ $limit: 10 },
{
$lookup: {
from: 'user',
let: { id: "$user.contributions" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$id"] } } }
],
as: 'user'
}
},
{
$addFields: {
activity: 'contribution',
launchName: '$name',
launchId: '$_id',
date: '$addedAt',
content: '$description'
}
}
])

average per day mongo in lookup

I have a collection (sensordatas) with data every minute. When I use this code I get all the data from de collections from sensordatas. This data is way too much. I like to have the average data per day, otherwise it will send to much data to the client. My code is
Sensor.aggregate([
{
$match: { _id: ObjectId(req.params.id) },
},
{
$lookup: {
from: "sensordatas",
as: "data",
let: { device_id: "$device_id", datum: "$datum" },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: ["$device_id", "$$device_id"],
},
{ $gte: ["$datum", moment(startdate).toDate(),] },
{ $lte: ["$datum", moment(enddate).toDate(),] },
],
},
},
},
{ $sort: { datum: -1 } },
],
},
},
How can I print not every item but have a average per day. I hope you can help me

$sum number of objects on $lookup fields [duplicate]

This question already has an answer here:
How to find length of the array mongodb
(1 answer)
Closed 3 years ago.
I am trying to use the $sum in MongoDB to summarize the number of objects in an array. However it only returns 0, even when there are more objects. What am I doing wrong?
{
$lookup: {
from: "events",
let: { user: "$_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{$eq: ['$creator', '$$user']},
],
},
}
},
{
$match: {
'createdAt': {
$gte: moment(a).startOf('day').toDate(),
$lte: moment(b).startOf('day').toDate(),
}
}
},
{ $project: { _id: 0, leads: 0, bookings: 0, sales: 0 } },
],
as: "events"
}
},
{
$addFields: {
countEvents: {$sum: "$events"},
}
}
I think you need to use $size instead of $sum

Compare Dates from arrays of different objects in aggregation

on my project i have users that complete combinations (called sessions) of courses. the fact of playing a course is called an attempt. During the attempt they can close it and come back later (so we keep a timelog object).
I have a request from the client which needs to return for each session, the users (and their attempts) that have played whole or part of their session during a certain timeframe.
During a certain timeframe means that the client sends a begin and end date and we count a user for a specific session if:
- the first attempt has begun before the end of the timeframe => the started of the first timelog of the first < ending date
- the last attempt has been finished after the begining of the timeframe => the end of the last timelog of the last attempt > starting date
Here is an example of an attempt object (the only one we need to use here):
{
"_id" : ObjectId("5b9148650ab5f43b5e829a4b"),
"index" : 0,
"author" : ObjectId("5acde2646055980a84914b6b"),
"timelog" : [
{
"started" : ISODate("2018-09-06T15:31:49.163Z"),
"ended" : ISODate("2018-09-06T15:32:03.935Z")
},
...
],
"session" : ObjectId("5b911d31e58dc13ab7586f9b")}
My idea was to make an aggregate on the attempts, to group those using author and session as an _id for the $group stage, and to push all the attempts of the user for this particular session into an array userAttempts.
Then to make an $addField stage to retrieve the started field of the first timelog of the first attempt and the last ended of the last attempt.
And finally to $filter or $match using those new fields.
Here is my aggregate:
const newDate = new Date()
_db.attempts.aggregate([
{ $match: {
author: { $in: programSessionsData.users },
$or: [{ programSession: { $in: programSessionIds } }, { oldTryFor: { $in: programSessionIds } }],
globalTime: $ex,
timelog: $ex }
},
{
$group: {
_id: {
user: "$author",
programSession: "$programSession"
},
userAttempts: { $push: { attemptId: "$_id", lastTimelog: { $arrayElemAt: ["$timelog", -1] }, timelog: "$timelog" } }
}
},
{
$addFields: { begin: { $reduce: {
input: "$userAttempts",
initialValue: newDate,
in: {
$cond: {
if: { $lt: ["$$this.timelog.0.started", "$$value"] },
then: "$$this.timelog.0.started",
else: "$$value"
} }
} } }
}
I also tried this for the addFields stage:
{
$addFields: { begin: { $reduce: {
input: "$userAttempts",
initialValue: newDate,
in: { $min: ["$$this.timelog.0.started", "$$value] }
} } }
}
However everytime begin is an empty array.
I do not really know how i can extract those two date, or compare dates between them.
To Note: the end one is more difficult that is why i have to first extract lastTimelog. If you an other method i would gladly take it.
Also this code is on a node server so i cannot use ISODate. and the mongo version used is 3.6.3.
After playing with aggregate a bit i came up with 2 solutions:
Solution 1
_db.attempts.aggregate([
{ $match: {
query
},
{
$group: {
_id: {
user: "$author",
programSession: "$programSession"
},
userAttempts: { $push: { attemptId: "$_id", timelog: "$timelog" } }
}
}, {
$addFields: {
begin: { $reduce: {
input: "$userAttempts",
initialValue: newDate,
in: { $min: [{ $reduce: {
input: "$$this.timelog",
initialValue: newDate,
in: { $min: ["$$this.started", "$$value"] }
} }, "$$value"] }
} },
end: { $reduce: {
input: "$userAttempts",
initialValue: oldDate,
in: { $max: [{ $reduce: {
input: "$$this.timelog",
initialValue: oldDate,
in: { $max: ["$$this.ended", "$$value"] }
} }, "$$value"] }
} }
}
},
{
$match: {
begin: { $lt: req.body.ended },
end: { $gt: req.body.started }
}
}
], { allowDiskUse: true });
newDate is today and oldDate is an arbitrary date in the past.
I had to chain 2 reduce because "$$this.timelog.0.started" would always return nothing. Don't really know why though.
Solution 2
_db.attempts.aggregate([
{ $match: {
query
},
{
$addFields: {
firstTimelog: { $arrayElemAt: ["$timelog", 0] },
lastTimelog: { $arrayElemAt: ["$timelog", -1] }
}
},
{
$group: {
_id: {
user: "$author",
programSession: "$programSession"
},
begin: { $min: "$firstTimelog.started" },
end: { $max: "$lastTimelog.ended" },
userAttempts: { $push: { attemptId: "$_id", timelog: "$timelog"} }
}
},
{
$match: {
begin: { $lt: req.body.ended },
end: { $gt: req.body.started }
}
}
], { allowDiskUse: true });
This one is a lot more straight forward and seems simpler, but oddly enough, from my testing, Solution 1 is always quicker at least in the object distribution for my project.

Categories