mongodb aggregate subtract by key - mongodb

DB : mongoDB
i have following query result.
(ex1)
[
{
"array": [
{
"_id": 1,
"createdAt": NumberLong(1675408092026),
"pType": "A"
},
{
"_id": 2,
"createdAt": NumberLong(1675408097462),
"pType": "B"
},
{
"_id": 3,
"createdAt": NumberLong(1675408101259),
"pType": "A"
},
{
"_id": 4,
"createdAt": NumberLong(1675408104682),
"pType": "B"
}
]
}
]
OR
(ex2)
[
{
"array": [
{
"_id": 1,
"createdAt": NumberLong(1675408092026),
"pType": "A"
},
{
"_id": 2,
"createdAt": NumberLong(1675408095026),
"pType": "A"
},
{
"_id": 3,
"createdAt": NumberLong(1675408097462),
"pType": "B"
},
{
"_id": 4,
"createdAt": NumberLong(1675408101259),
"pType": "A"
},
{
"_id": 5,
"createdAt": NumberLong(1675408104682),
"pType": "B"
},
{
"_id": 6,
"createdAt": NumberLong(1675408108682),
"pType": "B"
},
{
"_id": 7,
"createdAt": NumberLong(1675408118682),
"pType": "A"
}
]
}
]
I want to subtract the 'createdAt' value of pType 'A' from the 'createdAt' value of 'B'
And I want to add up the subtracted value.
(ex2)
1675408097462(_id:2) - 1675408092026(_id:1)
+
1675408104682(_id:4) - 1675408101259(_id:3)
(ex2)
1675408097462(_id:3) - 1675408095026(_id:2)
+
1675408104682(_id:5) - 1675408101259(_id:4)
i want to following result using with mongodb 'aggregate'
please help me.
The expected result is...
(ex1)
{
"sum_of_diff": "8859"
}
(ex2)
{
"sum_of_diff": "5859"
}
thank you

One option is to use $reduce with $mergeObjects:
db.collection.aggregate([
{$project: {
sum_of_diff: {$reduce: {
input: "$array",
initialValue: {lastA: {_id: -1}, sum: 0},
in: {$mergeObjects: [
"$$value",
{$cond: [
{$eq: ["$$this.pType", "A"]},
{lastA: "$$this"},
{$cond: [
{$eq: [{$subtract: ["$$this._id", 1]}, "$$value.lastA._id"]},
{sum: {
$add: [
"$$value.sum",
{$subtract: ["$$this.createdAt", "$$value.lastA.createdAt"]}
]
}},
{}
]}
]}
]}
}}
}},
{$project: {sum_of_diff: "$sum_of_diff.sum", _id: 0}}
])
See how it works on the playground example

Related

How can I get the documents having an id that are attribute values of another document in mongodb?

This is an exemple:
{"_id": 1,
"attr1": "x",
"class":"vehicle"}
{"_id": 2,
"attr1": "xf",
"class":"vehicle"}
{"_id": 3,
"attr1": "xz",
"class":"vehicle"}
{"_id": 4,
"attr1": "xddz",
"class":"vehicle"}
{"_id": 11,
"attr1": "xy",
"class":"seat",
"vehicle":[1,2]}
{"_id": 12,
"attr1": "exy",
"class":"seat",
"vehicle":[3,2]}
I would like to know what query or aggregate to use in order to get the document with "class" vehicle having their ids in the array of attribute value of the documents with class "seat".
In our example I need to have those documents returned:
{"_id": 1,
"attr1": "x",
"class":"vehicle"}
{"_id": 2,
"attr1": "xf",
"class":"vehicle"}
{"_id": 3,
"attr1": "xz",
"class":"vehicle"}
Assuming every document is in the same collection, you could do something like this :
db.collection.aggregate([
{
"$group": {
"_id": null,
"root": {
"$push": "$$ROOT"
},
}
},
{
$project: {
_id: 0,
res: {
"$filter": {
"input": "$root",
"as": "values",
"cond": {
$in: [
"$$values._id",
{
"$reduce": {
input: "$root.vehicle",
initialValue: [],
in: {
"$setUnion": [
"$$value",
"$$this"
]
},
}
},
]
}
}
}
}
},
{
"$unwind": "$res"
},
{
"$replaceRoot": {
"newRoot": "$res"
}
}
])
You can check on https://mongoplayground.net/p/iYG8o0ZOde5

Mongodb Aggregations - Group by date including condition

I have a series of documents gathered by aggregation grouping. This is the result for one document:
{
"_id": {
"ip": "79.xxx.xxx.117",
"myDate": "2022-10-19"
},
"date": "2022-10-19",
"allVisitedPages": [
{
"page": "/",
"time": {
"time": "2022-10-19T11:35:44.655Z",
"tz": "-120",
"_id": "634fe1100a011986b7137da0"
}
},
{
"page": "/2",
"time": {
"time": "2022-10-19T12:14:29.536Z",
"tz": "-120",
"_id": "634fea257acb264f23d421f1"
}
},
{
"page": "/",
"time": {
"time": "2022-10-19T15:37:30.002Z",
"tz": "-120",
"_id": "634fea266001ea364eeb38ea"
}
},
],
"visitedPages": 3,
"createdAt": "2022-10-19T11:35:44.920Z"
},
I want to get this (in this case 2 documents as the time difference between array position 2 and 3 is greater than 2 hours):
{
"_id": {
"ip": "79.xxx.xxx.117",
"myDate": "2022-10-19"
},
"date": "2022-10-19",
"allVisitedPages": [
{
"page": "/",
"durationInMinutes": "39",
"time": {
"time": "2022-10-19T11:35:44.655Z",
"tz": "-120",
"_id": "634fe1100a011986b7137da0"
}
},
{
"page": "/2",
"durationInMinutes": "2",
"time": {
"time": "2022-10-19T12:14:29.536Z",
"tz": "-120",
"_id": "634fea257acb264f23d421f1"
}
}
],
"visitedPages": 2,
},
{
"_id": {
"ip": "79.xxx.xxx.117",
"myDate": "2022-10-19"
},
"date": "2022-10-19",
"allVisitedPages": [
{
"page": "/",
"durationInMinutes": "2",
"time": {
"time": "2022-10-19T15:37:30.002Z",
"tz": "-120",
"_id": "634fea266001ea364eeb38ea"
}
},
],
"visitedPages": 1,
},
I want to get a new grouping document if the time between an array position and the following array position is greater than 2 hours. On the last array position it show always show "2".
I tried $divide and $datediff. But this is not possible on the group stage as it's an unary operator. An approach I tried is to calculate the sum of start and end time by dividing. But how to execute this on an array level on the group stage? Maybe someone could point me in the right direction if possible at all?
You can group and then reduce, but another option is to use $setWindowFields to calculate your grouping index before grouping:
db.collection.aggregate([
{$setWindowFields: {
partitionBy: {$concat: ["$ip", "$date"]},
sortBy: {"time.time": 1},
output: {prevtime: {
$push: "$time.time",
window: {documents: [-1, "current"]}
}}
}},
{$addFields: {
minutesDiff: {
$toInt: {
$dateDiff: {
startDate: {$first: "$prevtime"},
endDate: {$last: "$prevtime"},
unit: "minute"
}
}
}
}},
{$addFields: {deltaIndex: {$cond: [{$gt: ["$minutesDiff", 120]}, 1, 0]}}},
{$setWindowFields: {
partitionBy: {$concat: ["$ip", "$date"]},
sortBy: {"time.time": 1},
output: {
groupIndex: {
$sum: "$deltaIndex",
window: {documents: ["unbounded", "current"]}
},
duration: {
$push: "$minutesDiff",
window: {documents: ["current", 1]}
}
}
}
},
{$set: {
duration: {
$cond: [
{$and: [
{$eq: [{$size: "$duration"}, 2]},
{$lte: [{$last: "$duration"}, 120]}
]},
{$last: "$duration"},
2
]
}
}},
{$group: {
_id: {ip: "$ip", myDate: "$date", groupIndex: "$groupIndex"},
date: {$first: "$date"},
allVisitedPages: {$push: {page: "$page", time: "$time", duration: "$duration"}},
visitedPages: {$sum: 1}
}},
{$unset: "_id.groupIndex"}
])
See how it works on the playground example

How can I get a single item from the array and display it as an object? and not as an array Mongodb

I have a collection from which I need specific obj e.g. notes.blok2 and notes.curse5 as an object, not as an array
{
"year":2020,
"grade":4,
"seccion":"A",
"id": 100,
"name": "pedro",
"notes":[{"curse":5,
"block":1,
"score":{ "a1": 5,"a2": 10, "a3": 15}
},{"curse":5,
"block":2,
"score":{ "b1": 10,"b2": 20, "b3": 30}
}
]
}
My query
notas.find({
"$and":[{"grade":1},{"seccion":"A"},{"year":2020}]},
{"projection":{ "grade":1, "seccion":1,"name":1,"id":1,
"notes":{"$elemMatch":{"block":2,"curse":5}},"notes.score":1} })
It works but returns notes like array
{
"_id": "55",
"id": 100,
"grade": 5,
"name": "pedro",
"seccion": "A",
"notes": [
{"score": { "b1": 10,"b2": 20, "b3": 30} }
]
}
But I NEED LIKE THIS: score at the same level as others and if doesn't exist show empty "score":{}
{
"year":2020,
"grade":5,
"seccion":"A",
"id": 100,
"name": "pedro",
"score":{ "b1": 10,"b2": 20, "b3": 30}
}
Demo - https://mongoplayground.net/p/XlJqR2DYW1X
You can use aggregation query
db.collection.aggregate([
{
$match: { // filter
"grade": 1,
"seccion": "A",
"year": 2020,
"notes": {
"$elemMatch": {
"block": 2,
"curse": 5
}
}
}
},
{ $unwind: "$notes" }, //break into individual documents
{
$match: { // match query on individual note
"notes.block": 2,
"notes.curse": 5
}
},
{
$project: { // projection
"grade": 1,
"seccion": 1,
"name": 1,
"id": 1,
"score": "$notes.score"
}
}
])
Update
Demo - https://mongoplayground.net/p/mq5Kue3UG42
Use $filter
db.collection.aggregate([
{
$match: {
"grade": 1,
"seccion": "A",
"year": 2020
}
},
{
$set: {
"score": {
"$filter": {
"input": "$notes",
"as": "note",
"cond": {
$and: [
{
$eq: [ "$$note.block",3]
},
{
$eq: [ "$$note.curse", 5 ]
}
]
}
}
}
}
},
{
$project: {
// projection
"grade": 1,
"seccion": 1,
"name": 1,
"id": 1,
"score": {
"$first": "$score.score"
}
}
}
])
If you want empty object for score when match not found you can do -
Demo - https://mongoplayground.net/p/dumax58kgrc
{
$set: {
score: {
$cond: [
{ $size: "$score" }, // check array length
{ $first: "$score" }, // true - take 1st
{ score: {} } // false - set empty object
]
}
}
},

MongoDB multiple counts, single document, arrays

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here

Count records according to the difference of two dates in Mongodb

Based on the documents below, I need to count the records that the difference between the inicial_date and end_date is greater than and less than 5 minutes and the name = A.
[{
"_id": 1,
"name": "A",
"inicial_date": "2019-01-01 12:00:00",
"end_date": "2019-01-01 12:01:00"
},{
"_id": 2,
"name": "A",
"inicial_date": "2019-01-01 12:05:00",
"end_date": "2019-01-01 12:07:00"
},{
"_id": 3,
"name": "A",
"inicial_date": "2019-01-01 12:00:00",
"end_date": "2019-01-01 12:06:00"
},{
"_id": 4,
"name": "A",
"inicial_date": "2019-01-01 12:04:00",
"end_date": "2019-01-01 12:05:00"
},
"_id": 5,
"name": "A",
"inicial_date": "2019-01-01 12:10:00",
"end_date": "2019-01-01 12:20:00"
},{
"_id": 6,
"name": "A",
"inicial_date": "2019-01-01 12:00:00",
"end_date": "2019-01-01 12:08:00"
},{
"_id": 7,
"name": "A",
"inicial_date": "2019-01-01 13:00:00",
"end_date": "2019-01-01 13:01:00"
},{
"_id": 8,
"name": "B",
"inicial_date": "2019-01-01 14:00:00",
"end_date": "2019-01-01 14:09:00"
}]
The expected result:
{
"less_than_5": 4,
"greater_than_5": 3
}
Well, you can do this using an aggregate query,
db.collection.aggregate([
{
$match: {
name: "A"
}
},
{
// find time difference, the result is in milliseconds
$project: {
timeDiffInMilliseconds: {
$subtract: [
{
$toDate: "$end_date"
},
{
$toDate: "$inicial_date"
}
]
}
}
},
{
// convert the time difference to minutes
$project: {
timeDiffInMinutes: {
$divide: [
"$timeDiffInMilliseconds",
60000
]
}
}
},
{
// check if the number of minutes is greater than 5 mins or not
$project: {
timeDiffGreaterThan5Mins: {
$cond: [
{
$gt: [
"$timeDiffInMinutes",
5
]
},
1,
0
]
}
}
},
{
// group according to greater than 5 minutes or not
$group: {
_id: "null",
greater_than_5: {
$sum: {
$cond: [
{
$eq: [
"$timeDiffGreaterThan5Mins",
1
]
},
1,
0
]
}
},
less_than_5: {
$sum: {
$cond: [
{
$eq: [
"$timeDiffGreaterThan5Mins",
0
]
},
1,
0
]
}
}
}
}
])
Making it a bit more efficient,
db.collection.aggregate([
{
$match: {
name: "A"
}
},
{
$project: {
more_than_5_mins: {
$cond: [
{
$gt: [
{
$subtract: [
{
$toDate: "$end_date"
},
{
$toDate: "$inicial_date"
}
]
},
300000
]
},
1,
0
]
}
}
},
{
$group: {
_id: "",
less_than_5: {
$sum: {
$cond: [
{
$eq: [
"$more_than_5_mins",
0
]
},
1,
0
]
}
},
greater_than_5: {
$sum: {
$cond: [
{
$eq: [
"$more_than_5_mins",
1
]
},
1,
0
]
}
}
}
}
])