Ok, here's my data:
"stats" : [
{
"campaign_id" : "some_id",
"log_id" : "some_id",
"agent" : "some_id",
"office" : "some_id",
"hq" : "some_name",
"seller" : "some_name",
"status" : "live",
"phases" : [
{
"phase" : "main_phase",
"banners" : [
{
"banner_id" : "some_id_same_as_below",
"split_var" : "light",
"reports" : [
{
"date" : "2016-11-25",
"banner" : "some_id_same_as_above",
"cost" : "0.231",
"impressions" : 14,
"clicks" : 0
},
...
And I need to query the database for all reports:
"reports" : [
{
"date" : "2016-11-25",
"banner" : "some_id_same_as_above",
"cost" : "0.231",
"impressions" : 14,
"clicks" : 0
},
For the "date" : "2016-11-25" within a date range. For the date range I have this:
start_month = DateTime.current.beginning_of_month - 1.month
end_month = DateTime.current.end_of_month - 1.month
Which gives me start and end of the previous month, which is right. How can I search for all documents that have reports (the nested values inside stats, phases, etc) that falls within this range?
Any ideas?
EDIT
It has been suggested to change the way the data is inserted into the db, but unfortunately I have no control on how the data is inserted (done by a third party service/API).
You can store the dates as standard Date objects instead of formatted strings, which MongoDB stores in ISODate format, for instance:
db.collection.insert({date: new Date()});
will have a field like:
{ "date" : ISODate("2016-11-15T15:50:15.167Z") }
Then you can query by date range (Use the $and operator if you need to query between two ranges or the second statement may override the first)
Such as:
// Return all documents in collection with a date between 11-1-2016 and 12-1-2016
db.collection.find({
$and: [
{ date: { $gte: ISODate("2016-11-01T00:00:00.000Z") } },
{ date: { $lt: ISODate("2016-12-01T00:00:00.000Z") } }
]
})
EDIT: In case you cannot modify your collections, you could do a regex style search...
For instance:
db.collection.find({
"stats.phases.banners.reports.date": /2016-11/
});
will return all documents for November 2016 since it matches on all strings containing "2016-11"
EDIT AGAIN:
Here is a solution using the aggregation framework to return the documents in the format you mentioned above i.e.
{
"reports" : {
"date" : "2016-11-23",
"banner" : ObjectId("58404a9450b5412e92ebbb97"),
"cost" : "0.231",
"impressions" : 14,
"clicks" : 0
}
},
{
"reports" : {
"date" : "2016-11-25",
"banner" : ObjectId("58404a9450b5412e92ebbb97"),
"cost" : "0.231",
"impressions" : 14,
"clicks" : 0
}
}
Note you have to do a lot of unwinds due to your heavily nested array structure...
db.collection.aggregate([
{ $unwind: "$stats" },
{ $unwind: "$stats.phases" },
{ $unwind: "$stats.phases.banners" },
{ $unwind: "$stats.phases.banners.reports" },
{ $match: { "stats.phases.banners.reports.date": /2016-11/ } },
{ $project: { _id: 0, reports: "$stats.phases.banners.reports" } }
])
You need to store date fields in as ISODate objects and then you can use comparison operators like $lt, $lte, $gt, $gte, etc.
Here is how to insert data:
db.test.insert({
"stats": [
{
"campaign_id": "some_id",
"log_id": "some_id",
"agent": "some_id",
"office": "some_id",
"hq": "some_name",
"seller": "some_name",
"status": "live",
"phases": [
{
"phase": "main_phase",
"banners": [
{
"banner_id": "some_id_same_as_below",
"split_var": "light",
"reports": [
{
"date": ISODate("2016-11-25T00:00:00.0Z"),
"banner": "some_id_same_as_above",
"cost": "0.231",
"impressions": 14,
"clicks": 0
}
]
}
]
}
]
}
]
})
Following is the query to find documents whose stats.phases.banners.reports.date is between 25 Nov 2016 to 15 Dec 2016.
db.test.find({"stats.phases.banners.reports.date": {$lt: ISODate("2016-11-25T00:00:00.0Z"), $gt: ISODate("2016-12-25T00:00:00.0Z")}})
Related
I'm being challenged by the $group $max in an aggregation with MongoDB on Nodes Express app. Here is the a sample of the collection;
{"_id":"5b7e78cf022be03c35776bec",
"humidity":60,
"pressure":1014.18,
"temperature":26.8,
"light":2464,
"timestampiso":"2018-08-23T09:05:19.112Z",
"timestamp":1535015119112
},
{
"_id":"5b7e7892022be03c35776bea",
"humidity":60.4,
"pressure":1014.14,
"temperature":26.7,
"light":2422,
"timestampiso":"2018-08-23T09:04:18.115Z",
"timestamp":1535015058115
},
{
"_id":"5b7e7855022be03c35776be8",
"humidity":60.6,
"pressure":1014.2,
"temperature":26.6,
"light":2409,
"timestampiso":"2018-08-23T09:03:17.113Z",
"timestamp":1535014997113
}]
What I'm trying to do is to query the collection, by first retrieving the entries of the last hour based on the timestamp and then looking for highest pressure of the sample (should be 60 entries as there is one entry per minute)
What I can de is find this value. What I'm stuggling on to have the timestamp related to that max value.
When I run
db.collection("ArduinoSensorMkr1000").
aggregate([{ "$match" : {"timestamp" : {"$gte" : (Date.now()-60*60*1000)}}},
{ "$group" : {"_id" : null, maxpressure : {"$max" : "$pressure"}
}
},
{
"$project" : { "_id" : 0 }
}
])
Fine, the output is correct and I get the maxpressure as so
[{"maxpressure":1014.87}]
but what I'm trying to output is the maxpressure field but with it, its corresponding timestamp. The output should look as so
[{"maxpressure":1014.87,"timestamp":1535015058115}]
Any hints on how I get this timestamp value to show?
Thank you for your support
You can try this first need to sort your data using $sort and you can pick max value by using $first
QUERY
db.col.aggregate([
{ "$match": { "timestamp": { "$gte": (Date.now() - 60 * 60 * 1000) } } },
{ "$sort": { "pressure": -1 } },
{
"$group": {
"_id": null, "maxpressure": { "$first": "$pressure" },
"timestamp": { "$first": "$timestamp" }
}
},
{
"$project": { "_id": 0 }
}
])
DATA
[{
"_id" : "5b7e78cf022be03c35776bec",
"humidity" : 60.0,
"pressure" : 1014.18,
"temperature" : 26.8,
"light" : 2464.0,
"timestampiso" : "2018-08-23T09:05:19.112Z",
"timestamp" : 1535015119112.0
},
{
"_id" : "5b7e7892022be03c35776bea",
"humidity" : 60.4,
"pressure" : 1014.14,
"temperature" : 26.7,
"light" : 2422.0,
"timestampiso" : "2018-08-23T09:04:18.115Z",
"timestamp" : 1535015058115.0
},
{
"_id" : "5b7e7855022be03c35776be8",
"humidity" : 60.6,
"pressure" : 1014.2,
"temperature" : 26.6,
"light" : 2409.0,
"timestampiso" : "2018-08-23T09:03:17.113Z",
"timestamp" : 1535014997113.0
}]
THE OUTPUT
{
"maxpressure" : 1014.87,
"timestamp" : 1535015058115.0
}
My suggestion is to use sort/limit instead of grouping. By this way you can get entire document before project only interesting fields :
db['ArduinoSensorMkr1000'].aggregate(
[{ "$match" : {"timestamp" : {"$gte" : (Date.now()-5*60*60*1000)}}},
{$sort:{pressure:-1}},
{$limit:1},{
"$project" : { "_id" : 0,"timestamp":1,"pressure":1 }}
])
I have the data below. I want to run a query to group my results by category and month and return a total.
The first desired output is a nested array of month names with aggregated totals for all 12 months by category. Months that are not present in the data will still be returned but have 0 as the total.
{"category":"Auto","month":{"Jan":9.12,"Feb":9.12,"Mar":0,...}},
{"category":"Fees","month":{..."Apr":0,"May":4.56,"Jun":0,...}},
{"category":"Travel","month":{..."Oct":0,"Nov":4.56,"Dec":0}}
The second desired output is an array that doesn't have nested months...
{"category":"Auto","Jan":4.56,"Feb":4.56,"Mar":0,...},
{"category":"Fees",..."Apr":0,"May":4.56,"Jun":0,...},
{"category":"Travel",..."Oct":0,"Nov":0,"Dec":4.56,}
How can these results be queried with Mongodb? Here is the sample input data:
{
"_id" : ObjectId("583f6e6d14c8042dd7c153f1"),
"transid" : 1,
"category": "Auto",
"postdate" : ISODate("2016-01-28T05:00:00.000Z"),
"total" : 4.56 }
{
"_id" : ObjectId("583f6e6d14c8042dd7c153f2"),
"transid" : 5,
"category": "Auto",
"postdate" : ISODate("2016-01-31T05:00:00.000Z"),
"total" : 4.56 }
{
"_id" : ObjectId("583f6e6d14c8042dd7c153f3"),
"transid" : 3,
"category": "Auto",
"postdate" : ISODate("2016-02-28T05:00:00.000Z"),
"total" : 4.56 }
{
"_id" : ObjectId("583f6e6d14c8042dd7c153f4"),
"transid" : 2,
"category": "Auto",
"postdate" : ISODate("2016-02-31T05:00:00.000Z"),
"total" : 4.56 }
{
"_id" : ObjectId("583f6e6d14c8042dd7c153f5"),
"transid" : 6,
"category": "Fees",
"postdate" : ISODate("2016-05-16T05:00:00.000Z"),
"total" : 4.56 }
{
"_id" : ObjectId("583f6e6d14c8042dd7c153f6"),
"transid" : 7,
"category": "Travel",
"postdate" : ISODate("2016-11-13T05:00:00.000Z"),
"total" : 4.56 }
I'm new to mongodb and come from a sql background so I feel I've been thinking about all this in sql terms.
Below is what I've tried so far based on reading through the mongodb documentation and attempting to translate "sql think". I'm essentially trying to filter to a specified year (in this case 2016). I'm then grouping by category and date. And in the last step I plan to use project and the $cond keyword to "subaggregate" on month by specifying the start and end dates of each month and then assign the month name as Jan, Feb, etc... I have syntax errors and I don't know if this is the right or best approach.
db.transactions.aggregate(
[
{ $match: { "postdate": {$gte: new Date("2016-01-01")}} },
{ $group: { _id: {"category":"$category","postdate":"$postdate"} , "total": { $sum: "$debit" } } },
{ $project: {"_id":0,"category":"$_id.category",
"month":{$cond: {
$and:
[
{ $gte: ["$_id.postdate", new Date("2016-01-01")] },
{ $lt: ["$_id.postdate", new Date("2016-02-01")] },
]
},"Jan":"$sum"}
//repeat for all other 11 months...
}}
]
)
If you want to group by month you can use month operator. eg:
db.transaction.aggregate([{$group:{_id:{ $month:"$postdate"}, "total":{$sum:1}}}])
I am not sure what project is doing for you.
In a database in MongoDB I am trying to group some data by their date (one group for each day of the year), and then add an additional field that would be the result of the multiplication of two of the already existing fields.
The data structure is:
{
"_id" : ObjectId("567a7c6d9da4bc18967a3947"),
"units" : 3.0,
"price" : 50.0,
"name" : "Name goes here",
"datetime" : ISODate("2015-12-23T10:50:21.560+0000")
}
I first tried a two stage approach using $project and then $group like this
db.things.aggregate(
[
{
$project: {
"_id" : 1,
"name" : 1,
"units" : 1,
"price" : 1,
"datetime":1,
"unitsprice" : { $multiply: [ "$price", "$units" ] }
}
},
{
$group: {
"_id" : {
"day" : {
"$dayOfMonth" : "$datetime"
},
"month" : {
"$month" : "$datetime"
},
"year" : {
"$year" : "$datetime"
}
},
"things" : {
"$push" : "$$ROOT"
}
}
}
],
)
in this case, the first step (the $project) gives the expected output (with the expected value of unitsprice), but then when doing the second $group step, it outputs this error:
"errmsg":$multiply only supports numeric types, not String",
"code":16555
I tried also turning around things, doing the $group step first and then the $project
db.things.aggregate(
[
{
$group: {
"_id" : {
"day" : {
"$dayOfMonth" : "$datetime"
},
"month" : {
"$month" : "$datetime"
},
"year" : {
"$year" : "$datetime"
}
},
"things" : {
"$push" : "$$ROOT"
}
}
},
{
$project: {
"_id" : 1,
"things":{
"name" : 1,
"units" : 1,
"price" : 1,
"datetime":1,
"unitsprice" : { $multiply: [ "$price", "$units" ] }
}
}
}
],
);
But in this case, the result of the multiplication is: unitsprice:null
Is there any way of doing this multiplication? Also, it would be nice to do it in a way that the output would not have nested fields, so it would look like:
{"_id":
"units":
"price":
"name":
"datetime":
"unitsprice":
}
Thanks in advance
PS:I am running MongoDB 3.2
Finally found the error. When importing one of the fields, a few of the price fields were created as a string. Surprisingly, the error didn't came out when first doing the multiplication in the project step (the output was normal until it reached the first wrong field, then it stopped), but when doing the group step.
In order to find the text fields I used this query:
db.things.find( { price: { $type: 2 } } );
Thanks for the hints
I'm learning how to use MeteorJS and I have a record that looks like:
meteor:PRIMARY> db.meals.find()
{ "_id" : "kHjRCXRRoC6JLYjJY", "name" : "Spaghetti & Meatballs", "calories" : "300", "eatenAt" : ISODate("2015-05-20T07:07:00Z"), "userId" : "movpJRhRMwyMZDBqf", "author" : "sergiotapia" }
{ "_id" : "vcQZ2S4MXHs49BknJ", "name" : "Lasgagna", "calories" : "150", "eatenAt" : ISODate("2015-05-20T07:07:00Z"), "userId" : "movpJRhRMwyMZDBqf", "author" : "sergiotapia" }
{ "_id" : "oqw4HZ5tybBKfMJmj", "name" : "test", "calories" : "900", "eatenAt" : ISODate("2015-05-20T07:38:00Z"), "userId" : "movpJRhRMwyMZDBqf", "author" : "sergiotapia" }
{ "_id" : "Pq6vawvTnXQniBvMZ", "name" : "booya", "calories" : "1000", "eatenAt" : ISODate("2015-05-19T07:37:00Z"), "userId" : "movpJRhRMwyMZDBqf", "author" : "sergiotapia" }
I want to filter these records using the ISODate value by both date and time. For example, get me the records from January 1st to January 12nd that are between 9am and 2pm.
Is it possible using a single field, or do I need to have a separate field specifically for time?
Your query is basically:
find documents that are between 2015-01-01 AND 2015-01-12 AND have time between 09:00 AND 14:00.
One approach is using the aggregation framework in particular the Date Aggregation Operators. You can use the meteorhacks:aggregate package that adds proper aggregation support for Meteor. This package exposes .aggregate method on Mongo.Collection instances.
Add to your app with
meteor add meteorhacks:aggregate
Then simply use .aggregate function like below.
var meals = new Mongo.Collection('meals');
var pipeline = [
{
"$project": {
"year": { "$year": "$eatenAt" },
"month": { "$month": "$eatenAt" },
"day": { "$dayOfMonth": "$eatenAt" },
"hour": { "$hour": "$eatenAt" },
"name" : 1,
"calories" : 1,
"eatenAt" : 1,
"userId" : 1,
"author" : 1
}
},
{
"$match": {
"year": 2015,
"month": 1,
"day": { "$gte": 1, "$lte": 12 },
"hour": { "$gt": 8, "$lt": 14 }
}
}
];
var result = meals.aggregate(pipeline);
I have a collection that has records looking like this:
"_id" : ObjectId("550424ef2f44472856286d56"), "accountId" : "123",
"contactOperations" :
[
{ "contactId" : "1", "operation" : 1, "date" : 500 },
{ "contactId" : "1", "operation" : 2, "date" : 501 },
{ "contactId" : "2", "operation" : 1, "date" : 502 }
]
}
I want to know the latest operation number that has been applied on a certain contact.
I'm using the aggregation framework to first unwind the contactOperations and then grouping by accountId and contactOperations.contactId and max contactOperations.date.
aggregate([{$unwind : "$contactOperations"}, {$group : {"_id":{"accountId":"$accountId", "contactId":"$contactOperations.contactId"}, "date":{$max:"$contactOperations.date"} }}])
The result I get is:
"_id" : { "accountId" : "123", "contactId" : "2" }, "time" : 502 }
"_id" : { "accountId" : "123", "contactId" : "1" }, "time" : 501 }
Which seems correct so far, but I also need the contactOperations.operation field that was recorded with $max date. How can I select that?
You have to sort the unwind values then apply $last operator to get operation for max date. Hope this query will solve your problem.
aggregate([
{
$unwind: "$contactOperations"
},
{
$sort: {
"date": 1
}
},
{
$group: {
"_id": {
"accountId": "$accountId",
"contactId": "$contactOperations.contactId"
},
"date": {
$max: "$contactOperations.date"
},
"operationId": {
$last: "$contactOperations.operation"
}
}
}
])