Sum all the fields in documents in MongoDB - mongodb

I have documents with a number of tokens in text, one document for a text. I need to calculate a total token count across documents. I don't know the exact number and keys of these fields, so I cannot use $sum for each field individually.
For example, I have two documents:
{
"count": {
"a": 1,
"b": 5,
"c": 7
}
}
{
"count": {
"a": 4,
"c": 2,
"d": 6
}
}
I want to aggregate them and get
{
"count": {
"a": 5,
"b": 5,
"c": 9,
"d": 6
}
}
As I understand, it is not possible, but I just wanted to make sure

Maybe something like this:
db.collection.aggregate([
{
$set: {
count: {
"$objectToArray": "$count"
}
}
},
{
$unwind: "$count"
},
{
$group: {
_id: "$count.k",
v: {
$sum: "$count.v"
}
}
},
{
$project: {
v: 1,
k: "$_id",
_id: 0
}
},
{
$group: {
_id: "total",
count: {
$push: {
k: "$k",
v: "$v"
}
}
}
},
{
$project: {
_id: 0,
count: {
"$arrayToObject": "$count"
}
}
}
])
Explained:
Convert the objects to array(to be easy to manipulate unknown number of fields )
Unwind the array
group by the keys , sum the values.
project the necessary key / values.
group all in single array
Project to convert the array to object as per the expectations
Playground1
Version 2 ( Without unwind ):
db.collection.aggregate([
{
$set: {
count: {
"$objectToArray": "$count"
}
}
},
{
"$group": {
"_id": "test",
count: {
"$push": "$count"
}
}
},
{
$set: {
count: {
$reduce: {
input: {
$concatArrays: "$count"
},
initialValue: [],
in: {
$setUnion: [
"$$this",
"$$value"
]
}
}
}
}
},
{
"$addFields": {
"count": {
"$arrayToObject": {
"$map": {
"input": "$count",
"as": "m",
"in": {
"k": "$$m.k",
"v": {
"$sum": {
"$map": {
"input": "$count",
"as": "d",
"in": {
"$cond": [
{
"$eq": [
"$$d.k",
"$$m.k"
]
},
"$$d.v",
0
]
}
}
}
}
}
}
}
}
}
}
])
Explained:
Convert object to array to be easier later
Group in single document with push
Concat the arrays to single array
Using two nested $map's group & $sum inside the k/v array and convert back to object.
Playground 2

Related

MongoDB get $sum of fields created via $addFields

I'm trying to get sum of fields that were created with $addFields operator.
I'd like to get sum of fields for the first month among all documents.
Please see link to the MongoDB sandbox.
Data:
[
{
"key": 1,
"account": "a",
"cases_total_date": {
"20220101": 1,
"20220102": 2,
"20220103": 3,
"20220501": 4,
"20221201": 5,
"20221202": 6,
}
},
{
"key": 2,
"account": "b",
"cases_total_date": {
"20220101": 11,
"20220102": 12,
"20220103": 13,
"20220501": 14,
"20221201": 15,
"20221202": 16,
}
}
]
Query I've tried:
db.collection.aggregate([
{
"$match": {
"account": {
"$in": [
"a",
"b"
]
}
}
},
{
"$addFields": {
"cases_total_months|202201": {
"$sum": [
"$cases_total_date.20220101",
"$cases_total_date.20220102",
"$cases_total_date.20220103"
]
}
}
},
{
"$group": {
"_id": "",
"cases_total_months|202201_all": {
"$sum": "$cases_total_months|20220101"
}
}
}
])
The response I've got vs expected:
[
{
"_id": "",
"cases_total_months|202201_all": 0 # EXPECTED sum of fields from 2 docs 6+36=42
}
]
Would appreciate any feedback. Thank you!
Using dynamic values as field names is considered an anti-pattern and introduces unnecessary complexity to the queries. With a proper schema, you can do something simple as this:
db.collection.aggregate([
{
"$set": {
"cases_total_months|202201_all": {
"$filter": {
"input": "$cases_total_date",
"as": "ctd",
"cond": {
$and: [
{
$eq: [
2022,
{
$year: "$$ctd.date"
}
]
},
{
$eq: [
1,
{
$month: "$$ctd.date"
}
]
}
]
}
}
}
}
},
{
$group: {
_id: null,
"cases_total_months|202201_all": {
$sum: {
$sum: "$cases_total_months|202201_all.value"
}
}
}
}
])
Mongo Playground
For your current schema, you can still rely on $objectToArray and iterate through the resulting k-v tuples to get what you need.
db.collection.aggregate([
{
$set: {
cases_total_date: {
"$objectToArray": "$cases_total_date"
}
}
},
{
$set: {
"cases_total_months|202201_all": {
"$filter": {
"input": "$cases_total_date",
"as": "ctd",
"cond": {
$eq: [
0,
{
"$indexOfCP": [
"$$ctd.k",
"202201"
]
}
]
}
}
}
}
},
{
$set: {
"cases_total_months|202201_all": {
$sum: "$cases_total_months|202201_all.v"
}
}
},
{
$group: {
_id: null,
"cases_total_months|202201_all": {
$sum: "$cases_total_months|202201_all"
}
}
}
])
Mongo Playground

MongoDB group by and SUM by array

I'm new in mongoDB.
This is one example of record from collection:
{
supplier: 1,
type: "sale",
items: [
{
"_id": ObjectId("60ee82dd2131c5032342070f"),
"itemBuySum": 10
},
{
"_id": ObjectId("60ee82dd2131c50323420710"),
"itemBuySum": 10,
},
{
"_id": ObjectId("60ee82dd2131c50323420713"),
"itemBuySum": 10
},
{
"_id": ObjectId("60ee82dd2131c50323420714"),
"itemBuySum": 20
}
]
}
I need to group by TYPE field and get the SUM. This is output I need:
{
supplier: 1,
sales: 90,
returns: 170
}
please check Mongo playground for better understand. Thank you!
$match - Filter documents.
$group - Group by type and add item into data array which leads to the result like:
[
[/* data 1 */],
[/* data 2 */]
]
$project - Decorate output document.
3.1. First $reduce is used to flatten the nested array to a single array (from Result (2)) via $concatArrays.
3.2. Second $reduce is used to aggregate $sum the itemBuySum.
db.collection.aggregate({
$match: {
supplier: 1
},
},
{
"$group": {
"_id": "$type",
"supplier": {
$first: "$supplier"
},
"data": {
"$push": "$items"
}
}
},
{
"$project": {
_id: 0,
"supplier": "$supplier",
"type": "$_id",
"returns": {
"$reduce": {
"input": {
"$reduce": {
input: "$data",
initialValue: [],
in: {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
},
"initialValue": 0,
"in": {
$sum: [
"$$value",
"$$this.itemBuySum"
]
}
}
}
}
})
Sample Mongo Playground
db.collection.aggregate([
{
$match: {
supplier: 1
},
},
{
"$group": {
"_id": "$ID",
"supplier": {
"$first": "$supplier"
},
"sale": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$type",
"sale"
]
},
"then": {
"$sum": "$items.itemBuySum"
},
"else": {
"$sum": 0
}
}
}
},
"returns": {
"$sum": {
"$sum": {
"$cond": {
"if": {
"$eq": [
"$type",
"return"
]
},
"then": {
"$sum": "$items.itemBuySum"
},
"else": {
"$sum": 0
}
}
}
}
}
}
},
{
"$project": {
_id: 0,
supplier: 1,
sale: 1,
returns: 1
}
}
])

how to label the field in MongoDB and make a sum with respect to date

Need to format the date and make a sum per day but sometimes a or b values are not available.
in the end, get one document with respect to date and sum. I'm using MongoDB 4.2.
Data Structure:
{
"data": {
"11-10-2001": {
"a": 17.281150000000001,
"b": 11.864060000000006
},
"13-10-2020": {
"b": 2.7616699999999994
},
"12-10-2001": {
"b": 4.0809599999999997
},
"09-10-2001": {
"b": 4.1286300000000005
},
"17-10-2001": {
"a": 15.140560000000123,
"b": 5.017139999999998
},
"18-10-2001": {
"b": 1.975189999999997,
"a": 7.093789999999976
}
}
}
Expected Output one document that contains the day and sum:
{
{
day: 11-10-2001,
sum : 29.145
},
{
day: 13-10-201,
sum : 2.7616699
},
{
day: 12-10-2001,
sum : 4.0809599999999997
},
{
day: 17-10-2001,
sum : 20.114
},
{
day: 18-10-2001,
sum : 9.145
}
}
You can try,
$map to iterate loop of data object after converting to array using $objectToArray
add key day, and sum, $reduce to loop of number object after converting to array using $objectToArray, $add to sum the value of number
$unwind deconstruct data array
$replaceRoot to replace data object to root
db.collection.aggregate([
{
$addFields: {
data: {
$map: {
input: { $objectToArray: "$data" },
in: {
day: "$$this.k",
sum: {
$reduce: {
input: { $objectToArray: "$$this.v" },
initialValue: 0,
in: { $add: ["$$this.v", "$$value"] }
}
}
}
}
}
}
},
{ $unwind: "$data" },
{ $replaceRoot: { newRoot: "$data" } }
])
Playground
You can do like following
db.collection.aggregate([
{
$project: { data: { "$objectToArray": "$data" } }
},
{
$unwind: "$data"
},
{
"$replaceRoot": { "newRoot": "$data" }
},
{
$addFields: { v: { "$objectToArray": "$v" } }
},
{
$addFields: {
v: {
$reduce: {
input: "$v",
initialValue: 0,
in: {
$add: [ "$$this.v", "$$value" ]
}
}
}
}
},
{
$group: {
_id: null,
data: {
$push: {
day: "$k",
sum: "$v"
}
}
}
}
])
Working Mongo playground

MongoDB: Total count of distinct values in array field

I've got simple documents like
{
...,
things: ["a", "b", "c"]
},
{
...,
things: ["b", "d", "e"]
},
...
and I want a total count of distinct things, in this case: ["a","b","c","d","e"], the result should be 5.
How to do this using an aggregate query?
My current attempt is
[
{
"$group": {
"_id": "things",
"things": { "$push": "$things" }
}
},
{
"$addFields": {
"allThings": {
"$reduce": {
"input": "$things",
"initialValue": [],
"in": {
"$concatArrays": ["$$value", "$$this"]
}
}
}
}
}
]
which yields all fields in a single array, but I have no idea how to count distinct values in there now.
Happy for any help!
This is literally the first MongoDB query I ever wrote. It works, but happy to hear about improvements:
db.collection.aggregate([
{
$group: {
_id: null,
things: { $push: "$things"}
}
},
{
$project: {
_id: 0,
things: {
$reduce: {
input: "$things",
initialValue: [],
in: {
$setUnion: [ "$$this", "$$value"]
}
}
}
}
},
{
$project: {
item: 1,
numberOfUniqueThings: { $size: "$things" }
}
}
])

How we can use $toUpper with array fields?

How we can use toUpper with array field, I have the following query which compare array field 'locations' with an array of camel case items, now my problem is how we can convert locations field values to upper case and then compare with array.
var array = ["KABUL","KAPISA","WARDAK","LOGAR","PARWAN","BAGHLAN","NANGARHAR","LAGHMAN",
"BAMYAN","PANJSHER","KHOST","GHAZNI","KUNARHA","PAKTYA","PAKTIKA","KUNDUZ",
"NOORISTAN","SAMANGAN","TAKHAR","DAYKUNDI","BADAKHSHAN","BALKH","GHOR",
"UROZGAN","FARYAB","ZABUL","SAR-E-PUL","NIMROZ","JAWZJAN","HELMAND","BADGHIS",
"KANDAHAR","FARAH","HERAT"];
db.getCollection('test').aggregate([
{ "$project": {
"locations": {
"$map": {
"input": {
"$setIntersection": ["$locations", array ]
},
"in": { "k": "$$this", "v": 1 }
}
}
}},
{ "$unwind": "$locations" },
{ "$group": {
"_id": "$locations.k",
"v": { "$sum": "$locations.v" }
}},
{ "$sort": { "_id": 1 } },
{ "$group": {
"_id": null,
"obj": { "$push": { "k": "$_id", "v": "$v" } }
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$obj" }
}}
])
locations field is like :
"locations" : [
"Afghanistan",
"Kabul",
.....
],
Using $map to transform "each" element of course:
{ "$project": {
"locations": {
"$map": {
"input": {
"$setIntersection": [
{ "$map": { "input": "$locations", "in": { "$toUpper": "$$this" } } },
array
]
},
"in": { "k": "$$this", "v": 1 }
}
}
}},