I would like to write a query for summing each field payment of the first object inside an array, for each element of my database.
The schema is the following:
var schema = new Schema({
plate : String,
category : String,
brand : String,
model : String,
sign : String,
tax : [{
date : { type: Date, default: Date.now },
payment : { type: Number, default: 0 },
}],
});
I wrote the following function for my query:
function(callback){
Machine.aggregate(
[
{$unwind: "$tax"},
{$group : {
_id : null ,
tot : { $sum: "$tax.payment"}
}}
]
,callback);
}
But in this way I retrieve the sum of all the payments inside the array tax. My goal is to take only the first, so I tried with $tax.0.payment and using arrayElemAt : [$tax,0] but all my trials gave a tot = 0.
The idea here is pick out the first element of each of payment array field via $arrayElemAt with projection and then group-sum the field $group $sum.
Query:
db.collection.aggregate([
{
$project: {
firstPayment: {
$arrayElemAt: [
"$tax",
0
]
}
}
},
{
$group: {
_id: null,
PaymentSum: {
$sum: "$firstPayment.payment"
}
}
}
]);
Demo O/P:
[
{
"PaymentSum": 11,
"_id": null
}
]
Machine.aggregate({$unwind:
{path: "$tax"}
},
{$group:{
_id: "$_id",
payment: {$first: "$tax.payment"}
}},
{$group: {
_id: null,
total: {$sum: "$payment"}
}}
)
Explanation:
First I used $unwind on tax, then in the first $group stage I grouped them according to _id,
that way I will get the first payment information from unwinded tax array.
Then I used $sum to add them in the second $group stage.
I tested with this data:
Machine collection docs:
{
"_id" : ObjectId("5dbf09a4d7912bcbc61ee9e4"),
"tax" : [
{
"payment" : 10
},
{
"payment" : 20
}
]
},
{
"_id" : ObjectId("5dbf09aad7912bcbc61ee9e5"),
"tax" : [
{
"payment" : 30
},
{
"payment" : 40
}
]
},
{
"_id" : ObjectId("5dbf09afd7912bcbc61ee9e6"),
"tax" : [
{
"payment" : 50
},
{
"payment" : 60
}
]
}
The result I got is:
{ "_id" : null, "tot" : 90 }
I hope this fulfills your requirements.
Add $arrayElemAt in your aggregate like this..
Machine.aggregate(
[
{$unwind: "$tax"},
{$group : {
_id : null ,
tot : { $sum: { $arrayElemAt : [ "$tax.payment", 0 ]}
}}
]
,callback);
Related
I have data in mongodb with multiple fields, I am trying to filter data on basis of a field named create_date and then trying to fetch totalrecordscount along with further filtering the data. Following is the data structure:
"_id" : ObjectId("62a886a76034628f8028e8dc"),
"create_time" : "18:53:01",
"close_date" : "2022-05-09",
"close_time" : "13:34:43",
"country_code" : "US",
"closed_case" : 1,
"resolution_days" : 8,
"status_code" : "5",
"state_code" : "1",
"issue_resolved_flag" : "Yes",
"incident_created_by" : "09D4A6BB-C51E-EB11-A813-000D3A58F938",
"incident_modified_by" : "A3CBC776-DF3C-E711-810B-E0071B7284D1",
"modifiedon" : "2022-05-09 13:34:46.0",
"row_insertion_dttm" : "2022-06-14 02:58:21.202",
"data_source_category" : "CASE",
"resolution_duration_minutes" : 5060,
"create_date" : "2022-05-01",
"repeat_case_different_issue7_day" : 0,
"repeat_case_same_issue_7day" : 0,
"scr_7day" : 1,
"ocr_7day" : 0,
"csat_status" : "no"
I am able to aggregate the data on basis of create date and fetch the totalrecordscount for a particular date using following command :
country_code:1
}},
{
$match:{create_date:{$gt:"2022-06-01"}}
},
{$group:{ _id: {datebasis: "$create_date"},
TotalRecordscount: { $sum: 1 },
}
},
])
The output is: {
"_id" : {
"datebasis" : "2022-06-17"
},
"TotalRecordscount" : 13254.0
}
/* 2 */
{
"_id" : {
"datebasis" : "2022-06-14"
},
"TotalRecordscount" : 16688.0
}
/* 3 */
{
"_id" : {
"datebasis" : "2022-06-09"
},
"TotalRecordscount" : 15478.0
}
But my ask is to further group the data to get the number of records on a particular date for fields like "scr_7day" equals to 0 or "resolution_duration_minutes" < 1440.
Can you help me in achieving this?
Assume you solve the date string logic as mentioned in the comment, my answer just focuses on your question.
You can work with $count and $cond operators to calculate the documents by condition.
db.collection.aggregate([
{
$group: {
_id: {
datebasis: "$create_date"
},
TotalRecordscount: {
$sum: 1
},
scr_7dayIsZero: {
$sum: {
$cond: {
if: {
$eq: [
"$scr_7day",
0
]
},
then: 1,
else: 0
}
}
},
resolution_duration_minutesLessThan1440: {
$sum: {
$cond: {
if: {
$lt: [
"$resolution_duration_minutes",
1440
]
},
then: 1,
else: 0
}
}
}
}
}
])
Sample Mongo Playground
With the following document:
{
"_id" : "123",
"firstArray" : [
{
"_id" : "456",
"status" : "open",
"nestedArray" : [
{
"_id" : "100",
"quantity" : 10
},
{
"_id" : "101",
"quantity" : 10
},
{
"_id" : "102",
"quantity" : 10
}
},
{
"_id" : "789",
"status" : "open",
"nestedArray" : [
{
"_id" : "200",
"quantity" : 10
},
{
"_id" : "201",
"quantity" : 10
},
{
"_id" : "202",
"quantity" : 10
}
}
]
}
How can I update the quantity by 20 of the nested ID 101 element and pull the one with the ID 201 from the same MongoDB query ?
I am trying to do that in Java with $set and $pull operator and I'm stuck with the following error:
[BulkWriteError{index=0, code=40, message='Update created a conflict
at 'firstArray.0.nestedArray'', details={}}]
MongoDB doesn’t allow multiple operations on the same property in the same update call. This means that the two operations must happen in two individual queries.
The first solution is you can write 2 seperate queries for both the operations.
The second solution is you can try update with aggregation pipeline, starting from MongoDB 4.2,
$map to iterate loop of firstArray
$filter to iterate loop of nestedArray and remove _id: "201" record
$map to iterate loop of above filtered nestedArray
$cond check condition if _id: "101" then return new quantity otherwise return current
$mergeObjects to merge current object with updated properties
db.collection.update(
{ "firstArray.nestedArray._id": "101" },
[{
$set: {
firstArray: {
$map: {
input: "$firstArray",
in: {
$mergeObjects: [
"$$this",
{
nestedArray: {
$map: {
input: {
$filter: {
input: "$$this.nestedArray",
cond: { $ne: ["$$this._id", "201"] }
}
},
in: {
_id: "$$this._id",
quantity: {
$cond: [
{ $eq: ["$$this._id", "101"] },
20,
"$$this.quantity"
]
}
}
}
}
}
]
}
}
}
}
}
])
Playground
Can someone please help me with this query ??
Query >>> Find all warehouses that keep item "Planner" and having in-stock quantity less than 20
This is the sample document in the items collection of the Inventory database :
{
"_id" : ObjectId("6067640da9a907175caaca34"),
"id" : 101,
"name" : "Planner",
"status" : "A",
"height" : 12,
"tags" : [
"mens",
"womens"
],
"warehouses" : [
{
"name" : "Phoenix",
"quantity" : 25
},
{
"name" : "Quickshift",
"quantity" : 15
},
{
"name" : "Poona",
"quantity" : 10
}
]
}
This is what I have tried doing :
db.items.find({"name":"Planner","warehouses.quantity":{"$lt":20}},{"warehouses":1,"_id":0}).pretty()
But it gives me the result as
{
"warehouses" : [
{
"name" : "Phoenix",
"quantity" : 25
},
{
"name" : "Quickshift",
"quantity" : 15
},
{
"name" : "Poona",
"quantity" : 10
}
]
}
Demo - https://mongoplayground.net/p/IpD5ypWSZyt
Use aggregation query
db.collection.aggregate([
{ $match: { "name": "Planner" } },
{ $unwind: "$warehouses" }, // break into individual documents
{ $match: { "warehouses.quantity": { $lt: 20 } } }, // query the data
{ $group: { _id: "_id", warehouses: { $push: "$warehouses" } } } // join them back
])
Demo - https://mongoplayground.net/p/pdTY0IkIqgF
Use $elemMatch only if you think there will be only 1 array element matching per document
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.
db.collection.find({
"name":"Planner",
"warehouses": { "$elemMatch": { "quantity": { $gt: 20 } } }
},
{ "warehouses.$": 1})
https://docs.mongodb.com/manual/reference/method/db.collection.find/#find-projection
Sample data: there are multiple similar collection:
{
"_id" : NumberLong(301),
"telecom" : [
{
"countryCode" : {
"value" : "+1"
},
"extension" : [
{
"url" : "primary",
"value" : [
"true"
]
}
],
"modifiedValue" : {
"value" : "8887778888"
},
"system" : {
"value" : "phone"
},
"useCode" : {
"value" : "Home Phone"
},
"value" : {
"value" : "8887778888"
}
},
{
"extension" : [
{
"url" : "primary",
"value" : [
"true"
]
}
],
"modifiedValue" : {
"value" : "abc#test.com"
},
"system" : {
"value" : "email"
},
"useCode" : {
"value" : "work"
},
"value" : {
"value" : "abc#test.com"
}
}
]
}
Issue: I want to cont the collection where telecom.system.value = email and countryCode doesn't exist in the email part object. here I am attaching a script but I need one line query
var count = 0,i;
db.getCollection('practitioner').find({"telecom.system.value":"email"}).forEach(function(practitioner){
//print("updating : " +practitioner._id.valueOf())
telecom = practitioner.telecom.valueOf()
for(i= 0;i<telecom.length;i++){
if(telecom[i].system.value === 'email' && telecom[i].countryCode){
count+=1;
}
}
});
print(" Total count of the practitioner with country code in email object: "+count)
Above mention, the script is working fine and the output is as I expected. but the script is not optimised and I want to write in a single line query. Thanks in advance.
You can try aggregation method aggregate(),
Approach 1:
$match condition for countryCode should exists and system.value should be email
$filter to iterate loop of telecom array and check both condition, this will return expected elements
$size to get total element from above filter result
$group by null and count total
var result = await db.getCollection('practitioner').aggregate([
{
$match: {
telecom: {
$elemMatch: {
countryCode: { $exists: true },
"system.value": "email"
}
}
}
},
{
$project: {
count: {
$size: {
$filter: {
input: "$telecom",
cond: {
$and: [
{ $ne: [{ $type: "$$this.countryCode" }, "missing"] },
{ $eq: ["$$this.system.value", "email"] }
]
}
}
}
}
}
},
{
$group: {
_id: null,
count: { $sum: "$count" }
}
}
]);
print("Total count of the practitioner with country code in email object: "+result[0].count);
Playground
Approach 2:
$match condition for countryCode should exists and system.value should be email
$unwind deconstruct telecom array
$match to filter document using above conditions
$count to get total elements count
var result = await db.getCollection('practitioner').aggregate([
{
$match: {
telecom: {
$elemMatch: {
countryCode: { $exists: true },
"system.value": "email"
}
}
}
},
{ $unwind: "$telecom" },
{
$match: {
"telecom.countryCode": { $exists: true },
"telecom.system.value": "email"
}
},
{ $count: "count" }
]);
print("Total count of the practitioner with country code in email object: "+result[0].count);
Playground
I have not tested the performance but you can check and use as per your requirement.
I have a schema which is:
{
"_id" : "12345678",
"action1" : [
{
"date" : "2021-01-15",
"value" : 20
},
{
"date" : "2021-01-14",
"value" : 16
}
],
"action2" : [
{
"date" : "2021-01-15",
"value" : 30
},
{
"date" : "2021-01-14",
"value" : 10
}
],
"action3" : [
{
"date" : "2021-01-15",
"value" : 40
},
{
"date" : "2021-01-14",
"value" : 20
}
],
"action4" : [
{
"date" : "2021-01-15",
"value" : 60
},
{
"date" : "2021-01-14",
"value" : 40
}
]
}
Now I want to write an aggregate query to filter out counts within a date range (for last 7 days, or 30 days or 90 days)
so the final sum should look something like the following:
{
_id: "12345678"
action1 : {
alltime: number,
last7Days : number,
last30Days: number,
last90Days: number
},
action2: {
alltime: number,
last7Days : number,
last30Days: number,
last90Days: number
},
action3: {
alltime: number,
last7Days : number,
last30Days: number,
last90Days: number
},
action4: {
alltime: number,
last7Days : number,
last30Days: number,
last90Days: number
},
}
I am trying to get the total number of actions using $project and $match for a particular _id
But how can I filter the past7days/30days/90days data
My query looks like the following
db.collection.aggregate([
{
$project: {
alltimeAction1: {$sum: "$action1.value"},
alltimeAction2: {$sum: "$action2.value"},
alltimeAction3: {$sum: "$action3.value"},
alltimeAction4: {$sum: "$action4.value"}
}
},
{
$match: {
_id: "12345678"
}
}
])
is mapReduce the only option available?
db.test1.aggregate([
{
"$project": {//Reshape actions, you need this as you have dynamic keys
data: {
"$objectToArray": "$$ROOT"
}
}
},
{//Denormalize
"$unwind": "$data"
},
{//Denormalize
"$unwind": "$data.v"
},
{
"$project": {//Formatting date
"_id": 1,
"key": "$data.k",
"date": {
"$dateFromString": {
"dateString": "$data.v.date",
"format":"%Y-%m-%d"
}
},
"value": "$data.v.value"
}
},
{
"$addFields": {//Getting the conditions ready
"7Days": {
$gte:["$date", new Date(new Date().getTime() - (7*24*3600*1000))]
},
"30Days": {
$gte:["$date", new Date(new Date().getTime() - (30*24*3600*1000))]
}
}
},
{
$group:{//Grouping them, you can add few more cases
"_id": "$key",
"7days":{
$sum:"$value"
},
"30days":{
$sum:"$value"
}
}
}
])
You don't need to do this much complex query if you have flattened schema where actionName can be identified by a constant field name.