Here is my sample document structure e.g.
{
"my_object": {
"1": {
"seq": "1",
"time": "xyz",
},
"2": {
"seq": "2",
"time": "abc",
"sub_aray": {
"0": {
"value": 10
},
"1": {
"value": 10
},
"2": {
"value": -10
}
}
}
}
}
So what I want to achieve is, Sum of all the sub_array's value if exists, if sub_array isn't found, default it to 0
{"seq" : "1", "sub_array" : 0},
{"seq" : "2", "sub_array" : 10}
My Mongo version is 3.4.6 and I am using PyMongo as my driver.
You can do as below
playground
db.collection.aggregate([
{
$project: {
"array": { //To remove dynamic keys - 1,2,etc
"$objectToArray": "$my_object"
}
}
},
{//reshaping array
"$unwind": "$array"
},
{
$project: {//reshaping sub array to access via a static name
"k": {
"$objectToArray": "$array.v.sub_aray"
},
"seq": "$array.v.seq"
}
},
{
"$project": {//actual logic, output structure
"sub_array": {
$sum: "$k.v.value"
},
"_id": 0,
"seq": 1
}
}
])
The trick part is to use the $objectToArray operator to iterate my_object items.
db.collection.aggregate([
{
"$project": {
"my_object": {
"$map": {
input: {
"$objectToArray": "$my_object"
},
as: "obj",
in: {
seq: "$$obj.v.seq",
sub_array: {
$sum: {
$map: {
input: {
"$objectToArray": "$$obj.v.sub_aray"
},
as: "sub",
in: "$$sub.v.value"
}
}
}
}
}
}
}
},
{
"$unwind": "$my_object"
},
{
"$replaceRoot": {
"newRoot": "$my_object"
}
}
])
MongoPlayground
Related
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
This is my first question ever here so super excited to learn and apologies if the syntax is not up to mark, I will improve with time.
"item" is an Array ( this doc has only one element)
"adjudication" is a nested Array with a variable number of elements(same structure)
I want to create keys out of "adjudication.category.coding.code" without hardcoding as the values will be different with each document but will have the same string length
I tried using "$map" to apply the same logic to each array element but failed
"item": [
{
"adjudication": [
{
"amount": {"code": "USD", "system": "4217", "value": 22.51},
"category": {
"coding": [
{
"code": "bb.org/paid_amt",
"system": "bb.org/adjudication"
}
]
},
"reason": {
"coding": [
{
"code": "C",
"system": "bb.org/cvrg_status"
}
]
}
},
{
"amount": {"code": "USD", "system": "4217", "value": 0},
"category": {
"coding": [
{
"code": "bb.org/discount_amt",
"system": "bb.org/adjudication"
}
]
}
}
]
}
]
Output desired
adjudication: {paid_amt: 22.51, discount_amt: 0}
Welcome to SO. I hope the following code will solve your exception. Sometime you may modify based on you wish.
$unwind to deconstruct the array
$map to loop / modify through the array, and $arrayElementAt to. get the first object from the array
$let to find the last potion by $spliting for "paid_amt" and "discount_amt". So this will be an array of object. But you need objects. So the next part I make it as k:v pair.
$arrayToObject to make array to object by using above k:v pair. ("k" and "v" names are must. Can't replace by any other names)
$group to reconstruct the array that we did in 1st step
Here is the code,
db.collection.aggregate([
{ "$unwind": "$item" },
{
$project: {
"item.adjudication": {
"$map": {
"input": "$item.adjudication",
"in": {
amount: "$$this.amount.value",
code: {
"$arrayElemAt": [ "$$this.category.coding", 0 ]
}
}
}
}
}
},
{
$project: {
"item.adjudication": {
"$map": {
"input": "$item.adjudication",
"in": {
v: "$$this.amount",
k: {
"$let": {
"vars": {
"code": {
"$split": [ "$$this.code.code", "/" ]
}
},
"in": { "$arrayElemAt": [ "$$code", -1 ] }
}
}
}
}
}
}
},
{
$project: {
"item.adjudication": {
"$arrayToObject": "$item.adjudication"
}
}
},
{
"$group": {
"_id": "$_id",
"item": { "$push": "$item" }
}
}
])
Working Mongo playground
try this playground
db.collection1.aggregate(
[{
$unwind: {
path: '$item'
}
}, {
$unwind: {
path: '$item.adjudication'
}
}, {
$unwind: {
path: '$item.adjudication.category'
}
}, {
$unwind: {
path: '$item.adjudication.category.coding'
}
}, {
$group: {
_id: null,
data: {
$push: {
k: '$item.adjudication.category.coding.code',
v: '$item.adjudication.amount.value'
}
}
}
}, {
$project: {
_id:0,
adjudication: {$arrayToObject: '$data'}
}
}]
)
[
{
"_id": ObjectId("id-1"),
"tests": [
{
"category": "cat1",
"status": "status1",
},
{
"category": "cat1",
"status": "status2",
},
{
"category": "cat2",
"status": "status2",
},
],
},
{
"_id": ObjectId("id-2"),
"tests": [
{
"category": "cat2",
"status": "status1",
},
{
"category": "cat1",
"status": "status1",
},
{
"category": "cat1",
"status": "status2",
},
],
}
]
I have the above collection, my intention is to generate the below result. Please note that the statuses and categories are dynamic.
[
{
"id" : id-1,
"status": {
"status1": count,
"status2": count
},
"category": {
"cat1": count of it,
"cat2": count of it
}
},
{
"id" : id-2,
"status": {
"status1": count of it,
"status2": count of it
},
"category": {
"cat1": count of it,
"cat2": count of it
}
}
]
What I've attempted to do till now, is
Unwinded tests field, then
{
"$group": {
"_id": {
"id": "$_id",
"testStatus": "$tests.status"
},
"val": {
"$sum": 1
}
}
},
{
"$group": {
"_id": {
"id": "$_id.id",
},
"resGroup": {
"$addToSet": {
k: "$_id.testStatus",
v: "$val"
}
}
}
},
{
"$project": {
"_id": "$_id.id",
"statusGroup": {
"$arrayToObject": "$resGroup"
}
}
}
I've done the same for the category field and used $facet to run multiple aggregations.
But, am unable to fetch the result in the required format.
Any help on this will be appreciated.
Thanks
MongoDB Version: 3.4
$map to iterate loop of tests array and convert the object to an array using $objectToArray
$unwind deconstruct tests array
$unwind again deconstruct tests array because it's a nested array
$group by _id, k, and v and get the total count
$group by _id and k and construct the array of the status field in items
$arrayToObject convert items key-value array to an object
$group by _id and construct the array of items
$arrayToObject convert items array to object
db.collection.aggregate([
{
$project: {
tests: {
$map: {
input: "$tests",
in: { $objectToArray: "$$this" }
}
}
}
},
{ $unwind: "$tests" },
{ $unwind: "$tests" },
{
$group: {
_id: {
_id: "$_id",
k: "$tests.k",
v: "$tests.v"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: {
_id: "$_id._id",
k: "$_id.k"
},
items: {
$push: {
k: "$_id.v",
v: "$count"
}
}
}
},
{
$group: {
_id: "$_id._id",
items: {
$push: {
k: "$_id.k",
v: { $arrayToObject: "$items" }
}
}
}
},
{ $project: { items: { $arrayToObject: "$items" } } }
])
Playground
I have a collection equivalent to:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"sides": {
"0": {
"dist": 100
},
"1": {
"dist": 10
}
}
},
{
"_id": ObjectId("5a934e000102030405000001"),
"sides": {
"0": {
"dist": 100
}
}
}
]
I would like to perform a query that return any documents that has for any key nested in sides has the key dist with a specific value. Something like:
db.collection.find({"sides.*.dist": 10})
Here * acts as a wildcard, any key would be valid in its place.
That would retrieve:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"sides": {
"0": {
"dist": 100
},
"1": {
"dist": 10
}
}
}
]
On the other hand
db.collection.find({"sides.*.dist": 100})
Would retrive both documents.
the following song and dance won't be neccessary if sides field was an array...
db.collection.find(
{
$expr: {
$gt: [{
$size: {
$filter: {
input: { $objectToArray: "$sides" },
as: "x",
cond: { $eq: ["$$x.v.dist", 10] }
}
}
}, 0]
}
})
You could get the matching elements using this
db.collection.aggregate([
{
"$project": {
"sides_array": {//Reshape the sides
"$objectToArray": "$sides"
}
}
},
{//Denormalize to get more than one matches
"$unwind": "$sides_array"
},
{
"$match": {//Condition goes here
"sides_array.v.dist": 10
}
},
{
"$group": {//Group the data back, after unwinding
"_id": "$_id",
"sides": {
"$push": "$sides_array"
}
}
},
{
"$project": {//Reshape the data
"_id": 1,
"sides": {
"$arrayToObject": "$sides"
}
}
}
])
Collection:
[
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
My query:
db.collection.aggregate([
{
"$addFields": {
"arrayofkeyvalue": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$arrayofkeyvalue"
},
{
"$group": {
"_id": null,
"allkeys": {
"$addToSet": "$arrayofkeyvalue.k"
}
}
}
])
The output currently:
[
{
"_id": null,
"allkeys": [
"additional",
"_id",
"para",
"type",
"name"
]
}
]
Detail see Playground
What I want to do is add a new column which includes all of top key of the mongodb query output, exclude "para". And then combine it with the old collection to form a new json.
Is it possible?
The expected result:
{
"column": [{"prop": "name"}, {"prop": "type"}, {"prop": "additional"}],
"columnData": [
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
}
You have the right general idea in mind, here's how I would do it by utilizing operators like $filter, $map and $reduce to manipulate the objects structure.
I separated the aggregation into 3 parts for readability but you can just merge stage 2 and 3 if you wish.
db.collection.aggregate([
{
"$group": {
"_id": null,
columnData: {
$push: "$$ROOT"
},
"keys": {
"$push": {
$map: {
input: {
"$objectToArray": "$$ROOT"
},
as: "field",
in: "$$field.k"
}
}
}
}
},
{
"$addFields": {
unionedKeys: {
$filter: {
input: {
$reduce: {
input: "$keys",
initialValue: [],
in: {
"$setUnion": [
"$$this",
"$$value"
]
}
}
},
as: "item",
cond: {
$not: {
"$setIsSubset": [
[
"$$item"
],
[
"_id",
"para"
]
]
}
}
}
}
}
},
{
$project: {
_id: 0,
columnData: 1,
column: {
$map: {
input: "$unionedKeys",
as: "key",
in: {
prop: "$$key"
}
}
}
}
}
])
Mongo Playground