I'm trying to adjust the timezone with date aggregation operators.
I need to make -7 hours adjustment on the $signs.timestamp field.
This is my code:
function statsSignatures() {
$cursor = $this->db->collection->users->aggregate(
array('$unwind' => '$signs'),
array('$project'=>array(
'signs'=>'$signs',
'y'=>array('$year'=>'$signs.timestamp'),
'm'=>array('$month'=>'$signs.timestamp'),
'd'=>array('$dayOfMonth'=>'$signs.timestamp'),
'h'=>array('$hour'=>'$signs.timestamp')
)),
array('$group'=>array(
'_id'=>array('year'=>'$y','month'=>'$m','day'=>'$d','hour'=>'$h'),
'total'=>array('$sum'=>1)
)),
array('$sort'=>array(
'_id.year'=>1,
'_id.month'=>1,
'_id.day'=>1,
'_id.hour'=>1
))
);
return $cursor['result'];
}
I'm using MongoDB version 2.6.3.
Thank you a lot !
You can use $project with $subtract operator to make a -7 hour adjustment to a Date field:
{
$project : {
ts : { $subtract : [ "$signs.timestamp", 25200000 ] }
}
}
// 25200000 == 1000 milis x 60 sec x 60 mins x 7 h
The projected field ts is a Date that's offset by -7 hours.
Edit
This is the correct PHP syntax when using $subtract.
array(
'$project' => array(
'ts' => array('$subtract' => array('$signs.timestamp', 25200000))
)
)
Subtract accepts an array of values, not a key=>value pair.
I'm not sure why, but I'm getting "exception: invalid operator '$signs.timestamp'" if i'm trying to subtract this in php like this code:
$cursor = $app['mdb']->changemi->users->aggregate(
array('$unwind' => '$signs'),
array('$project' => array(
'ts'=>array('$subtract'=>array(
'$signs.timestamp'=> 25200000
))
)),
array('$project'=>array(
'y'=>array('$year'=>'$ts'),
'm'=>array('$month'=>'$ts'),
'd'=>array('$dayOfMonth'=>'$ts'),
'h'=>array('$hour'=>'$ts')
)),
array('$group'=>array(
'_id'=>array('year'=>'$y','month'=>'$m','day'=>'$d','hour'=>'$h'),
'total'=>array('$sum'=>1)
)),
array('$sort'=>array(
'_id.year'=>1,
'_id.month'=>1,
'_id.day'=>1,
'_id.hour'=>1
))
);
So I came with 2 workarounds:
backend php. json_decode
$cursor = $app['mdb']->changemi->users->aggregate(
array('$unwind' => '$signs'),
json_decode('{"$project" : {"ts" : { "$subtract" : [ "$signs.timestamp", 25200000 ] }}}',true),
array('$project'=>array(
'y'=>array('$year'=>'$ts'),
'm'=>array('$month'=>'$ts'),
'd'=>array('$dayOfMonth'=>'$ts'),
'h'=>array('$hour'=>'$ts')
)),
array('$group'=>array(
'_id'=>array('year'=>'$y','month'=>'$m','day'=>'$d','hour'=>'$h'),
'total'=>array('$sum'=>1)
)),
array('$sort'=>array(
'_id.year'=>1,
'_id.month'=>1,
'_id.day'=>1,
'_id.hour'=>1
))
);
frontend javascript (minusHours)
Date.prototype.minusHours= function(h){
this.setHours(this.getHours()-h);
return this;
}
...
"date": new Date({{ i._id.year }}, {{ i._id.month -1 }}, {{ i._id.day }}, {{ i._id.hour }}, 0, 0, 0).minusHours(7),
Here is what worked for me. Instead of doing the timezone conversion in the 'project', I just convert the timestamp while grouping.
group._id = {
year: { $year : [{ $subtract: [ "$timestamp", 25200000 ]}] },
month: { $month : [{ $subtract: [ "$timestamp", 25200000 ]}] },
day: { $dayOfMonth : [{ $subtract: [ "$timestamp", 25200000 ]}] }
};
group.count = {
$sum : 1
};
There's no need to close some objects in array, this way worked for me:
group._id = {
year: { $year : { $subtract: [ "$timestamp", 25200000 ]}},
month: { $month : { $subtract: [ "$timestamp", 25200000 ]}},
day: { $dayOfMonth : { $subtract: [ "$timestamp", 25200000 ]}}
};
group.count = {
$sum : 1
};
Related
i'm trying to load events into a calendar.
Therefore i need to make a JSONstring with the event data but i can't figure out how to do this.
how i create the data:
$tickets = Ticket::where('mechanic_id', $request->mechanic_id)->get();
$tickets_json[] = [];
foreach($tickets as $ticket) {
$tickets_json['title'] = $ticket->number;
$tickets_json['start'] = $ticket->planned_timestamp_start->format('d-m-Y H:i');
$tickets_json['end'] = $ticket->planned_timestamp_end->format('d-m-Y H:i');
$tickets_json['url'] = '/tickets/' . $ticket->id;
}
This returns:
array:5 [▼
0 => []
"title" => "452"
"start" => "27-10-2017 09:00"
"end" => "27-10-2017 12:00"
"url" => "/tickets/2"
]
This is not what i want and need.
I need it like this :
{
title: '63782136',
start: '27-10-2017 09:00',
end: '"27-10-2017 12:00"',
url: '/tickets/2'
},
{
title: '23123',
start: '27-10-2017 09:00',
end: '"27-10-2017 12:00"',
url: '/tickets/3'
},
{
title: '432512',
start: '27-10-2017 09:00',
end: '"27-10-2017 12:00"',
url: '/tickets/4'
},
I have tried:
return response()->json($tickets_json);
But doesn't return the correct format:
#data: "{"0":[],"title":"452","start":"27-10-2017 09:00","end":"27-10-2017 12:00","url":"\/tickets\/2"}"
It adds {"0":[],}
.
Thanks in advance.
You're making the mistake of assigning your data to the first dimension of the array when you want it in the second dimension. The following should do the trick:
$tickets_json = [];
$tickets->each(function ($ticket) use (&$tickets_json) {
$tickets_json[] = [
'title' => $ticket->numer,
'start' => $ticket->planned_timestamp_start->format('d-m-Y H:i'),
'end' => $ticket->planned_timestamp_end->format('d-m-Y H:i'),
'url' => '/tickets/' . $ticket->id
];
});
return $tickets_json;
This code will give you a proper formatted array which you can use to get the following JSON output:
[
{
"title": "63782136",
"start": "27-10-2017 09:00",
"end": "27-10-2017 12:00",
"url": "/tickets/2"
},
{
"title": "23123",
"start": "27-10-2017 09:00",
"end": "27-10-2017 12:00",
"url": "/tickets/3"
}
]
Below is my sample mongodb collection
{
"_id" : ObjectId("57ed32f4070577ec56a56b9f"),
"log_id" : "180308",
"issue_id" : "108850",
"author_key" : "priyadarshinim_contus",
"timespent" : NumberLong(18000),
"comment" : "Added charts in the dashboard page of the application.",
"created_on" : "2017-08-16T18:22:04.816+0530",
"updated_on" : "2017-08-16T18:22:04.816+0530",
"started_on" : "2017-08-16T18:21:39.000+0530",
"started_date" : "2017-08-02",
"updated_date" : "2017-08-02",
"role" : "PHP",
"updated_at" : ISODate("2017-09-29T15:27:48.069Z"),
"created_at" : ISODate("2017-09-29T15:27:48.069Z"),
"status" : 1.0
}
I need to get records with help of started_date , by default I will give two dates in that i will check $gt and $lt of started date .
$current_date = '2017-08-31';
$sixmonthfromcurrent ='2017-08-01';
$worklogs = Worklog::raw ( function ($collection) use ($issue_jira_id, $current_date, $sixmonthfromcurrent) {
return $collection->aggregate ( [
['$match' => ['issue_id' => ['$in' => $issue_jira_id],
'started_date' => ['$lte' => $current_date,'$gte' => $sixmonthfromcurrent]
]
],
['$group' => ['issue_id' => ['$push' => '$issue_id'],
'_id' => ['year' => ['$year' => '$started_date'],
'week' => ['$week' => '$started_date'],'resource_key' => '$author_key'],
'sum' => array ('$sum' => '$timespent')]
],
[ '$sort' => ['_id' => 1]
]
] );
} );
If I run this query I am getting this type of error:
Can't convert from BSON type string to Date
How to rectify this error?
The only field in your $group that I see as troubling is the field week.
The year you could extract by doing a $project before your $group aggregation:
$project: {
year: { $substr: [ "$started_date", 0, 4 ] },
issue_id: 1,
author_key: 1,
timespent: 1
}
if you know that the date string will always come at this format. Of course you cannot do a substr operation for finding out the week.
It would be easy though if your field started_date would be an actual ISODate(), then you could use exactly what you wrote as you probably already saw in the documentation.
If you need the field week very bad, which I imagine you do, then I'd suggest you convert your field started_date to an ISODate().
You can do that with a bulkWrite:
db = db.getSiblingDB('yourDatabaseName');
var requests = [];
db.yourCollectionName.find().forEach(doc => {
var date = yourFunctionThatConvertsStringToDate(doc.started_date);
requests.push( {
'updateOne': {
'filter': { '_id': doc._id },
'update': { '$set': {
"started_date": date
} }
}
});
if (requests.length === 500) {
db.yourCollectionName.bulkWrite(requests);
requests = [];
}
});
if(requests.length > 0) {
db.yourCollectionName.bulkWrite(requests);
}
Load this script directly on your mongodb server and execute there.
Hope this helps.
I have a mongo collection lie this :
{
"_id":ObjectId("55f16650e3cf2242a79656d1"),
"user_id":11,
"push":[
ISODate("2015-09-08T11:14:18.285 Z"),
ISODate("2015-09-08T11:14:18.285 Z"),
ISODate("2015-09-09T11:14:18.285 Z"),
ISODate("2015-09-10T11:14:18.285 Z"),
ISODate("2015-09-10T11:14:18.285 Z")
]
}{
"_id":ObjectId("55f15c78e3cf2242a79656c3"),
"user_id":12,
"push":[
ISODate("2015-09-06T11:14:18.285 Z"),
ISODate("2015-09-05T11:14:18.285 Z"),
ISODate("2015-09-07T11:14:18.285 Z"),
ISODate("2015-09-09T11:14:18.285 Z"),
ISODate("2015-09-09T11:14:18.285 Z"),
ISODate("2015-09-10T11:14:18.285 Z"),
ISODate("2015-09-11T11:14:18.285 Z")
]
}
How can I find user_ids where count of timeStamps < 3 and having date(timestamp) > (currentDate-5) in single query. I will be using php and dont want to bring all the documents in memory.
Explanation:
user_id : date : count
11 : 2015-09-08 : 2
2015-09-09 : 1
2015-09-10 : 2
12 : 2015-09-05 : 1
2015-09-06 : 1
2015-09-07 : 1
2015-09-09 : 2
2015-09-10 : 1
2015-09-11 : 1
If date set to 2015-09-09(user input) it will give 3(count) for user_id 11 and 4(count) for user_id 12. So suppose count is set to 3(user input). The query should return 11(user_id). If count is set to 2, there will be no user_id available and if count is set to 5, it should return both 11 and 12
To solve this you need an aggregation pipeline that first "filters" the results to the "last 5 days" and then essentially "sums the count" of array items present in each qualifying document to then see if the "total" is "less than three".
The $size operator of MongoDB aggregation really helps here, as does $map and some additional filtering via $setDifference for the false results returned from $map, as doing this "in document first" and "within" the $group stage required, is the most efficient way to process this
$result = $collection->aggregate(array(
array( '$match' => array(
'push' => array(
'time' => array(
'$gte' => MongoDate( strtotime('-5 days',time()) )
)
)
)),
array( '$group' => array(
'_id' => '$user_id',
'count' => array(
'$sum' => array(
'$size' => array(
'$setDifference' => array(
array( '$map' => array(
'input' => '$push',
'as' => 'time',
'in' => array(
'$cond' => array(
array( '$gte' => array(
'$$time',
MongoDate( strtotime('-5 days',time()) )
)),
'$time',
FALSE
)
)
)),
array(FALSE)
)
)
)
)
)),
array( '$match' => array(
'count' => array( '$lt' => 3 )
))
));
So the after all of the work to first find the "possible" documents that contain array entries meeting the criteria via $match and then find the "total" size of the matched array items under $group, then the final $match excludes all results that are less than three in total size.
For the largely "JavaScript brains" out there ( like myself, well trained into it ) this is basically this contruct:
db.collection.aggregate([
{ "$match": {
"push": {
"$gte": new Date( new Date().valueOf() - ( 5 * 1000 * 60 * 60 * 24 ))
}
}},
{ "$group": {
"_id": "$user_id",
"count": {
"$sum": {
"$size": {
"$setDifference": [
{ "$map": {
"input": "$push",
"as": "time",
"in": {
"$cond": [
{ "$gte": [
"$$time",
new Date(
new Date().valueOf() -
( 5 * 1000 * 60 * 60 * 24 )
)
]},
"$$time",
false
]
}
}},
[false]
]
}
}
}
}},
{ "$match": { "count": { "$lt": 3 } } }
])
Also, future versions of MongoDB will offer $filter, which simplifies the whole $map and $setDifference statement part:
db.collection.aggregate([
{ "$match": {
"push": {
"$gte": new Date( new Date().valueOf() - ( 5 * 1000 * 60 * 60 * 24 ))
}
}},
{ "$group": {
"_id": "$user_id",
"count": {
"$sum": {
"$size": {
"$filter": {
"input": "$push",
"as": "time",
"cond": {
"$gte": [
"$$time",
new Date(
new Date().valueOf() -
( 5 * 1000 * 60 * 60 * 24 )
)
]
}
}
}
}
}
}},
{ "$match": { "count": { "$lt": 3 } } }
])
As well as noting that the "dates" are probably best calculated "before" the pipeline definition as a separate variable for the best accuracy.
I'm using PHP with MongoDB, How can apply below commend inside?
db.event.group({
keyf: function(doc) {
return {
year: doc.created.getFullYear(),
month: doc.created.getMonth() + 1,
day: doc.created.getDate()
}
},
reduce: function(curr, result){
result.count++;
},
initial: {count: 0}
});
I have tried below, but NOT working. Looks like not supprt keyf?
$keyf = 'function(doc){return {year: doc.created.getFullYear(), month: doc.created.getMonth()+1, day: doc.created.getDate()}}';
$initial = array('count' => 0);
$reduce = 'function(curr, result){result.count++;}';
$collection->group($keyf, $initial, $reduce);
It looks like you are basically counting the amount of documents under a date.
It should be noted that the group command has numerous flaws including:
Not officially supporting sharding (warning not to use it)
Is basically JavaScript
Is Basically a Map Reduce
Is extremely slow
that means it has since been "deprecated" in favour of the aggregation framework, which in PHP for you would be:
$db->collection->aggregate(array(
array('$group' => array(
'_id' => array(
'day' => array('$dayOfMonth' => '$created'),
'month' => array('$month' => '$created'),
'year' => array('$year' => '$created')
),
'count' => array('$sum' => 1)
))
));
To understand what operators I used etc you can look here:
http://docs.mongodb.org/manual/reference/operator/aggregation/dayOfMonth/
http://docs.mongodb.org/manual/reference/operator/aggregation/month/#exp._S_month
http://docs.mongodb.org/manual/reference/operator/aggregation/year/
http://docs.mongodb.org/manual/reference/operator/aggregation/sum/
The PHP driver does have the MongoCode class for constructing the JavaScript values that are required.
But you are actually better off using the .aggregate() command to this as it is "native* code and does not rely on the JavaScript engine. So it is much faster at producing results.
db.collection.aggregate([
{ "$group": {
"_id": {
"year": { "$year": "$created" },
"month": { "$month": "$created" },
"day": { "$dayOfMonth": "$created" }
},
"count": { "$sum": 1 }
}}
])
Data Problem
So the aggregate function works are expected, but you seem to have a problem with your test data. Here is cwhat you gave:
db.post.insert({'field':'b', 'created':new Date('2014, 1, 1')});
db.post.insert({'field':'c', 'created':new Date('2014, 1, 1 11:11:11')});
db.post.insert({'field':'d', 'created':new Date('2014, 1, 1 12:00:00')});
db.post.insert({'field':'a', 'created':new Date('2014, 1, 2')});
db.post.insert({'field':'b', 'created':new Date('2014, 1, 2')})
And this produces the data:
{ "field" : "a", "created" : ISODate("2013-12-31T13:00:00Z") }
{ "field" : "b", "created" : ISODate("2013-12-31T13:00:00Z") }
{ "field" : "c", "created" : ISODate("2014-01-01T00:11:11Z") }
{ "field" : "d", "created" : ISODate("2014-01-01T01:00:00Z") }
{ "field" : "a", "created" : ISODate("2014-01-01T13:00:00Z") }
{ "field" : "b", "created" : ISODate("2014-01-01T13:00:00Z") }
So it looks like you were trying to add "hours" in the same day to test the grouping. But the arguments to Date() are not correct. You wanted this:
db.post.insert({'field':'b', 'created':new Date('2014-01-01')});
db.post.insert({'field':'c', 'created':new Date('2014-01-01 11:11:11')});
So the whole date as a string and not the "comma" separated values
I have a collection with documents that look like this:
{
_id: ObjectId("516eb5d2ef4501a804000000"),
accountCreated: "2013-04-17 16:46",
accountLevel: 0,
responderCount: 0
}
I want to group and count these documents based on the accountCreated date (count per day), but I am stuck with the handling of dates since the date includes time as well.
This is what I have, but it returns the count including the time, witch means lots of entries always with 1 as accounts.
$g = $form->mCollectionUsers->aggregate(array(
array( '$group' => array( '_id' => '$accountCreated', 'accounts' => array( '$sum' => 1 ) ) )
));
Is there a way to rewrite the date to only take day in account and skip the time?
I have found this example but I can´t really get figure out how to adapt it to this example.
If accountCreated is a date you can do it like this (I'll use the mongo shell syntax since I'm not familiar with the php driver):
db.mCollectionUsers.aggregate([
{$project :{
day : {"$dayOfMonth" : "$accountCreated"},
month : {"$month" : "$accountCreated"},
year : {"$year" : "$accountCreated"}
}},
{$group: {
_id : {year : "$year", month : "$month", day : "$day"},
accounts : { "$sum" : 1}
}}
]);
If you want to display the date properly:
db.mCollectionUsers.aggregate([
{
$group: {
_id: { $dateToString: { format: '%Y-%m-%d', date: '$accountCreated' } },
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
date: '$_id',
count: 1
}
}
])
The result would look like:
[
{
"date": "2020-11-11",
"count": 8
},
{
"date": "2020-11-13",
"count": 3
},
{
"date": "2020-11-16",
"count": 3
},
]