Min nested array in mongo - mongodb

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

Related

(MongoDB) Combining Union and Intersection in the same pipeline

I have the following aggregation pipeline running in the latest version of mongoDB and pymongo:
[
{
"$project": {
"union": {
"$setUnion": [
"$query_a",
"$query_b"
]
}
}
},
{
"$unwind": "$union"
},
{
"$group": {
"_id": "$union.ID",
"date_a": {
"$addToSet": "$union.date_a"
},
"date_b": {
"$addToSet": "$union.date_b"
}
}
},
{
"$unwind": "$date_a"
},
{
"$unwind": "$date_b"
},
{
"$project": {
"_id": 1,
"date_a": "$date_a",
"date_b": "date_b",
"diff": {
"$subtract": [
{
"$toInt": "$date_b"
},
{
"$toInt": "$date_a"
}
]
}
}
},
{
"$match": {
"diff": {
"$gt": 0,
"$lte": 20
}
}
},
]
This gives the union of the 2 pipelines query_a and query_b. After this union I want to get an intersection on ID with the pipeline query_c: (query_a UNION query_b) INTERSECTION query_c.
For this playground example the desired output would be:
[
{
"ID": "c80ea2cb-3272-77ae-8f46-d95de600c5bf",
},
{
"ID": "cdbcc129-548a-9d51-895a-1538200664e6",
}
]
You could change and augment your pipeline a little to get your desired output.
db.collection.aggregate([
{
"$project": {
"union": {
// do the intersection here
"$filter": {
"input": {
"$setUnion": [
"$query_a",
"$query_b"
]
},
"as": "elem",
"cond": {
// only take IDs in query_c
"$in": ["$$elem.ID", "$query_c.ID"]
}
}
}
}
},
{
"$unwind": "$union"
},
{
"$group": {
"_id": "$union.ID",
"date_a": {
"$addToSet": "$union.date_a"
},
"date_b": {
"$addToSet": "$union.date_b"
}
}
},
{
"$unwind": "$date_a"
},
{
"$unwind": "$date_b"
},
{
"$project": {
"diff": {
"$subtract": [
{
"$toInt": "$date_b"
},
{
"$toInt": "$date_a"
}
]
}
}
},
{
"$match": {
"diff": {
"$gt": 0,
"$lte": 20
}
}
},
{ // get unique _id's
"$group": {
"_id": "$_id"
}
},
{ // rename _id to ID
"$project": {
"_id": 0,
"ID": "$_id"
}
}
])
Try it on mongoplayground.net.
You can do it with:
Updating first $project stage to also project an array of IDs from query_c.
Using $set as a second stage where you would filter out all items from the union of query_a and query_b, that does not have ID that's in query_c.
You can do it like this:
{
"$project": {
"union": {
"$setUnion": [
"$query_a",
"$query_b"
]
},
"query_c": {
"$map": {
"input": "$query_c",
"in": "$$this.ID"
}
}
}
},
{
"$set": {
"union": {
"$filter": {
"input": "$union",
"cond": {
"$in": [
"$$this.ID",
"$query_c"
]
}
}
}
}
},
The rest of your Aggregation pipeline can remain the same.
Working example

Merging documents while storing all ids in an array

So in mongodb 3.2 (for reasons, we can't upgrade yet) I have a bunch of documents in this structure:
// Document 1
{
"id": "record-1",
"childItems": [
"child-1",
"child-2"
],
"specLookup":[
{
"specId": "spec-1"
},
{
"specId": "spec-2"
}
]
},
// Document 2
{
"id": "record-2",
"childItems": [
"child-3"
],
"specLookup":[
{
"specId": "spec-3"
}
]
}
I need an aggregation query that will merge and manipulate all these records into one document, while maintaining all individual ids in a new array. So based on the two documents above I'd end up with this:
{
"ids": ["record-1", "record-2"],
"childItems": [
"child-1",
"child-2",
"child-3"
],
"specIds":[
"spec-1"
"spec-2",
"spec-3"
]
}
How can I do this? Cheers!
You can do it like this:
$group - to group all the documents add collect all the ids, childItems and specLookup.
$reduce with $concatArrays - to get the data in the format you want.
db.collection.aggregate([
{
"$group": {
"_id": null,
"ids": {
"$addToSet": "$id"
},
"childItems": {
"$addToSet": "$childItems"
},
"specLookup": {
"$addToSet": "$specLookup.specId"
}
}
},
{
"$set": {
"childItems": {
"$reduce": {
"input": "$childItems",
"initialValue": [],
"in": {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
},
"specLookup": {
"$reduce": {
"input": "$specLookup",
"initialValue": [],
"in": {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
}
}
}
])
Working example

Variables from values in MongoDB Arrays with variable length

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

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

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