Mongodb find the Consecutive Failure Count - mongodb

I have a dataset like this:
{
ip: 1.1.1.1,
process: 123,
type: failure,
date: 2021-04-01
},
{
ip: 1.1.1.2,
process: 124,
type: failure,
date: 2021-03-01
},
{
ip: 1.1.1.1,
process: 123,
type: failure,
date: 2021-02-01
},
{
ip: 1.1.1.1,
process: 123,
type: success,
date: 2021-01-01
}
How can I get the consecutive failure count for a given ip address and process? For example, given the dataset above, if I was to check how many times ip 1.1.1.1 has failed for process 123 before the last success I should get 2. However if the success record was the last record then I should get 0.
What I have so far is:
activityLog.find([
{
$match: {
ip: "1.1.1.1",
process: "123"
},
},
{
$sort: {
date: -1,
},
},
{
$limit: 10,
},
{
$project: {
_id: 0,
type: 1,
},
},
]);
This gives me a list of all types - sorted and matched

You can do the followings in an aggregation pipeline:
find the previous success record
do a self-lookup to find the fail records after the previous success record
do the count of the lookup of step 2
db.collection.aggregate([
{
$match: {
ip: "1.1.1.1",
process: "123",
type: "success"
}
},
{
"$sort": {
date: -1
}
},
{
$limit: 1
},
{
"$lookup": {
"from": "collection",
let: {
ip: "$ip",
process: "$process",
date: "$date"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$ip",
"$$ip"
]
},
{
$eq: [
"$process",
"$$process"
]
},
{
$lt: [
"$date",
"$$date"
]
},
{
$eq: [
"$type",
"success"
]
}
]
}
}
},
{
$sort: {
date: -1
}
},
{
$limit: 1
}
],
"as": "lastSuccess"
}
},
{
"$unwind": {
path: "$lastSuccess",
preserveNullAndEmptyArrays: true
}
},
{
"$lookup": {
"from": "collection",
let: {
ip: "$ip",
process: "$process",
date: {
$ifNull: [
"$lastSuccess.date",
ISODate("9999-12-31")
]
}
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$ip",
"$$ip"
]
},
{
$eq: [
"$process",
"$$process"
]
},
{
$gte: [
"$date",
"$$date"
]
},
{
$eq: [
"$type",
"failure"
]
}
]
}
}
}
],
"as": "previousFailures"
}
},
{
"$addFields": {
"lastFailuresCount": {
$size: "$previousFailures"
}
}
}
])
Here is the Mongo playground for your reference

Related

Merge array data and add some additional field by performing multiplication on field

I have below kind of schema
Mongo playground
Problem - I want to get all succeeded transaction in list with their respective user with some extra field like reward - for that particular transaction. like if paid amount in 10 then reward will be 0.3 times -> 3. But i need 0.3 in case of 1st successful payment only for others it will be 0.1.
tried: I have achieved partial output, not able to get reward calculation based on first transaction
The output will be -
[
{
_id: 1,
name: 'Stephen',
transactions: [
{
_id: 1,
paidAmount: 10,
reward: 3
},
{
_id: 3,
paidAmount: 20,
reward: 2
}
]
},
{
_id: 2,
user: 'Peter',
transactions: [
{
_id: 2,
paidAmount: 5,
reward: 0.15
}]
}
]
Hi i have found the solution for above
db.users.aggregate([
{
$match: {
_id: 1,
},
},
{
$lookup: {
from: "payments",
localField: "_id",
foreignField: "user",
as: "payments",
},
},
{
$addFields: {
transactions: {
$reduce: {
input: "$payments",
initialValue: [],
in: {
$concatArrays: [
"$$value",
{
$filter: {
input: "$$this.transactions",
cond: {
$eq: [
"$$this.status",
"succeeded"
]
},
},
},
],
},
},
},
},
},
{
$project: {
transactions: {
$map: {
input: "$transactions",
as: "t",
in: {
paidAmount: "$$t.paidAmount",
status: "$$t.status",
createdAt: "$$t.createdAt",
reward: {
$cond: {
if: {
$eq: [
{
$indexOfArray: [
"$transactions",
"$$t"
]
},
0
]
},
then: {
$multiply: [
"$$t.paidAmount",
0.3
]
},
else: {
$multiply: [
"$$t.paidAmount",
0.1
]
},
},
},
},
},
},
},
},
{
$project: {
id: {
$toString: "$_id"
},
totalAMount: {
$sum: "$transactions.paidAmount"
},
totalReward: {
$sum: "$transactions.reward"
},
transactions: "$transactions",
},
},
])
Please comment if you find out a better solution/ approach.
Try this

MongoDB compare endTime with startTime of next document

I have a similar collection where I have sort them by their startTime:
{"name": 'A', "startTime": '1634626355', "endTime": '1634631405'}
{"name": 'A', "startTime": '1634631406', "endTime": '1634631864'}
{"name": 'A', "startTime": '1634631865', "endTime": '1634656048'}
{"name": 'A', "startTime": '1634712642', "endTime": '1634718856'}
How can I compare the documents such that if the document endTime and the next document startTime duration is less than 5 minutes, merge it.
This is the result I'm trying to achieve (The 1st 3 documents are merged into 1 where it uses the startTime of the 1st document and the endTime of the 3rd document):
{"name": 'A', "startTime": '1634626355', "endTime": '1634656048'}
{"name": 'A', "startTime": '1634712642', "endTime": '1634718856'}
Thanks
First of all, you should never store date/time values as string, it's a design flaw. Store always proper Date object.
This solution works without self-lookup, so it may perform better:
db.collection.aggregate([
{
$set: {
startDateTime: { $toDate: { $multiply: ["$startTime", 1000] } },
endDateTime: { $toDate: { $multiply: ["$endTime", 1000] } }
},
},
{ $sort: { startDateTime: 1 } },
{ $group: { _id: null, data: { $push: "$$ROOT" } } },
{
$set: {
data: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$cond: {
if: {
$or: [
{ $eq: [{ $size: "$$value" }, 0] }, // for the initail element
{
$gt: [
{
$dateDiff: { // calculate difference
endDate: "$$this.startDateTime",
startDate: { $last: "$$value.endDateTime" },
unit: "minute"
}
},
5 // more than 5 Minutes
]
}
]
},
then: { $concatArrays: ["$$value", ["$$this"]] }, // append new element
else: {
$map: {
input: "$$value",
as: "data",
in: {
$cond: {
if: { $eq: ["$$data._id", { $last: "$$value._id" }] }, // find last element
then: { // update last element
$mergeObjects: [
"$$data",
{ endDateTime: "$$this.endDateTime" },
{ endTime: "$$this.endTime" }
]
},
else: "$$data"
}
}
}
}
}
}
}
}
}
},
// some cosmetic
{ $unwind: "$data" },
{ $replaceRoot: { newRoot: "$data" } }
])
Mongo Playground
You can use $lookup in an aggregation pipeline to find out the documents that you need to remove. Then, perform a forEach to remove them.
db.collection.aggregate([
{
$addFields: {
endDateTime: {
"$toDate": {
"$multiply": [
{
$toLong: "$endTime"
},
1000
]
}
}
},
},
{
"$lookup": {
"from": "collection",
let: {
end: "$endDateTime"
},
pipeline: [
{
"$addFields": {
startDateTime: {
"$toDate": {
"$multiply": [
{
$toLong: "$startTime"
},
1000
]
}
}
}
},
{
$match: {
$expr: {
$and: [
{
$lte: [
{
$subtract: [
"$startDateTime",
"$$end"
]
},
300000
]
},
{
$lte: [
"$$end",
"$startDateTime"
]
}
]
}
}
}
],
"as": "lessThan5min"
}
},
{
"$unwind": "$lessThan5min"
},
{
"$replaceRoot": {
"newRoot": "$lessThan5min"
}
}
]).forEach(function(doc){
db.collection.remove({ "_id": doc._id });
});
Here is the Mongo playground to find out the documents that you need to remove for your reference.

MongoDb Create Aggregate Create query

I have 3 table users,shifts,temporaryShifts,
shifts:[{_id:ObjectId(2222),name:"Morning"},{_id:ObjectId(454),name:"Night"}]
users:[{_id:ObjectId(123),name:"Albert",shift_id:ObjectId(2222)}]
temporaryShifts:[
{_id:2,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-01"},
{_id:987,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-03"},
{_id:945,userId:ObjectId(123),shiftId:ObjectId(454),type:"temporary",date:"2020-02-08"},
{_id:23,userId:ObjectId(123),shiftId:ObjectId(454),date:"2020-02-09"}]
i want to make a mongoose aggregate query then give me result :
get result between two dates for example :2020-02-01 2020-02-05,
resullts is :
[
{_id:ObjectId(123),name:"Albert",shift:[
{_id:2,shiftId:ObjectId(454),type:"temporary",date:"2020-02-01"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-02"},
{_id:2,shiftId:ObjectId(454),type:"temporary",date:"2020-02-03"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-04"},
{_id:2,shiftId:ObjectId(2222),type:"permanent",date:"2020-02-05"},
]}
]
in result type temporary mean selected date in table temporaryShift document available else type permanent
MongoPlayGround You Can edit
You can first project a date range array using $range, in your example it will be like [2020-02-01, 2020-02-02, 2020-02-03, 2020-02-04, 2020-02-05], then you can use the array to perform $lookup
db.users.aggregate([
{
$limit: 1
},
{
"$addFields": {
"startDate": ISODate("2020-02-01"),
"endDate": ISODate("2020-02-05")
}
},
{
"$addFields": {
"dateRange": {
"$range": [
0,
{
$add: [
{
$divide: [
{
$subtract: [
"$endDate",
"$startDate"
]
},
86400000
]
},
1
]
}
]
}
}
},
{
"$addFields": {
"dateRange": {
$map: {
input: "$dateRange",
as: "increment",
in: {
"$add": [
"$startDate",
{
"$multiply": [
"$$increment",
86400000
]
}
]
}
}
}
}
},
{
"$unwind": "$dateRange"
},
{
"$project": {
"name": 1,
"shiftId": 1,
"dateCursor": "$dateRange"
}
},
{
"$lookup": {
"from": "temporaryShifts",
"let": {
dateCursor: "$dateCursor",
shiftId: "$shiftId"
},
"pipeline": [
{
"$addFields": {
"parsedDate": {
"$dateFromString": {
"dateString": "$date",
"format": "%Y-%m-%d"
}
}
}
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$$dateCursor",
"$parsedDate"
]
}
]
}
}
}
],
"as": "temporaryShiftsLookup"
}
},
{
"$unwind": {
path: "$temporaryShiftsLookup",
preserveNullAndEmptyArrays: true
}
},
{
$project: {
shiftId: 1,
type: {
"$ifNull": [
"$temporaryShiftsLookup.type",
"permanent"
]
},
date: "$dateCursor"
}
}
])
Here is the Mongo Playground for your reference.

Returning a document with two fields from the same array in MongoDB

Given documents such as
{
_id: 'abcd',
userId: '12345',
activities: [
{ status: 'login', timestamp: '10000001' },
{ status: 'logout', timestamp: '10000002' },
{ status: 'login', timestamp: '10000003' },
{ status: 'logout', timestamp: '10000004' },
]
}
I am trying to create a pipeline such as all users that have their latest login/logout activities recorded between two timestamps will be returned. For example, if the two timestamp values are between 10000002 and 10000003, the expected document should be
{
_id: 'abcd',
userId: '12345',
login: '10000003',
logout: '10000002'
}
Of if the two timestamp values are between -1 and 10000001, the expected document should be :
{
_id: 'abcd',
userId: '12345',
login: '10000001',
logout: null
}
Etc.
I know it has to do with aggregations, and I need to $unwind, etc., but I'm not sure about the rest, namely evaluating two fields from the same document array
You can try below aggregation:
db.col.aggregate([
{
$unwind: "$activities"
},
{
$match: {
$and: [
{ "activities.timestamp": { $gte: "10000001" } },
{ "activities.timestamp": { $lte: "10000002" } }
]
}
},
{
$sort: {
"activities.timestamp": -1
}
},
{
$group: {
_id: "$_id",
userId: { $first: "$userId" },
activities: { $push: "$activities" }
}
},
{
$addFields: {
login: { $arrayElemAt: [ { $filter: { input: "$activities", as: "a", cond: { $eq: [ "$$a.status", "login" ] } } } , 0 ] },
logout: { $arrayElemAt: [ { $filter: { input: "$activities", as: "a", cond: { $eq: [ "$$a.status", "logout" ] } } } , 0 ] }
}
},
{
$project: {
_id: 1,
userId: 1,
login: { $ifNull: [ "$login.timestamp", null ] },
logout: { $ifNull: [ "$logout.timestamp", null ] }
}
}
])
We need to use $unwind + $sort + $group to make sure that our activities will be sorted by timestamp. After $unwind you can use $match to apply filtering condition. Then you can use $filter with $arrayElemAt to get first (latest) value of filtered array. In the last $project you can explicitly use $ifNull (otherwise JSON key will be skipped if there's no value)
You can use below aggregation
Instead of $unwind use $lte and $gte with the $fitler aggregation.
db.collection.aggregate([
{ "$project": {
"userId": 1,
"login": {
"$max": {
"$filter": {
"input": "$activities",
"cond": {
"$and": [
{ "$gte": ["$$this.timestamp", "10000001"] },
{ "$lte": ["$$this.timestamp", "10000004"] },
{ "$lte": ["$$this.status", "login"] }
]
}
}
}
},
"logout": {
"$max": {
"$filter": {
"input": "$activities",
"cond": {
"$and": [
{ "$gte": ["$$this.timestamp", "10000001"] },
{ "$lte": ["$$this.timestamp", "10000004"] },
{ "$lte": ["$$this.status", "logout"] }
]
}
}
}
}
}}
])

Using document field as JS object field-name in aggregation pipeline

I have an JS object called tasksCountMap:
const tasksCountMap = {
'Freshman': 46,
'Senior': 10
}
and I need get count of task for each user type in my aggregation pipe, 'Freshman', 'Senior' it's document field called gradeLevel. I'm trying do it like this:
status: {
$let: {
vars: {
tasksCount: tasksCountMap['$gradeLevel'],
completedTasksCount: '$completedTasksCount[0].count'
},
in: {
$cond: {
if: { $or: [
{ $eq: ['$$tasksCount', '$$completedTasksCount'] },
{ $lte: ['$$tasksCount', '$$completedTasksCount'] },
]},
then: 'On track',
else: 'High priority'
}
}
}
}
Also '$completedTasksCount[0].count' doesn't work to...
Can someone show right way to do this?
All pipeline:
{
$match: {
type: 'student',
counselorUserName: username
},
$project: {
username: '$username',
email: '$email',
phone: '$phone',
fullName: '$fullName',
gradeLevel: {
$switch: {
branches: [{
case: {
$eq: ['$gradeLevel', '9']
},
then: 'Freshman'
},
{
case: {
$eq: ['$gradeLevel', '10']
},
then: 'Sophomore'
},
{
case: {
$eq: ['$gradeLevel', '11']
},
then: 'Junior'
},
{
case: {
$eq: ['$gradeLevel', '12']
},
then: 'Senior'
}
],
default: "Freshman"
}
}
},
$lookup: {
from: 'RoadmapTasksCompleted',
let: {
username: '$username',
gradeLevel: '$gradeLevel'
},
pipeline: [{
$match: {
monthToComplete: {
$in: prevMonthsNames
},
$expr: {
$and: [{
$eq: ['$username', '$$username']
},
{
$eq: ['$gradeLevel', '$$gradeLevel']
}
]
}
}
},
{
$count: 'count'
}
],
as: 'completedTasksCount'
},
$project: {
username: '$username',
email: '$email',
phone: '$phone',
fullName: '$fullName',
completedTask: { $arrayElemAt: ['$completedTasksCount', 0] },
status: {
$let: {
vars: {
tasksCount: tasksCountMap['$gradeLevel'],
completedTasksCount: '$completedTasksCount[0].count'
},
in: {
$cond: {
if: { $or: [
{ $eq: ['$$tasksCount', '$$completedTasksCount'] },
{ $lte: ['$$tasksCount', '$$completedTasksCount'] },
]},
then: '$$tasksCount',
else:'$$tasksCount'
}
}
}
}
}
$limit: 10,
$skip: 10,
}
You have to move the js map into the aggregation pipeline for you to be able to access the map.
I split $project stage into two and took the liberty to clean up the status calculation.
Something like
{"$project":{
"username":"$username",
"email":"$email",
"phone":"$phone",
"fullName":"$fullName",
"completedTask":{"$arrayElemAt":["$completedTasksCount",0]},
"tasksCountMap":[{"k":"Freshman","v":46},{"k":"Senior","v":10}]
}},
{"$addFields":{
"status":{
"$let":{
"vars":{
"tasksCount":{
"$arrayElemAt":[
"$tasksCountMap.v",
{"$indexOfArray":["$tasksCountMap.k","$gradeLevel"]}
]
},
"completedTasksCount":"$completedTask.count"
},
"in":{
"$cond":{
"if":{"$lte":["$$tasksCount","$$completedTasksCount"]},
"then":"$$tasksCount",
"else":"$$completedTasksCount"
}
}
}
}
}}