Trying to use $cond to $sum and $subtract - mongodb

My documents:
_id:"DwNMQtHYopXKK3rXt"
client_id:"ZrqKavXX8ieGpx5ae"
client_name:"luana"
companyId:"z3ern2Q7rdvviYCGv"
is_active:true
client_searchable_name:"luana"
status:"paid"
items:Object
id:912602
gross_amount:1000
type:"service"
description:"Pedicure com Zé (pacote)"
item_id:"bjmmPPjqKdWfwJqtC"
user_id:"gWjCskpHF2a3xHYy9"
user_id_commission:50
user_id_amount:0
use_package:true
quantity:1
item_costs:Array
discount_cost:Object
type:"package"
value:100
charge_from:"company_only"
entity_id:"LLRirWu5DabkRna7X"
created_at:2019-10-29T10:35:39.493+00:00
updated_at:2019-10-29T10:36:42.983+00:00
version:"2"
created_by:"2QBRDN9MACQagSkJr"
amount:0
multiple_payment_methods:Array
closed_at:2019-10-29T10:36:52.781+00:00
So i made a $project:
{
_id: 0,
closed_at: 1,
serviceId: "$items.item_id",
serviceAmount: "$items.gross_amount",
discounts:"$items.discount_cost"
}
And then $group
_id: {
month: { $month: "$closed_at" },
serviceId: "$serviceId",
discountType: "$discounts.type",
discountValue: "$discounts.value"
},
totalServiceAmount: {
$sum: "$serviceAmount"
}
}
I'm trying to make a $sum of values of the categories in my DB, actually i filtered all the data, so i have exactly what i need, like that;
_id:Object
"month":10
"serviceId":"MWBqhMyW8ataGxjBT"
"discountType":""courtesy"
"discountValue":100
"totalServiceAmount":5000
So, i have 5 types of discounts on my DB, they are: Percentage (discount in percentage), courtesy (make the service amount 0), package (make the service amount 0), gross (gross discount of value) and null if there's no discount o value.
so, if the type of discount is;
Percentage: I need to subtract the discountValue for the totalServiceAmount (discountValue will be in percentage, how i do that subtract if total serviceAmount is on gross value)
Courtesy and package: I need to transform the totalServiceAmount in 0 value.
Gross: i need to subtract the discountValue for the totalServiceAmount.
Null: just let totalServiceAmount.
I tried like that, to make some test, but i really don't know if i'm goign to the right path, the result was null for every amountWithDiscount.
{
$project: {
{
amountWithDiscount: {
$cond: {
if: {
$eq: ["$_id.discountType", "null"]
},
then: "$serviceAmount", else: {
$cond: {
if: {
$eq: ["$_id.discountType", "gross"]
},
then: {
$subtract: ["$serviceAmount", "$_id.discountValue"]
},
else: "$serviceAmount"
}
}
}
}
}
Make sense?

I create a collection with your grouping result:
01) Example of Documents:
[
{
"_id": "5db9ca609a17899b8ba6650d",
"month": 10,
"serviceId": "MWBqhMyW8ataGxjBT",
"discountType": "courtesy",
"discountValue": 0,
"totalServiceAmount": 5000
},
{
"_id": "5db9d0859a17899b8ba66856",
"month": 10,
"serviceId": "MWBqhMyW8ataGxjBT",
"discountType": "gross",
"discountValue": 100,
"totalServiceAmount": 5000
},
{
"_id": "5db9d0ac9a17899b8ba66863",
"month": 10,
"serviceId": "MWBqhMyW8ataGxjBT",
"discountType": "percentage",
"discountValue": 10,
"totalServiceAmount": 5000
},
{
"_id": "5db9d0d89a17899b8ba6687f",
"month": 10,
"serviceId": "MWBqhMyW8ataGxjBT",
"discountType": null,
"discountValue": 10,
"totalServiceAmount": 6000
}
]
02) Query:
db.collection.aggregate([
{
$project: {
discountType: "$discountType",
amountWithDiscount: {
$cond: {
if: {
$eq: [
"$discountType",
null
]
},
then: "$totalServiceAmount",
else: {
$cond: {
if: {
$eq: [
"$discountType",
"gross"
]
},
then: {
$subtract: [
"$totalServiceAmount",
"$discountValue"
]
},
else: {
$cond: {
if: {
$eq: [
"$discountType",
"percentage"
]
},
then: {
$multiply: [
"$totalServiceAmount",
{
$subtract: [
1,
{
$divide: [
"$discountValue",
100
]
}
]
}
]
},
else: "$totalServiceAmount"
}
}
}
}
}
}
}
}
])
A working example at https://mongoplayground.net/p/nU7vhGN-uSp.
I don't know if I fully understand your problem, but
take a look and see if it solves your problem.

Related

MongoDB: Search by field with date and update it by condition

Please tell me how can I fulfill the following condition - if the time in the info.startDate field is not equal to 00 hours, increase the date (2021-05-27) by 1 day ahead, set the time to 00:00:00.000Z. I tried to do it clumsily, through Mongock, getting all the elements of the collection and doing a check through LocalDateTime, but my heap overflowed, which is logical because the collection is large. How can I do this through Mongock or at least a manual request to MongoDB. So far I've only written this:
db.getSiblingDB("ervk_core").getCollection("supervision").updateMany(
{},
[
{
"$set": {
"info.startDate": {
"$cond": {
if: {
$eq: [
"$info.startDate",
(there should be a condition at midnight)
]
},
then: (here the day should be added to full and the time should be set to midnight)
}
}
}
}
])
I would like to use dateToString to do a partial search by hour, but as I understand it, this function can only be used in an aggregation.
I would be grateful for your help :)
If you're using Mongo version 5.0+ then you can use $dateTrunc and $dateAdd to achieve this quite easily, like so:
db.collection.updateMany(
{},
[
{
$set: {
"info.startDate": {
$cond: [
{
$ne: [
{
$hour: "$info.startDate"
},
0
]
},
{
$dateTrunc: {
date: {
$dateAdd: {
startDate: "$info.startDate",
unit: "day",
amount: 1
}
},
unit: "day",
}
},
"$info.startDate"
]
}
}
}
])
For older Mongo versions this is slightly messier, you should use $dateFromParts to create the new date object, like so:
db.collection.updateMany(
{},
[
{
$set: {
"info.startDate": {
$cond: [
{
$ne: [
{
$hour: "$info.startDate"
},
0
]
},
{
$dateFromParts: {
"year": {
$year: {
$add: [
"$info.startDate",
86400000
]
}
},
"month": {
$month: {
$add: [
"$info.startDate",
86400000
]
}
},
"day": {
$dayOfMonth: {
$add: [
"$info.startDate",
86400000
]
}
},
"hour": 0,
"minute": 0,
"second": 0,
"millisecond": 0,
}
},
"$info.startDate"
]
}
}
}
])
Mongo Playground

MongoDB - How to bring age group data

How to bring age group base data from a collection in MongoDB i.e how many people are 0-18, 19-24, 25-34, 35+
[
{
"_id": ObjectId("608be7c608c7de2367c89638"),
"status": true,
"gender": "Male",
"first_name": "Vinter",
"last_name": "R",
"dob": "1-2-1999"
},
{
"_id": ObjectId("608be7c608c7de2267c89639"),
"status": true,
"gender": "Male",
"first_name": "Ray",
"last_name": "Morgan",
"dob": "1-2-2015"
}
....
]
See the Mongo Playground:
https://mongoplayground.net/p/4ydNg9Plh6P
Interesting question!
Would like to credit to #Takis and #YuTing.
Good hint from #Takis's comment on $bucket.
#YuTing's answer is good.
Think this answer is shorter by utilizing the feature provided by MongoDB.
$toDate - Convert date string to Date (supported for version 4.0 above).
$dateDiff - Date subtraction and get the unit (Supported in version 5).
$$CURRENT - Variable to get the current iterated document. For adding into persons array field (via $push).
$switch - To display group value based on conditions (Optional).
db.collection.aggregate([
{
"$addFields": {
"age": {
$dateDiff: {
startDate: {
$toDate: "$dob"
},
endDate: "$$NOW",
unit: "year"
}
}
}
},
{
$bucket: {
groupBy: "$age",
// Field to group by
boundaries: [
0,
19,
25,
35
],
// Boundaries for the buckets
default: "Other",
// Bucket id for documents which do not fall into a bucket
output: {
// Output for each bucket
"count": {
$sum: 1
},
"persons": {
$push: "$$CURRENT"
}
}
}
},
{
$project: {
_id: 0,
group: {
$switch: {
branches: [
{
case: {
$lt: [
"$_id",
19
]
},
then: "0-18"
},
{
case: {
$lt: [
"$_id",
25
]
},
then: "19-24"
},
{
case: {
$lt: [
"$_id",
35
]
},
then: "25-34"
}
],
default: "35+"
}
},
count: 1,
persons: 1
}
}
])
Sample Mongo Playground
use $bucket
db.collection.aggregate([
{
$bucket: {
groupBy: {
"$subtract": [
{
$year: new Date()
},
{
$toInt: {
$substr: [
"$dob",
{
$subtract: [
{
$strLenCP: "$dob"
},
4
]
},
4
]
}
}
]
},
// Field to group by
boundaries: [
0,
19,
25,
35,
100
],
// Boundaries for the buckets
default: "Other",
// Bucket id for documents which do not fall into a bucket
output: {
// Output for each bucket
"count": {
$sum: 1
},
"artists": {
$push: {
"name": {
$concat: [
"$first_name",
" ",
"$last_name"
]
},
"age": {
"$subtract": [
{
$year: new Date()
},
{
$toInt: {
$substr: [
"$dob",
{
$subtract: [
{
$strLenCP: "$dob"
},
4
]
},
4
]
}
}
]
}
}
}
}
}
}
])
mongoplayground

Mongo DB aggregate grouping multiple values that belong to the same document

I have documents that look like this
{
"_id": "5e3334cede31d9555e38dbee",
"time": 400,
"datetime": "2020-01-05T16:35:42.315Z",
"version": "2.0.30",
"hostname": "bvasilchik-lt.extron.com",
"testfile": "cards.txt",
"tests": 5,
"failures": 3,
"skips": 0,
"status": "Failed",
"__v": 0
}
I want to create a result that includes the documents that have the highest number of time per testfile name, so if the top 10 were all the same testfile name I'd only want to show the top one that had the same testfile name.
I have done this but I also wanted to include another field that also shows the number of tests matching that grouping, but the only ways I found were to add the $first or the $last or the $max or the $min for the tests field, but that wouldn't be correct b/c the highest time might have a different number of tests.
I am also matching results from a specific date range
const times = await Suite.aggregate([
{
"$match": {
datetime: { "$gte": dateRange.startDate, "$lt": dateRange.endDate, }
}
},
{
"$group": {
_id: "$testfile",
time: { "$max" : "$time" },
}
},
{
"$sort": {
time: order
}
},
{
"$project": {
_id: 0,
testfile: "$_id",
time: "$time"
}
}
])
this produces these results
[
{
"testfile": "lists.txt",
"time": 900
},
{
"testfile": "buttons.txt",
"time": 800
},
{
"testfile": "cards.txt",
"time": 400
},
{
"testfile": "popover.txt",
"time": 300
},
{
"testfile": "about-pages.neb",
"time": 76
}
]
but what I want it to return is
[
{
"testfile": "lists.txt",
"tests": 5,
"time": 900
},
{
"testfile": "buttons.txt",
"tests": 4,
"time": 800
},
{
"testfile": "cards.txt",
"tests": 8,
"time": 400
},
{
"testfile": "popover.txt",
"tests": 1,
"time": 300
},
{
"testfile": "about-pages.neb",
"tests": 2,
"time": 76
}
]
You need to add extra field into $group and $project stages.
You need to use $max operator for time field and accumulatetests field time:tests values. In the last stage, we $reduce tests field taking highest value
{
"$group": {
_id: "$testfile",
time: {
$max: "$time"
},
tests: {
"$push": {
time: "$time",
tests: "$tests"
}
}
}
},
{
"$sort": {
time: 1
}
},
{
"$project": {
_id: 0,
testfile: "$_id",
time: "$time",
tests: {
$reduce: {
input: "$tests",
initialValue: 0,
in: {
$add: [
"$$value",
{
$cond: [
{
$and: [
{
$eq: [
"$time",
"$$this.time"
]
},
{
$gt: [
"$$this.tests",
"$$value"
]
}
]
},
{
$subtract: [
"$$this.tests",
"$$value"
]
},
0
]
}
]
}
}
}
}
}
MongoPlayground

MongoDB aggregation, Group by value interval,

MongoDB documents:
[{
_id: '123213',
elevation: 2300,
area: 25
},
{
_id: '343221',
elevation: 1600,
area: 35,
},
{
_id: '545322',
elevation: 500
area: 12,
},
{
_id: '234234',
elevation: null,
area: 5
}]
I want to group these on a given interval on elevation and summarize the area property.
Group 1: < 0
Group 2: 0 - 1500
Group 3: 1501 - 3000,
Group 4: > 3000
So the expected output would be:
[{
interval: '1501-3000',
count: 2,
summarizedArea: 60
},
{
interval: '0-1500',
count: 1,
summarizedArea: 12,
},
{
interval: 'N/A',
count: 1,
summarizedArea: 5
}]
If possible, I want to use the aggregation pipeline.
Maybe something with $range? Or a combination of $gte and $lte?
As Feliix suggested $bucket should do the job, but boundaries should be slightly different to play well with negative and N/A values:
db.collection.aggregate([
{
$bucket: {
groupBy: "$elevation",
boundaries: [ -Number.MAX_VALUE, 0, 1501, 3001, Number.POSITIVE_INFINITY ],
default: Number.NEGATIVE_INFINITY,
output: {
"count": { $sum: 1 },
"summarizedArea" : { $sum: "$area" }
}
}
}
])
The formatting stage below can be added to the pipeline to adjust shape of the response:
{ $group: {
_id: null,
documents: { $push: {
interval: { $let: {
vars: {
idx: { $switch: {
branches: [
{ case: { $eq: [ "$_id", -Number.MAX_VALUE ] }, then: 3 },
{ case: { $eq: [ "$_id", 0 ] }, then: 2 },
{ case: { $eq: [ "$_id", 1501 ] }, then: 1 },
{ case: { $eq: [ "$_id", 3001 ] }, then: 0 }
],
default: 4
} }
},
in: { $arrayElemAt: [ [ ">3000", "1501-3000", "0-1500", "<0", "N/A" ], "$$idx" ] }
} },
count: "$count",
summarizedArea: "$summarizedArea"
} }
} }
$group with _id: null $push es all groups into array of a single document.
$let maps $_id from previous stage to text labels of interval defined in the array [ ">3000", "1501-3000", "0-1500", "<0", "N/A" ]. For that it calculates idx index of the label using $switch.
It must be way simpler to implement the logic on application level unless you absolutely need to do it in the pipeline.
you can use $bucket introduced in MongoDB 3.4 to achive this:
db.collection.aggregate([
{
$bucket: {
groupBy: "$elevation",
boundaries: [
0,
1500,
3000,
5000
],
default: 10000,
output: {
"count": {
$sum: 1
},
"summarizedArea": {
$sum: "$area"
}
}
}
}
])
output:
[
{
"_id": 0,
"count": 1,
"summarizedArea": 12
},
{
"_id": 1500,
"count": 2,
"summarizedArea": 60
},
{
"_id": 10000,
"count": 1,
"summarizedArea": 5
}
]
you can try it here: mongoplayground.net/p/xFe7ZygMqaY

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 }}}
}
},