Variables from values in MongoDB Arrays with variable length - mongodb

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'}
}
}]
)

Related

Regroup all elements of nested array of nested array into a single array

I have this collection :
{
"_id": ObjectId("6045eec8f113547ddd3472d9"),
"execution": {
"_id": ObjectId("6045eec8f113547ddd3472d8"),
"steps": [
{
"_id": ObjectId("6045eec8f113547ddd3472d7"),
"actions": [
{
"_id": ObjectId("6045eec8f113547ddd3472d6"),
"title": "action title"
}
]
}
]
}
}
and i want to achieve this result:
{
"_id": ObjectId("6045eec8f113547ddd3472d9"),
"allActions": [
{
"_id": ObjectId("6045eec8f113547ddd3472d6"),
"title": "action title"
}
]
}
to clarify more, i want to add an array that groups together all "actions" under the "steps" array
db.getCollection("missions").aggregate([
{
$match: {
_id: ObjectId("6045eec8f113547ddd3472d9")
}
},
{
$addFields: {
"allActions": "$execution.steps"
}
}
])
Could someone help me complete the part of the code that will allow me to achieve the desired output?
You may just use $reduce and $concatArrays to group all the entries in the actions array
db.missions.aggregate([
{
$match: {
_id: ObjectId("6045eec8f113547ddd3472d9")
}
},
{
$addFields: {
"allActions": {
"$reduce": {
"input": "$execution.steps.actions",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
}
}
}
])
Here is the Mongo playground for your reference.

Wildcard for key in mongodb query

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"
}
}
}
])

Min nested array in mongo

How can I use the $min function to get the min value within nested arrays (and add it to the document)?
[
{
"_id": "a357e77f-a76a-4bc2-8765-923280663e97",
"customers": [
{
"_id": "97170117-4660-4c6f-b8da-2b34d4d0c9ce",
"orders": [
{
"amount": 0.5
},
{
"amount": 6.400001525878906
}
]
},
{
"_id": "7b9ccf5b-3acb-4ed1-8df4-e3b5afc49cba",
"orders": [
{
"amount": 27.29999542236328
},
{
"amount": 0.29999542236328125
}
]
}
]
},
{
"_id": "58433224-8162-4f0a-8168-bc11b4306b0a",
"customers": [
{
"_id": "8a6055d0-9b94-40be-8f96-8fd9088d24aa",
"orders": [
{
"amount": 19.700000762939453
}
]
},
{
"_id": "a50a57b8-61e7-4727-a15a-4a4137b2f81a",
"orders": [
{
"amount": 43.80000305175781
}
]
}
]
}
]
How can I get the min amount value within the $customers.orders.amount path?
I've tried but it returns 0.
db.collection.aggregate([
{
$addFields: {
"amount": {
$sum: "$customers.orders.amount"
}
}
}
])
You can do as below for each customer
playground
db.collection.aggregate([
{//Destruct
"$unwind": "$customers"
},
{//Destruct
"$unwind": "$customers.orders"
},
{//Group by customer id,
$group: {
"_id": "$customers._id",
min: {
$push: {
"$min": "$customers.orders.amount"
}
}
}
}
])
You can use group by null if you want to find min across all the customers.
Found a solution using reduce to create a flat array of the nested arrays and then use $min on that. MongoPlayground
db.collection.aggregate([
{
"$addFields": {
"min": {
$min: {
"$reduce": {
"input": "$customers",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this.orders.amount"
]
}
}
}
}
}
}
])

Writing an aggregation query in MongodB

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

Sort records by array field values in MongoDb

I have a collection which has documents like;
{
"name": "Subject1",
"attributes": [{
"_id": "security_level1",
"level": {
"value": "100",
"valueKey": "ABC"
}
}, {
"_id": "security_score1",
"level": {
"value": "1000",
"valueKey": "CDE"
}
}
]
},
{
"name": "Subject2",
"attributes": [{
"_id": "security_level1",
"level": {
"value": "99",
"valueKey": "XYZ"
}
}, {
"_id": "security_score1",
"level": {
"value": "2000",
"valueKey": "EDF"
}
}
]
},
......
Each document will have so many attributes generated dynamically, can be different in size.
Is it possible to sort records based on level.value of security_level1? (security_level1 is _id field value)
As per above example, the second document ("name": "Subject2") should come first as the value ('level.value') of _id:security_level1 is 99, which is less than of Subject1's security_level1 value (100) - (Ascending order)
Use $filter and $arrayElemAt to get security_level1 item. Then you can use $toInt to convert that value to an integer so that $sort can be applied:
db.collection.aggregate([
{
$addFields: {
level: {
$let: {
vars: {
level_1: { $arrayElemAt: [ { $filter: { input: "$attributes", cond: { $eq: [ "$$this._id", "security_level1" ] } } } ,0] }
},
in: {
$toInt: "$$level_1.level.value"
}
}
}
}
},
{
$sort: {
level: 1
}
}
])
Mongo Playground