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