I have a collection called "project" which is having a field expected time and actual time both are in string format
{
"_id" : ObjectId("5ce7455d77af2d1143f84d49"),
"project_name" : "p1",
"expected" : "0:11:30",
"actual" : "7:30:00",
}
How can I compare two string format times using mongodb?
I want to find if actual time is more than expected
You can use $split with $toInt (MongoDB 4.0 or newer) to convert your string values to a number of seconds and then use $expr to compare both fields:
db.col.aggregate([
{
$addFields: {
expected: {
$let: {
vars: {
parts: {
$split: [ "$expected", ":" ]
}
},
in: {
$sum: [
{ $toInt: { $arrayElemAt: [ "$$parts", 2 ] } },
{ $multiply: [ 60, { $toInt: { $arrayElemAt: [ "$$parts", 1 ] } } ] },
{ $multiply: [ 3600, { $toInt: { $arrayElemAt: [ "$$parts", 0 ] } } ] }
]
}
}
},
actual: {
$let: {
vars: {
parts: {
$split: [ "$actual", ":" ]
}
},
in: {
$sum: [
{ $toInt: { $arrayElemAt: [ "$$parts", 2 ] } },
{ $multiply: [ 60, { $toInt: { $arrayElemAt: [ "$$parts", 1 ] } } ] },
{ $multiply: [ 3600, { $toInt: { $arrayElemAt: [ "$$parts", 0 ] } } ] }
]
}
}
}
}
},
{
$match: {
$expr: { $gt: [ "$expected", "$actual" ] }
}
}
])
You can convert time to any date you want using $dateFromString operator and then can easily use $lte $gte to perform simple match operations.
db.collection.find({
"$expr": {
"$gt": [
{ "$dateFromString": {
"dateString": {
"$concat": ["2018-09-19", "T", "$actual"]
}
}},
{ "$dateFromString": {
"dateString": {
"$concat": ["2018-09-19", "T", "$expected"]
}
}}
]
}
})
Related
I have an object like so
{
"_id": "62334a3c86f03ce0985f11a1",
"stops": [
{
"location": {
"baseLocation": "622e29bd0a56c69f81e0e6e9",
"extraLocation": "622e29bd0a56c69f81e0e6eb"
},
"timesData": [
{
"wType": "date"
}
],
},
{
"location": {
"baseLocation": "622e70a59975be022276178c",
"extraLocation": "622e70a59975be022276178e"
},
"timesData": [
{
"wType": "date"
}
],
}
},
],
}
I need to find the index of objects that have a baseLocation equal to a value and which have a wType of date and I do so using a $map like the one below
{
$map: {
input: "$stops",
in: {
$and: [
{
$eq: ["$$this.location.baseLocation", mongoose.Types.ObjectId(from.id)]
},
{
$eq: ["$$this.timesData.0.wType", "date"]
}
]
}
}
},
I get no matches, but if I do it like this
{
$map: {
input: "$stops",
in: {
$eq: ["$$this.location.baseLocation", mongoose.Types.ObjectId(from.id)]
}
}
},
It works, so I guess the problem is that I can't access "$$this.timesData.0.wType"
How about using $arrayElemAt:
$map: {
input: "$stops",
in: {
$and: [
{
$eq: [
"$$this.location.baseLocation",
mongoose.Types.ObjectId(from.id)]
]
},
{
$eq: [
{
"$arrayElemAt": [
"$$this.timesData",
0
]
},
{
"wType": "date"
}
]
}
]
}
}
}
}
You can check it on the playground: https://mongoplayground.net/p/p5eRv0pXYXM
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.
There are many documents in the collection which contains this field timeTaken: "00:10:00",
I want to sum up from all the documents and have to give a single integer in mongodb robo3T.
That is for the following documents:
[
{ timeTaken: "00:10:00" },
{ timeTaken: "01:10:00" },
{ timeTaken: "02:20:50" }
]
I want the result to be:
{ timeTaken: "03:40:50" }
Our strategy will be to split the string into minutes, seconds and hours, convert them to numbers, sum them up and then reconstruct the structure.
For this you will need access to operators like $toString and $toInt which means you can only do this for version 4.0+, for older Mongo versions you will have to read the documents and do this in code.
I've split the following query into multiple stages so it's clearer what I'm doing but this could be re-written into just 2 stages, the $group stage and a final $project stage to restructure the data.
db.collection.aggregate([
{
"$addFields": {
dataParts: {
$map: {
input: {
$split: [
"$data",
":"
]
},
as: "num",
in: {
"$toInt": "$$num"
}
}
},
}
},
{
$group: {
_id: null,
seconds: {
$sum: {
"$arrayElemAt": [
"$dataParts",
2
]
}
},
minutes: {
$sum: {
"$arrayElemAt": [
"$dataParts",
1
]
}
},
hours: {
$sum: {
"$arrayElemAt": [
"$dataParts",
0
]
}
},
}
},
{
"$addFields": {
finalSeconds: {
$mod: [
"$seconds",
60
]
},
}
},
{
$addFields: {
minutes: {
$sum: [
"$minutes",
{
"$divide": [
{
"$subtract": [
"$seconds",
"$finalSeconds"
]
},
60
]
}
]
},
}
},
{
$addFields: {
finalMinutes: {
$mod: [
"$minutes",
60
]
},
finalHours: {
$sum: [
"$hours",
{
$mod: [
{
$max: [
{
"$subtract": [
"$minutes",
60
]
},
0
]
},
60
]
}
]
}
}
},
{
$project: {
final: {
$concat: [
{
"$toString": "$finalHours"
},
":",
{
"$toString": "$finalMinutes"
},
":",
{
"$toString": "$finalSeconds"
},
]
}
}
}
])
Mongo Playground
my collection in mongo db like this:
{
name:"mehdi",
grades:
[
{
a:1,
b:[2,3,4],
c:3,
d:4,
e:5
},
{
a:11,
b:[22,33,44],
c:33,
d:44,
e:55
}
]
}
I want to get a result with project op to give me a specific field in an array like this:
{
name:"mehdi",
grades:
[
{
a:1,
b:2
},
{
a:11,
b:22
}
]
}
how can I do this?
You can use $map to select a,b fields using $type to determine whether it's an array or number:
db.collection.aggregate([
{
$project: {
grades: {
$map: {
input: "$grades",
in: {
a: { $cond: [ { $eq: [ { $type: "$$this.a" }, "array" ] }, { $arrayElemAt: [ "$$this.a", 0 ] }, "$$this.a" ] },
b: { $cond: [ { $eq: [ { $type: "$$this.b" }, "array" ] }, { $arrayElemAt: [ "$$this.b", 0 ] }, "$$this.b" ] },
}
}
}
}
}
])
Mongo Playground
I would like to concat int array field values inside an array of objects into one string field after dividing them (by 10).
Heres the existing document format:
{
"no" : "2020921008981",
"date" : ISODate("2020-04-01T05:19:02.263+0000"),
"sale" : {
"soldItems" : [
{
"itemRefId" : "5b55ac7f0550de00210a3b24",
"soldPrice" : NumberInt(800),
},
{
"itemRefId" : "5b55ac7f0550de00210a3b25",
"soldPrice" : NumberInt(1000),
}
]
}
}
Expected result :
{
"no" : "2020921008981",
"date" : ISODate("2020-04-01T05:19:02.263+0000"),
"priceList" : "8.0 \n 10.0"
}
The attempt with $reduce :
priceList: {
$reduce: {
input: "$sale.soldItems.soldPrice",
initialValue: "",
in: {
$cond: [ { "$eq": [ { $toString: { $divide: [ "$$value", 10 ] } }, "" ] }, "$$this", { $concat: [ { $toString: { $divide: [ "$$value", 10 ] } }, "\n", "$$this" ] } ]
}
}
}
But end up getting "errmsg" : "$divide only supports numeric types, not string and double" error. Any idea would be appreciated.
db.case.aggregate([
{
$set: {
priceList: {
$reduce: {
input: {
$map: {
input: "$sale.soldItems.soldPrice",
in: { $toString: { $divide: ["$$this", 10] } }
}
},
initialValue: "",
in: { $concat: ["$$value", "$$this", " \n "] }
}
}
}
},
{
$project: {
_id: 0,
no: 1,
date: 1,
priceList: 1
}
}
])
Try the following aggregation query, where the idea is to:
First divide the field soldPrice by 10 or required divisor using $divide
Convert it into string and concat using $toString and $concat
the appender \n gets appended after each reduce op,remove that from the end using $rtrim
create the new field using $addFields
Query:
db.collection.aggregate([
{
$addFields: {
"itemPriceList": {
$rtrim: {
input: {
$reduce: {
input: "$salesOrder.purchaseItems",
initialValue: "",
in: {
$concat: [
"$$value",
{
$toString: {
$divide: [
"$$this.soldPrice",
10
]
}
},
"\n"
]
}
}
},
chars: "\n"
}
}
}
}
]);
Result:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"caseNumber": "2020921008981",
"itemPriceList": "80\n100",
"salesOrder": {
"purchaseItems": [
{
"itemRefId": "5b55ac7f0550de00210a3b24",
"soldPrice": 800
},
{
"itemRefId": "5b55ac7f0550de00210a3b25",
"soldPrice": 1000
}
]
},
"startTime": ISODate("2016-05-18T16:00:00Z")
}
]
Plaground Test Link