Mongodb aggregation $or condition on $eq - mongodb

I want to increment the value of the field 'XR' on either getting the value of modality equals to XR or CR or DX. But unfortunately its somehow not working. I read somewhere that the $eq can take regex. So my question is, Is there any why by which i can make an or comparison within $cond and $eq.
{
$group: {
_id: { week: { $week: '$createdAt' }, year: { $year: '$createdAt' } },
XR: { $sum: { $cond: [{ $eq: ['$modality', /(XR|CR|DX)/g] }, 1, 0] } },
CT: { $sum: { $cond: [{ $eq: ['$modality', 'CT'] }, 1, 0] } },
MR: { $sum: { $cond: [{ $eq: ['$modality', 'MR'] }, 1, 0] } },
MG: { $sum: { $cond: [{ $eq: ['$modality', /(NM|MM|MG)/g] }, 1, 0] } },
},
},

Related

MongoDB - Query calculation and group multiple items

Let's say I have this data:
{"Plane":"5546","Time":"55.0", City:"LA"}
{"Plane":"5548","Time":"25.0", City:"CA"}
{"Plane":"5546","Time":"6.0", City:"LA"}
{"Plane":"5548","Time":"5.0", City:"CA"}
{"Plane":"5555","Time":"15.0", City:"XA"}
{"Plane":"5555","Time":"8.0", City:"XA"}
and more but I just visualize the data
I want to calculate and group all the time and plane, this is expected output:
{"_id:":["5546","LA"],"Sum":2,"LateRate":1,"Prob"0.5}
The sum is sum all the time, Late is sum all the time with time > "15" and Prob is Late/Sum
The code I have tried but it still is missing something:
db.Collection.aggregate([
{
$project: {
Sum: 1,
Late: {
$cond: [{ $gt: ["$Time", 15.0] }, 1, 0]
},
prob:1
}
},
{
$group:{
_id:{Plane:"$Plane", City:"$City"},
Sum: {$sum:1},
Late: {$sum: "$Late"}
}
},
{
$addFields: {
prob: {
"$divide": [
"$Late",
"$Sum"
]
}
}
},
])
db.collection.aggregate([
{
$project: {
Time: 1,
Late: {
$cond: [
{
$gt: [
{
$toDouble: "$Time"
},
15.0
]
},
"$Time",
0
]
},
prob: 1,
Plane: 1,
City: 1
}
},
{
$group: {
_id: {
Plane: "$Plane",
City: "$City"
},
Sum: {
$sum: {
"$toDouble": "$Time"
}
},
Late: {
$sum: {
$toDouble: "$Late"
}
}
}
},
{
$addFields: {
prob: {
"$divide": [
"$Late",
"$Sum"
]
}
}
}
])
Project limits the fields passed to the next stage
On string, you cannot perform all relational/arithmetic operations
Playground

Mongodb aggregation, get expected result on groupBy without hard-coding categories

My objective is to write an efficient query, that with the given input, gives me the expected output. I have some working solution, but all "types" are "manually" written, so I guess I'm looking for help to get the same output but in a different way.
input
reportId
type
weight
A
"fish"
4
A
"fish"
2
A
"cow"
0
B
"fish"
2
B
"tuna"
1
B
"bird"
Expected output
[
{
reportId: "A",
totalCount: 3,
totalWeight: 6,
fishCount: 2,
tunaCount: 0,
cowCount: 1,
birdCount: 0
},
{
reportId: "A",
totalCount: 3,
totalWeight: 2,
fishCount: 1,
tunaCount: 1,
cowCount: 0,
birdCount: 1
},
]
Partial "hard-coded" solution
What I have been doing so far is to create 2 group-by steps: It kind of get's the job done, but in my real use-case there are a lot of types, and therefore the group-stages are very long.
[
{
$group: {
_id: { reportId: "$reportId", type: $type },
count: { $sum: 1 },
totalWeight: { $sum: "$weight" }
}
},
{
$group: {
_id: "$_id.reportId",
totalCount: { $sum: "$totalCount" },
totalWeight: { $sum: "$totalWeight" },
fishCount: {
$sum: {
$cond: {
"if": { $eq: ["$_id.type", "fish"] },
then: "$count",
else: 0
}
}
},
tunaCount: {
$sum: {
$cond: {
"if": { $eq: ["$_id.type", "tuna"] },
then: "$count",
else: 0
}
}
},
// <== And here I have a count blog for each type. Can I get the same result in a better way?
}
}
]
I will focus to the second part, which is the difficult one. I don't know whether there is a shorter and better solution, but this one should work:
db.collection.aggregate([
{
$unset: "_id"
},
{
$set: {
data: {
"$objectToArray": "$$ROOT"
}
}
},
{
$group: {
_id: "$reportId",
data: {
$push: "$data"
}
}
},
{
$set: {
data: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
},
{
$set: {
data: {
$filter: {
input: "$data",
cond: {
$not: {
$in: [
"$$this.k",
[
"totalCount",
"totalWeight"
]
]
}
}
}
}
}
},
{
$unwind: "$data"
},
{
$group: {
_id: "$_id",
data: {
$push: "$data"
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$data"
}
}
}
])
See Mongo playground

How to limit the result of the pipeline according to a date, week and month range?

I have a pipeline that gives me the result according to the players belonging to a certain company in a daily, weekly, and monthly manner. I have a date filter in the dashboard which gives an iso date range. I want to limit or range my results according to the date filter? is there any way to do it in the pipeline?
if (chartType === 'Daily') {
statsPipelineCondition = {
_id: { day: { $dayOfMonth: '$createdAt' }, month: { $month: '$createdAt' }, year: { $year: '$createdAt' } },
};
sortCondition = { '_id.year': 1, '_id.month': 1, '_id.day': 1 };
} else if (chartType === 'Monthly') {
statsPipelineCondition = {
_id: { month: { $month: '$createdAt' }, year: { $year: '$createdAt' } },
};
sortCondition = { '_id.year': 1, '_id.month': 1 };
} else {
statsPipelineCondition = {
_id: { week: { $week: '$createdAt' }, year: { $year: '$createdAt' } },
};
sortCondition = { '_id.year': 1, '_id.week': 1 };
}
const statsPipeline = [
{
$group: {
...statsPipelineCondition,
TOTAL: { $sum: 1 },
XR: { $sum: { $cond: [{ $in: ['$company', ['XR', 'CR', 'DX']] }, 1, 0] } },
CT: { $sum: { $cond: [{ $eq: ['$company', 'CT'] }, 1, 0] } },
MR: { $sum: { $cond: [{ $eq: ['$company', 'MR'] }, 1, 0] } },
MG: { $sum: { $cond: [{ $in: ['$company', ['NM', 'MM', 'MG']] }, 1, 0] } },
},
},
{
$sort: {
...sortCondition,
},
},
];
Date filter:
datefilter - { '$gte': '2020-09-01T04:49:50.899Z',
'$lte': '2020-11-03T04:49:50.899Z' }
You need to add a $match stage prior to the $group stage to filter our based on the range:
let datefilter = {
'$gte': new Date('2020-09-01T04:49:50.899Z'),
'$lte': new Date('2020-11-03T04:49:50.899Z')
};
const statsPipeline = [
{
$match: {
createdAt: datefilter
}
},
{
$group: {
...statsPipelineCondition,
TOTAL: { $sum: 1 },
XR: { $sum: { $cond: [{ $in: ['$company', ['XR', 'CR', 'DX']] }, 1, 0] } },
CT: { $sum: { $cond: [{ $eq: ['$company', 'CT'] }, 1, 0] } },
MR: { $sum: { $cond: [{ $eq: ['$company', 'MR'] }, 1, 0] } },
MG: { $sum: { $cond: [{ $in: ['$company', ['NM', 'MM', 'MG']] }, 1, 0] } },
},
},
{
$sort: {
...sortCondition,
},
},
];
Okay, So for some reason. MongoDB doesn't like dates in strings. It would be much better if we just convert the strings by an operator provided by the aggregation framework and this will make the things work.
{
$match: {
$expr: {
$and: [
{
$gte: [
'$createdAt',
{
$dateFromString: {
dateString: dateFilter.$gte,
},
},
],
},
{
$lte: [
'$createdAt',
{
$dateFromString: {
dateString: dateFilter.$lte,
},
},
],
},
],
},
},
},

Mongodb How to match one of two field do not equal zero?

I need to match one of two fields that must not be equal to zero. How to implement it?
I try these solutions but no luck:
Solution 1:
Model.aggregate[
{
$project: {
accountID: "$_id.accountID",
locationID: "$_id.locationID",
time: "$_id.time",
value: "$value",
actualValue: "$actualValue",
total: { $add: ["$value", "$actualValue"] },
},
},
{
$match: {
total: { $ne: 0 },
},
},
]
With this solution, it will wrong when a negative plus with the opposite version. Example -1500 + 1500 will become zero.
Solution 2
Model.aggregate([
{
$group: {
_id: {
accountID: "$accountID",
locationID: "$locationID",
time: "$time",
},
value: { $sum: "$values.val" },
actualValue: { $sum: "$values.actualVal" },
},
},
{
$addFields: {
absVal: { $abs: "$value" },
absActualVal: { $abs: "$actualValue" },
},
},
{
$project: {
accountID: "$_id.accountID",
locationID: "$_id.locationID",
time: "$_id.time",
value: "$value",
actualValue: "$actualValue",
total: { $add: ["$absVal", "$absActualVal"] },
},
},
{
$match: {
total: { $ne: 0 },
},
},
])
It works, but I lost 1 second from 3.5s to 4.5s when searching in 1m document.
Any suggestion? Thank you first
Some basic boolean logic should suffice, use something like:
Model.aggregate([
{
$match: {
$or: [
{
value: {$ne: 0}
},
{
actualValue: {$ne: 0}
}
]
}
}
{
$project: {
accountID: "$_id.accountID",
locationID: "$_id.locationID",
time: "$_id.time",
value: "$value",
actualValue: "$actualValue",
total: {$add: ["$value", "$actualValue"]},
},
}
])
If you care about efficiency make sure you have a compound index that covers both value and actualValue.

How to group by multiple keys and values

I have a collection of documents where each document has a nestes field outside with two values:
_id: 9287645ztiu234jgk2j3g5jh,
outside: {
temperature: 'low', // 'low' or 'high'
humidity: 'high', // 'low' or 'high'
},
... some more fields
temperature and humidity can have value low or high
I want to count how many times temperature: low, temperature: high, humidity: low, humidity: high is present in each document of the collection, so the query result for e.g. 14 documents should look like this:
{
temperatureLow: 2,
temperatureHigh: 12,
humidityLow: 8,
humidityHigh: 6,
}
I tried a $group (as the only stage in the aggregation pipeline) like this:
$group: {
_id: { temperature: '$outside.temperature', humidity: '$outside.humidity' },
count: { $sum: 1 },
},
And this gives me these documents (EDITED, first post had wrong data):
{
"_id": {
"temperature": "high",
"humidity": "high"
},
"count": 6
},
{
"_id": {
"temperature": "high",
"humidity": "low"
},
"count": 6
},
{
"_id": {
"temperature": "low",
"humidity": "low"
},
"count": 2
}
How can it be combined into on document?
It's possible. You need add project stage with the using cont operator before group:
{
$project: {
"temperatureLow": { $cond: { if: { $eq: ["$outside.temperature", "low"] }, then: 1, else: 0 }},
"temperatureHigh": { $cond: { if: { $eq: ["$outside.temperature", "high"] }, then: 1, else: 0 }},
"humidityLow": { $cond: { if: { $eq: ["$outside.humidity", "low"] }, then: 1, else: 0 }},
"humidityHigh": { $cond: { if: { $eq: ["$outside.humidity", "high"] }, then: 1, else: 0 }}
}
},
{
$group: {
_id: "result",
"temperatureLow": {$sum: "$temperatureLow"},
"temperatureHigh": {$sum: "$temperatureHigh"},
"humidityLow": {$sum: "$humidityLow"},
"humidityHigh": {$sum: "$humidityHigh"},
}
},
Update
or as notes Neil Lunn I can use cond inside sum operator without project stage:
{
$group: {
_id: "result",
"temperatureLow": {$sum: { $cond: { if: { $eq: ["$outside.temperature", "low"] }, then: 1, else: 0 }}},
"temperatureHigh": {$sum: { $cond: { if: { $eq: ["$outside.temperature", "high"] }, then: 1, else: 0 }}},
"humidityLow": {$sum:{ $cond: { if: { $eq: ["$outside.humidity", "low"] }, then: 1, else: 0 }}},
"humidityHigh": {$sum:{ $cond: { if: { $eq: ["$outside.humidity", "high"] }, then: 1, else: 0 }}}
}
},