Below is my aggregation
db.customers.aggregate([{
$match: {
"CDF.UTILITYTYPE.D1.G1" : "12387835"
}
}, {
$project: {
_id:0,
"CDF.UTILITYTYPE.D1.G22.NAME":1,
"CDF.UTILITYTYPE.D1.G1":1,
"CDF.UTILITYTYPE.D5.EVENT": {
$filter: {
input: "$CDF.UTILITYTYPE.D5.EVENT",
as: "item",
cond: { $eq: [ "$$item.TIME", "12-04-2018 15:46:02" ] }
}
}
}
}
]).pretty();
i am comparing TIME field here i actually want to compare "06-2022" as a substring instead of "12-04-2018 15:46:02" this whole date and time format
You never store date/time values as string, it's a design flaw. Store always proper Date objects.
Once you corrected the data type, e.g. with
{
$set: {
TIME: {
$dateFromString: {
dateString: "$TIME",
format: "%d-%m-%Y %H:%M:%S"
}
}
}
}
you can filter by using for example
cond: {
$eq: [
{ $dateTrunc: { date: "$TIME" unit: "month" } },
ISODate("2022-06-01")
]
}
or supposingly
cond: {
$eq: [
{ $dateTrunc: { date: "$TIME" unit: "month" } },
{ $dateTrunc: { date: "$$NOW" unit: "month" } }
]
}
db.customers.aggregate([{
$match: {
"CDF.UTILITYTYPE.D1.G1" : "12387835"
}
}, {
$project: {
_id:0,
"CDF.UTILITYTYPE.D1.G22.NAME":1,
"CDF.UTILITYTYPE.D1.G1":1,
"CDF.UTILITYTYPE.D5.EVENT": {
$filter: {
input: "$CDF.UTILITYTYPE.D5.EVENT",
as: "item",
cond: { $regexMatch: { input:"$$item.TIME", regex: "05-2022"}}
}
}
}
}
]).pretty();
Related
I am using Mongo daily bucketing pattern. Each daily document contains an array with value calculated for every hour for that day:
{
meter: 'meterId',
date: 'dailyBucket',
hourlyConsumption: [0,0,1,1,1,2,2,2,4,4,4,4,3,3,3...] // array with 24 values for every hour of a day
}
Now in one of my aggregation queries, I would like to group documents for the same day of multiple meters and get a result like this:
INPUT (consumption of multiple meters in a same day)
{
meter: 'MeterA',
date: '2021-05-01',
hourlyConsumption: [0,0,1,1,1,2,2,2,4,4,4,4,3,3,3...]
},
{
meter: 'MeterB',
date: '2021-05-01',
hourlyConsumption: [10,10,10,10,10,10,10,10,10,10,10,10,10,10,10...]
}
RESULT (combined into single document)
{
date: '2021-05-01',
hourlyConsumption: [10,10,11,11,11,12,12,12,14,14,14,14,13,13,13...]
}
is there a way to achieve this without using $accumulator?
You can use $reduce
db.collection.aggregate([
{
$group: {
_id: "$date",
hourlyConsumption: { $push: "$hourlyConsumption" }
}
},
{
$set: {
hourlyConsumption: {
$reduce: {
input: "$hourlyConsumption",
initialValue: [],
in: { $map: { input: { $range: [ 0, 23 ] },
as: "h",
in: {
$sum: [
{ $arrayElemAt: [ "$$value", "$$h" ] },
{ $arrayElemAt: [ "$$this", "$$h" ] }
]
}
}
}
}
}
}
}
])
Mongo Playground
Or you use $unwind and $group:
db.collection.aggregate([
{
$unwind: {
path: "$hourlyConsumption",
includeArrayIndex: "hour"
}
},
{
$group: {
_id: {
date: "$date",
hour: "$hour"
},
hourlyConsumption: { $sum: "$hourlyConsumption" }
}
},
{ $sort: { "_id.hour": 1 } },
{
$group: {
_id: "$_id.date",
hourlyConsumption: { $push: "$hourlyConsumption" }
}
}
])
Mongo Playground
However, when you use $unwind, then you actually contradict your bucketing design pattern.
I have a mongoDB database with multiple time series data and each time stamp is a separate document with some additional meta data from sensors. I want to sum the two time series in an aggregation but I am heavily struggling with that and can't find any examples.
Assume we have sensor A and B and the time stamps from the different sensors don't align. See an example of the data below. Next I want to sum the "volume" metric of the two time series. So for the example below sensor A has two time stamps en sensor B 3. So the sum of A and B should have 5 time stamps such that the sum reflects all the changes in the total volume (see also the schematic example below).
Anyone knows how to solve this in a mongoDB aggregation query? I can only use the mongoDB query language and not use NodeJS.
Sensor A
{
"_id":5d67d9ee074e99274eef30d5
"sensor": A
"volume":12.4
"temperatue": 20
"timestamp":2019-08-29 15:58:06.093
"__v":0
}
{
"_id":5d67da66074e99274eef30ea
"sensor": A
"volume":12.3
"temperatue": 21
"timestamp":2019-08-29 16:48:06.078
"__v":0
}
..etc
Sensor B
{
"_id":5d67d9ee074e99274eef30d5
"sensor": B
"volume":32.4
"temperatue": 20
"timestamp":2019-08-29 15:55:06.093
"__v":0
}
{
"_id":5d67da66074e99274eef30ea
"sensor": B
"volume":21.2
"temperatue": 21
"timestamp":2019-08-29 16:49:06.178
"__v":0
}
{
"_id":5d67da66074e99274eef30ea
"sensor": B
"volume":22.3
"temperatue": 22
"timestamp":2019-08-29 17:04:06.078
"__v":0
}
..etc
Here also a sketch of the result I would like to have.
Try this one:
db.collection.aggregate([
// Determine start and end-time
{ $sort: { timestamp: -1 } },
{ $group: { _id: "$sensor", data: { $push: "$$ROOT" } } },
{
$set: {
data: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
$mergeObjects: [
"$$this",
{
timestamp_end: {
$ifNull: [ { $last: "$$value.timestamp" }, "$$NOW" ]
}
}
]
}
]
]
}
}
}
}
},
{ $unwind: "$data" },
// find data per interval
{ $sort: { "data.timestamp": 1 } },
{
$group: {
_id: null,
data: { $push: "$data" },
timestamp: { $addToSet: "$data.timestamp" }
}
},
{
$set: {
sum_data: {
$map: {
input: "$timestamp",
as: "t",
in: {
$filter: {
input: "$data",
cond: {
$and: [
{ $lte: [ "$$this.timestamp", "$$t" ] },
{ $gt: [ "$$this.timestamp_end", "$$t" ] }
]
}
}
}
}
}
}
},
// sum up temperatures
{
$set: {
volume: {
$map: {
input: "$sum_data",
in: { $sum: "$$this.volume" }
}
},
result: { $range: [ 0, { $size: "$timestamp" } ] }
}
},
// Join arrays to final result
{
$project: {
result: {
$map: {
input: "$result",
as: "i",
in: {
timestamp: { $arrayElemAt: [ "$timestamp", "$$i" ] },
volume: { $arrayElemAt: [ "$volume", "$$i" ] }
}
}
}
}
}
])
Mongo Playground
I have documents with the following props:
{
'published_date': '2020/03/10 07:20:09',
'relationships': [
{'rel_name': 'HAS_REL'},
{'rel_name': 'HAS_NO_REL'},
]
}
I want to add in each field of relationships that has as rel_name the value of HAS_REL the value of the published date as the property dict. The document will become as follows:
{
'published_date': '2020/03/10 07:20:09',
'relationships': [
{ 'rel_name': 'HAS_REL'
'date': 2020,03,10,07,20,09
},
{'rel_name': 'HAS_NO_REL'},
]
}
So far my query looks something like this:
TEST_COLLECTION.update_one(
{'_id': ObjectId(document_id)},
{'$set': {'relationships.$[elem].date': {'$dateFromString': '$published_date'}}},
False,
False,
None,
[{'elem.rel_name': 'HAS_RELATIONSHIP'}],
)
But I'm getting the error:
WriteError: The dollar ($) prefixed field '$dateFromString' in 'parsed.relationships.1.date.$dateFromString' is not valid for storage.
any ideas?
UPDATE
With the initial answer, I've updated the query such as follows:
TEST_COLLECTION.update_one(
{'_id': ObjectId(document_id)},
[
{'$set': {
'relationships': {
'$let': {
'vars': {
'date': { '$dateFromString': { 'dateString': '$published_date', format: "%Y/%m/%d %H:%M:%S" } }
},
'in': {
'$map': {
'input': "$relationships",
'in': {
'$cond': {
'if': { '$eq': ["$$this.rel_name", "HAS_REL"] },
'then': { '$mergeObjects': ["$$this", { 'date': "$$date" }] },
'else': "$$this"
}
}
}
}
}
}
}
}
]
)
However, it seems I'me not getting a correct document:
InvalidDocument: documents must have only string keys, key was
built-in function format
Have a look at $dateFromString
You have to specify format field, unless you use default format "%Y-%m-%dT%H:%M:%S.%LZ". Storing date/time values as string is usually a design flaw.
So, it must be
{'$dateFromString': { dateString: '$published_date', format: "%Y/%m/%d %H:%M:%S" } }
Note, the time is considered at UTC time. Set field timezone to specify the time zone if required.
Be aware, $dateFromString() is an aggregation function, so you must use
TEST_COLLECTION.update_one(
{ '_id': ObjectId(document_id) },
[
{
$set: {
relationships: {
$map: {
input: "$relationships",
in: {
$cond: {
if: { $eq: ["$$this.rel_name", "HAS_REL"] },
then: {
$mergeObjects: [
"$$this",
{
date: {
$dateFromString: {
dateString: '$published_date',
format: "%Y/%m/%d %H:%M:%S"
}
}
}
]
},
else: "$$this"
}
}
}
}
}
}
]
)
or another style:
TEST_COLLECTION.update_one(
{ '_id': ObjectId(document_id) },
[
{
$set: {
relationships: {
$let: {
vars: {
date: { $dateFromString: { dateString: '$published_date', format: "%Y/%m/%d %H:%M:%S" } }
},
in: {
$map: {
input: "$relationships",
in: {
$cond: {
if: { $eq: ["$$this.rel_name", "HAS_REL"] },
then: { $mergeObjects: ["$$this", { date: "$$date" }] },
else: "$$this"
}
}
}
}
}
}
}
}
]
)
I am the beginner of MongoDB
Here I mentioned below my one document
{
"_id" : ObjectId("5e5bc292361b710c7727718e"),
"branch_id" : "BR5cc825dac42dac3aae49ff91",
"inventory" : [
{
"inventory_stock_id" : "wewe123",
"stock_name" : "xxxxx",
"stock_point" : "27",
"stock_type" : "yyyy",
"batch" : [
{
"quantity" : 40,
"manf_date" : "10-01-2020",
"exp_date" : "01-04-2020"
}
]
}
]
}
I want to get last 30 days from "exp_date" but it should be equal to current date
Here I mentioned exp_date: "01-04-2020" and the past 30 days of date is today date( "02-03-2020")
db.collection.find({"inventory.batch.exp_date" : {"$lte":"01-04-2020","$eq":"02-03-2020"}})
I don't know how to get last 30 days of exp_date and equal to current date
so anyone help me to solve this issue.
Usually it is a bad approach to store/compare Date values as strings.
You can do it like this. First convert the strings to proper Date objects:
db.collection.updateMany(
{},
[
{
$set: {
inventory: {
$map: {
input: "$inventory",
as: "inventory",
in: {
$mergeObjects: [
"$$inventory",
{
batch: {
$map: {
input: "$$inventory.batch",
in: {
quantity: "$$this.quantity",
manf_date: { $dateFromString: { dateString: "$$this.manf_date", format: "%d-%m-%Y" } },
exp_date: { $dateFromString: { dateString: "$$this.exp_date", format: "%d-%m-%Y" } }
}
}
}
}
]
}
}
}
}
}
]
)
When you have to work with Date values, then I recommend the Moment.js library.
The query would be this one:
db.collection.find(
{
"inventory.batch": {
$elemMatch: {
exp_date: {
$eq: moment().utc().add(30, 'days').startOf('day').toDate()
}
}
}
}
)
or as aggregation:
db.collection.aggregate([
{
$match: {
"inventory.batch": {
$elemMatch: {
exp_date: {
$eq: moment().utc().add(30, 'days').startOf('day').toDate()
}
}
}
}
}
])
Note, by default $dateFromString uses UTC times, whereas moment() uses your local time by default. Thus you have to use either moment().utc() or you specify the timezone field at $dateFromString.
In case you insist to keep the string values as Date, you can also use
db.collection.find(
{
"inventory.batch": {
$elemMatch: {
exp_date: {
$eq: moment().add(30, 'days').startOf('day').format("DD-MM-YYYY")
}
}
}
}
)
However, this will fail if you query with $gte, $ge, $lt, $lte operators.
Update
If you have not access to moments then you can run in purely in the aggregation:
db.collection.aggregate([
{ $unwind: "$inventory" },
{ $set: { ts: { $dateToParts: { date: { $add: ["$$NOW", { $multiply: [1000, 60, 60, 24, 30] }] } } } } },
{
$set: {
ts: {
$dateFromParts: {
year: "$ts.year",
month: "$ts.month",
day: "$ts.day",
timezone: "UTC"
}
}
}
},
{ $set: { matches: { $in: ["$ts", "$inventory.batch.exp_date"] } } },
{
$group: {
_id: { _id: "$_id", branch_id: "$branch_id" },
inventory: { $push: "$$ROOT.inventory" },
matches: { $push: "$$ROOT.matches" }
}
},
{ $match: { $expr: { $anyElementTrue: "$matches" } } },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$$ROOT", "$_id"] } } },
{$unset: "matches"}
])
Or, if you like to write all on a single aggregation:
db.collection.aggregate([
{ $unwind: "$inventory" },
{
$set: {
"inventory.batch": {
$map: {
input: "$inventory.batch",
in: {
quantity: "$$this.quantity",
manf_date: { $dateFromString: { dateString: "$$this.manf_date", format: "%d-%m-%Y" } },
exp_date: { $dateFromString: { dateString: "$$this.exp_date", format: "%d-%m-%Y" } }
}
}
}
}
},
{ $set: { ts: { $dateToParts: { date: { $add: ["$$NOW", { $multiply: [1000, 60, 60, 24, 30] }] } } } } },
{
$set: {
ts: {
$dateFromParts: {
year: "$ts.year",
month: "$ts.month",
day: "$ts.day",
timezone: "UTC"
}
}
}
},
{ $set: { matches: { $in: ["$ts", "$inventory.batch.exp_date"] } } },
{
$group: {
_id: { _id: "$_id", branch_id: "$branch_id" },
inventory: { $push: "$$ROOT.inventory" },
matches: { $push: "$$ROOT.matches" }
}
},
{ $match: { $expr: { $anyElementTrue: "$matches" } } },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$$ROOT", "$_id"] } } },
{ $unset: "matches" }
])
i have following query i want to count totalwasteddays please help me this. where i wrong in total line number. i am new in mongodb please help me this i am very thankful
error in this line TotalWastedDays: { $add: $toInt:"$none",$toInt:"$fade",$toInt:"$Torn" },
db.getCollection('campaigns').aggregate([
{ $match: { _id: ObjectId("5b49d08db8695590d4ea7204") } },
{ $project: { doc: "$$ROOT" } },
{ $unwind: '$doc.assets' } ,
{ $project: {
assets: '$doc.assets',
none: { $filter : { input: '$doc.assets.outdoor_tracking', as: 'outdoor_tracking', cond: { $eq: ['$$outdoor_tracking.issue_type','None'] } } },
fade: { $filter : { input: '$doc.assets.outdoor_tracking', as: 'outdoor_tracking', cond: { $eq: ['$$outdoor_tracking.issue_type','Fade'] } } },
Torn: { $filter : { input: '$doc.assets.outdoor_tracking', as: 'outdoor_tracking', cond: { $eq: ['$$outdoor_tracking.issue_type','Torn'] } } },
}
},
{
$project: {
assets:1,
none: { $size: { "$ifNull": ["$none", []] } },
fade: { $size: { "$ifNull": ["$fade", []] } },
Torn: { $size: { "$ifNull": ["$Torn", []] } },
},
$project: {
assets:1,
none:1 ,
fade:1 ,
Torn:1 ,
TotalWastedDays: { $add: $toInt:"$none",$toInt:"$fade",$toInt:"$Torn" },
}
}
])
Modify
{ $add: $toInt:"$none",$toInt:"$fade",$toInt:"$Torn" }
To
{ $add: [{$toInt:"$none"},{$toInt:"$fade"},{$toInt:"$Torn"}] }