MongoDB , getting the minimum & maximum of array subset - mongodb

Am trying to find a way to get the minimum number of orders between
2019-03-17 and 2019-03-19 excluding 2019-03-15 from the results ..
{
"_id" : ObjectId("5c8ffdadde62bf097d54ec47"),
"productId" : "32886845998",
"orders" : [
{
"date" : ISODate("2019-03-15T00:00:00.000+0000"),
"orders" : NumberInt(9)
},
{
"date" : ISODate("2019-03-17T00:00:00.000+0000"),
"orders" : NumberInt(21)
},
{
"date" : ISODate("2019-03-18T00:00:00.000+0000"),
"orders" : NumberInt(20)
},
{
"date" : ISODate("2019-03-19T00:00:00.000+0000"),
"orders" : NumberInt(30)
}
]
}
I tried using $min and $max operator but that didn't help because it iterated through the full array to find maximum & minimum
db.products.aggregate([
{
$project: {
maximum: {
$reduce: {
input: "$orders",
initialValue: 0,
in: {
$max: [
"$$value",
{
$cond: [
{ $gte: [ "$$this.date", ISODate("2019-03-17T00:00:00.000+0000") ] },
"$$this.orders",
0
]
}
]
}
}
}
}
}
])

You can use $filter to apply filtering by orders.date and then you can apply $min and $max on filtered set:
db.col.aggregate([
{
$project: {
filteredOrders: {
$filter: {
input: "$orders",
cond: {
$and: [
{ $gte: [ "$$this.date", ISODate("2019-03-17T00:00:00.000+0000") ] },
{ $lte: [ "$$this.date", ISODate("2019-03-19T00:00:00.000+0000") ] },
]
}
}
}
}
},
{
$project: {
min: { $min: "$filteredOrders.orders" },
max: { $max: "$filteredOrders.orders" },
}
}
])

Related

How to perform conditional arithmetic operations in MongoDB

I've following schema
{
"_id" : ObjectId("xxxxx"),
"updatedAt" : ISODate("2022-06-29T13:10:36.659+0000"),
"createdAt" : ISODate("2022-06-29T08:06:51.264+0000"),
"payments" : [
{
"paymentId" : "xxxxx",
"paymentType" : "charge",
"paymentCurrency" : "PKR",
"paymentMode" : "cash",
"paymentTotal" : 13501.88,
"penalties" : 100
},
{
"paymentId" : "ccccc",
"paymentType" : "refund",
"paymentCurrency" : "PKR",
"paymentMode" : "",
"paymentTotal" : 13061.879999999997,
"penalties" : 430.0
}
]
}
I want to get all paymentTotal sum if paymentType is 'charge' else subtract the paymentTotal from the sum if paymentType is other than charge, i.e refund also subtract penalties from total sum
I've tried following query which is not working giving me syntax error like,
A syntax error was detected at the runtime. Please consider using a higher shell version or use the syntax supported by your current shell.
xxx
Blockquote
db.getCollection("booking").aggregate([
{
$match: {
createdAt : {
"$gte":ISODate("2022-06-28"),
"$lte":ISODate("2022-06-30"),
}
}
},
{$unwind: '$payments'},
{
"$group":{
"_id" : "$_id",
"total" : {
$sum: "$payments.paymentTotal"
}
},
},
{
$project :
{
"grandTotal":{
$cond:{
if:{$eq:["$payments.paymentType", "charge"]},
then:{$add : {"$total,$payments.paymentTotal"}},
else:{ $subtract: {"$total,$payments.paymentTotal"}}
}
}
}
}
]);
I've tried, Condition and Switch statements but both are not working, or maybe I'm using them wrong.
You can use $reduce for it:
db.collection.aggregate([
{
$match: {
createdAt: {
$gte: ISODate("2022-06-28T00:00:00.000Z"),
$lte: ISODate("2022-06-30T00:00:00.000Z")
}
}
},
{
$project: {
grandTotal: {
$reduce: {
input: "$payments",
initialValue: 0,
in: {
$cond: [
{$eq: ["$$this.paymentType", "charge"]},
{$add: ["$$this.paymentTotal", "$$value"]},
{$subtract: ["$$value", "$$this.paymentTotal"]}
]
}
}
}
}
}
])
See how it works on the playground example
You can do simple math:
db.collection.aggregate([
{
$set: {
grandTotal: {
$map: {
input: "$payments",
in: {
$multiply: [
"$$this.paymentTotal",
{ $cond: [{ $eq: ["$$this.paymentType", "charge"] }, 1, -1] }
]
}
}
}
}
},
{ $set: { grandTotal: { $sum: "$grandTotal" } } }
])

MongoDB: add element to an inner array of array with an object that contains field calculated on another field

I have this document:
{
"_id" : ObjectId("626c0440e1b4f9bb5568f542"),
"ap" : [
{
"ap_id" : ObjectId("000000000000000000000001"),
"shop_prices" : [
{
"shop_id" : ObjectId("000000000000000000000097"),
"price" : 102
}
]
}
],
"bc" : [
{
"bc_id" : ObjectId("000000000000000000000003"),
"price" : 102
},
{
"bc_id" : ObjectId("000000000000000000000004"),
"price" : 104
}
],
"stock_price" : 70
}
My need is to eventually add to ap.shop_prices an element if not exists with this structure:
{
"shop_id" : ObjectId("000000000000000000000096"),
"price" : 104
}
where the price is bc.price where bc.bc_id = ObjectId("000000000000000000000004")
This is my first (unsuccesfull) try:
updateMany(
{
"_id": {"$eq": ObjectId("626c0421e1b4f9bb5568f531")},
"ap":{
$elemMatch:{
"ap_id":{$in:[ObjectId("000000000000000000000001")]},
"shop_prices.shop_id":{$ne:ObjectId("000000000000000000000096")}
}
},
"bc.bc_id": ObjectId("000000000000000000000003")
},
[
{"$set":
{"ap.$.shop_prices":
{"$cond":
[{"$in": [ObjectId("000000000000000000000096"), "$ap.$.shop_prices.shop_id"]}, "$ap.$.shop_prices",
{"$concatArrays":
["$ap.$.shop_prices",
[{"shop_id": ObjectId("000000000000000000000096"), "price": ???}]
]
}
]
}
}
}
]
)
thanks in advance
You can do that:
finding the bc related to your request using the $project
using $map in the $set operator
This should be the solution:
db.getCollection('test').update({
"ap": {
$elemMatch: {
"ap_id":{$in:[ObjectId("000000000000000000000001")]},
"shop_prices.shop_id":{$ne:ObjectId("000000000000000000000096")}
}
},
"bc.bc_id": ObjectId("000000000000000000000004")
},
[
{
$project: {
ap: 1,
bc: 1,
stock_price: 1,
current_bc: {
$arrayElemAt: [ {
$filter: {
input: "$bc",
as: "curr_bc",
cond: {$eq: ["$$curr_bc.bc_id", ObjectId("000000000000000000000004")]}
}
}, 0 ]
}
}
},
{
$set: {
"ap": {
"$map": {
input: "$ap",
as: "current_ap",
in: {
$cond: [
{$eq: [ObjectId("000000000000000000000001"), "$$current_ap.ap_id"]},
{
"$mergeObjects": [
"$$current_ap",
{"shop_prices": {$concatArrays: ["$$current_ap.shop_prices", [{"shop_id": ObjectId("000000000000000000000096"), "price": "$current_bc.price"}]]}}
]
},
"$$current_ap"
]
}
}
}
}
}
])

How to fetch records based on the max date in mongo document array

Below is the document which has an array name datum and I want to filter the records based on group by year and filter by the types and max date.
{
"_id" : ObjectId("5fce46ca6ac9808276dfeb8c"),
"year" : 2018,
"datum" : [
{
"Type" : "1",
"Amount" : NumberDecimal("100"),
"Date" : ISODate("2018-05-30T00:46:12.784Z")
},
{
"Type" : "1",
"Amount" : NumberDecimal("300"),
"Date" : ISODate("2023-05-30T00:46:12.784Z")
},
{
"Type" : "2",
"Amount" : NumberDecimal("340"),
"Date" : ISODate("2025-05-30T00:46:12.784Z")
},
{
"Type" : "3",
"Amount" : NumberDecimal("300"),
"Date" : ISODate("2021-05-30T00:46:12.784Z")
}
]
}
The aggregate Query I tried.
[{$group: {
_id :"$year",
RecentValue :
{
$sum: {
$reduce: {
input: '$datum',
initialValue: {},
'in': {
$cond:
[
{
$and:
[
{$or:[
{ $eq: [ "$$this.Type", '2' ] },
{$eq: [ "$$this.Type", '3' ] }
]},
{ $gt: [ "$$this.Date", "$$value.Date" ] },
]
}
,
"$$this.Amount",
0
]
}
}
}
}
}}]
the expected output would be which having the max date "2025-05-30T00:46:12.784Z"
{
_id :2018,
RecentValue : 340
}
Please let me know what mistake I did in the aggregate query.
You can get max date before $group stage,
$addFields to get document that having max date from, replaced $or with $in condition and corrected return value
$group by year and sum Amount
db.collection.aggregate([
{
$addFields: {
datum: {
$reduce: {
input: "$datum",
initialValue: {},
"in": {
$cond: [
{
$and: [
{ $in: ["$$this.Type", ["2", "3"]] },
{ $gt: ["$$this.Date", "$$value.Date"] }
]
},
"$$this",
"$$value"
]
}
}
}
}
},
{
$group: {
_id: "$year",
RecentValue: { $sum: "$datum.Amount" }
}
}
])
Playground

MongoDB If condition as the second field in gt

I want to select the $user_a_seen_at field if $user_a_id == socket.user_id otherwise select the $user_b_seen_at field. But my query isn't working.
$gt: ["$$this.created_at", IF CONDITION TO SELECT A FIELD ]
$project: {
unread_messages: {
$size: {
$filter: {
input: "$messages",
cond: {
$and: [
{ $eq: ["$$this.to_id", socket.user_id] },
{
$gt: [
"$$this.created_at", {
if: { $eq: ["$user_a_id", socket.user_id] },
then: "$user_a_seen_at",
else: "$user_b_seen_at"
}
]
}
]
}
}
}
}
}
Sample data
{
"_id" : ObjectId("5e4c57649fad2e2cac9f8cd5"),
"user_a_id" : 1,
"user_b_id" : 2,
"user_a_seen_at" : ISODate("2020-02-18T21:30:12.418Z"),
"user_b_seen_at" : ISODate("2020-02-18T15:30:12.418Z"),
"messages" : [
{
"text" : "Hello",
"_id" : ObjectId("5e4c57649fad2e2cac9f8cd4"),
"from_id" : 1,
"to_id" : 2,
"created_at" : ISODate("2020-02-18T21:30:12.409Z")
}
],
"created_at" : ISODate("2020-02-18T21:30:12.418Z"),
"last_activity" : ISODate("2020-02-18T21:30:12.418Z"),
"__v" : 0
}
You need to use $cond operator to evaluate a boolean expression to return one of the two specified return expressions
{ $cond: { if: <boolean-expression>, then: <true-case>, else: <false-case> } }
//or simplified
{ $cond: [ <boolean-expression>, <true-case>, <false-case> ] }
db.collection.aggregate([
{
$project: {
unread_messages: {
$size: {
$filter: {
input: "$messages",
cond: {
$and: [
{
$eq: [
"$$this.to_id",
socket.user_id
]
},
{
$gt: [
"$$this.created_at",
{
$cond: [
{
$eq: [
"$user_a_id",
socket.user_id
]
},
"$user_a_seen_at",
"$user_b_seen_at"
]
}
]
}
]
}
}
}
}
}
}
])
MongoPlayground

Count Both Outer and Inner embedded array in a single query

{
_id: ObjectId("5dbdacc28cffef0b94580dbd"),
"comments" : [
{
"_id" : ObjectId("5dbdacc78cffef0b94580dbf"),
"replies" : [
{
"_id" : ObjectId("5dbdacd78cffef0b94580dc0")
},
]
},
]
}
How to count the number of element in comments and sum with number of relies
My approach is do 2 query like this:
1. total elements of replies
db.posts.aggregate([
{$match: {_id:ObjectId("5dbdacc28cffef0b94580dbd")}},
{ $unwind: "$comments",},
{$project:{total:{$size:"$comments.replies"} , _id: 0} }
])
2. count total elements of comments
db.posts.aggregate([
{$match: {_id:ObjectId("5dbdacc28cffef0b94580dbd")}},
{$project:{total:{$size:"$comments.replies"} , _id: 0} }
])
Then sum up both, do we have any better solution to write the query like return the sum of of total element comments + replies
You can use $reduce and $concatArrays to "merge" an inner "array of arrays" into a single list and measure the $size of that. Then simply $add the two results together:
db.posts.aggregate([
{ "$match": { _id:ObjectId("5dbdacc28cffef0b94580dbd") } },
{ "$addFields": {
"totalBoth": {
"$add": [
{ "$size": "$comments" },
{ "$size": {
"$reduce": {
"input": "$comments.replies",
"initialValue": [],
"in": {
"$concatArrays": [ "$$value", "$$this" ]
}
}
}}
]
}
}}
])
Noting that an "array of arrays" is the effect of an expression like $comments.replies, so hence the operation to make these into a single array where you can measure all elements.
Try using the $unwind to flatten the list you get from the $project before using $count.
This is another way of getting the result.
Input documents:
{ "_id" : 1, "array1" : [ { "array2" : [ { id: "This is a test!"}, { id: "test1" } ] }, { "array2" : [ { id: "This is 2222!"}, { id: "test 222" }, { id: "222222" } ] } ] }
{ "_id" : 2, "array1" : [ { "array2" : [ { id: "aaaa" }, { id: "bbbb" } ] } ] }
The query:
db.arrsizes2.aggregate( [
{ $facet: {
array1Sizes: [
{ $project: { array1Size: { $size: "$array1" } } }
],
array2Sizes: [
{ $unwind: "$array1" },
{ $project: { array2Size: { $size: "$array1.array2" } } },
],
} },
{ $project: { result: { $concatArrays: [ "$array1Sizes", "$array2Sizes" ] } } },
{ $unwind: "$result" },
{ $group: { _id: "$result._id", total1: { $sum: "$result.array1Size" }, total2: { $sum: "$result.array2Size" } } },
{ $addFields: { total: { $add: [ "$total1", "$total2" ] } } },
] )
The output:
{ "_id" : 2, "total1" : 1, "total2" : 2, "total" : 3 }
{ "_id" : 1, "total1" : 2, "total2" : 5, "total" : 7 }