MongoDB Raw query to select documents which are in given datetime period - mongodb

I want to select documents which are in user given date time period like between :
$from_date : "2017-01-07 09:08:59" To `$to_date : "2017-08-09 09:08:59"`
I'm using laravel framework and jenssegers/laravel-mongodb mongodb driver to run my query, And this is my Raw query :
$normal_banners = BannerView::raw(function ($collection) use ($id, $from_date, $to_date) {
$conditions = [
["camp_id" => ['$eq' => $id]],
['$or' => [
['seat_target_status' => ['$eq' => true]],
['seat_target_status' => ['$eq' => false]]
]],
['camp_target_status' => ['$eq' => false]],
];
if ($from_date) {
$conditions[] = ['created_at' => ['$gte' => $from_date]];
}
return $collection->aggregate([
['$match' => ['$and' => $conditions]],
]);
})->count();
But my problem is that it returns 0 as result; while there are 16 documents in this time period.
I've tried this method to get the count of them but still 0 result :
$normal_banners = BannerView::Where('camp_id', $id)
->where(function ($query) {$query->where('seat_target_status', true)->orWhere('seat_target_status', false);
})
->where('camp_target_status', false)
->where("created_at", ">=", $from_date)
->count();
FYI : I've converted datetime of input to ISODate Which is the mongodb datatype for created_at field.
$Fromdatetime = $request->input('from_date');
$from_date = new DateTime($Fromdatetime);
$from_date = $from_date->format(DateTime::ISO8601);
mongodb field data type is :
"updated_at": ISODate("2017-04-10T09:35:58.641Z"),
"created_at": ISODate("2017-04-10T09:35:58.641Z")
My input data type is : "2017-01-07T09:08:59+0000"
Any suggestion ?

You have to "put in braces" orWhere function and in your raw query you are testing $to_date with updated_at column, but in Eloquent code you are making between with created_at column
$targeted_banners = BannerView::Where('camp_id', $id)
->where(function($query){$query->where('seat_target_status', true)->orWhere('seat_target_status', false);})
->where('camp_target_status', false)
->where("created_at", ">=" $from_date)
->where("updated_at", "<=", $to_date)
->count();

I've solved this problem using Carbon::createDateFrom method, it create a UTC date time base on my input :
Inputs :
{
"from_date": "2017-03-10",
"to_date": "2017-04-09"
}
Converting input dates :
$from_date_arr = explode("-",$request->input('from_date'));
$from_date = Carbon::createFromDate($from_date_arr[0],$from_date_arr[1],$from_date_arr[2]);
$to_date_arr = explode("-",$request->input('to_date'));
$to_date = Carbon::createFromDate($to_date_arr[0],$to_date_arr[1],$to_date_arr[2]);
And This is the query I run which worked :
$normal_banners = BannerView::Where('camp_id', $id)
->where(function ($query) {$query->where('seat_target_status', true)->orWhere('seat_target_status', false);
})
->where('camp_target_status', false)
->where("created_at", ">=",$from_date)
->where("created_at", "<=",$to_date)
->count();
There is a strange problem still with jessenger driver which whereBetween is not working and we should use two where clause to make it work.
Hope solves others problem.

Related

Extracting date parts using DBIC while keeping queries database agnostic

I use a MySQL database in production and a SQLite database for running tests. One part of my application is used to gather monthly statistics for a year. I've successfully done this, however it came at a cost of not being able to automate tests because I'm using MySQL specific functions when querying for the data:
my $criteria = {
status => ['Complete'],
'YEAR(completed_on)' => DateTime->now()->year(),
};
my $attributes = {
select => [ { count => 'title' }, 'completed_on' ],
as => [qw/num_completed datetime/],
group_by => [qw/MONTH(completed_on)/],
};
Notice I'm using YEAR and MONTH MySQL functions.
I know one way I can substitute the where clause to eliminate the use of MySQLs YEAR function, something like this:
my $dtf = $schema->storage->datetime_parser;
my $begin_date = DateTime->from_day_of_year( year => DateTime->now()->year(), day_of_year => 1 ); #inception o_O
my $end_date = DateTime->from_day_of_year( year => DateTime->now()->year(), day_of_year => 36[56] );
my $criteria = {
status => ['Complete'],
completed_on =>
-between => [
$dtf->format_datetime($begin_date),
$dtf->format_datetime($end_date),
]
};
Using the recommended way to query date fields using DBIC
But I'm stumped as to what to do with the group_by clause and how to make the grouping of this fields date value by month database agnostic as well. Wondering if anyone has any ideas?
Thanks!
Sometimes you will have to make engine specific code in DBIx::Class if you're trying to do special things. You can use $schema->storage->sqlt_type to make different SQL.
Note you can also use substr(completed_on,1,4) to get the year in SQLite.
This will solve your problem:
my $type = $schema->storage->sqlt_type;
my $criteria;
my $attributes;
if ($type eq 'MySQL') {
$criteria = {
status => ['Complete'],
'YEAR(completed_on)' => DateTime->now()->year(),
};
$attributes = {
select => [ { count => 'title' }, 'completed_on' ],
as => [qw/num_completed datetime/],
group_by => [qw/MONTH(completed_on)/],
};
}
elsif ($type eq 'SQLite') {
my $dtf = $schema->storage->datetime_parser;
my $begin_date = DateTime->from_day_of_year( year => DateTime->now()->year(), day_of_year => 1 ); #inception o_O
my $end_date = DateTime->from_day_of_year( year => DateTime->now()->year() + 1, day_of_year => 1 )->add( seconds => -1 );
$criteria = {
status => ['Complete'],
completed_on => {
-between => [
$dtf->format_datetime($begin_date),
$dtf->format_datetime($end_date),
]
}
};
$attributes = {
select => [ { count => 'title' }, 'completed_on' ],
as => [qw/num_completed datetime/],
group_by => ['substr(completed_on,6,2)'],
};
}

Find query with and operator in PHP

Hi i am working on backend of web application & want to find the documents from mongodb database that contain key active_status with value set to both 1 & 2. With mongodb PHP i am confused of how to find with both parameters in single query.
My query was this:
$mongoDb = MongoDbConnector::getCollection("endusers");
$endUserData = $mongoDb->find(array('active_status' => 1, '$and' => array('active_status' => 2)));
I have to fetch the users whose active_status should be 1 & 2. The above query doesnt seems to work. What is the right one for that?
Thanks on advance for quick response.
You have $and the wrong way around. Both arguments need to be included:
$endUserData = $mongoDb->find(array(
'$and' => array(
array( 'active_status' => 1 )
array( 'active_status' => 2 )
)
));
And since that would only make sense when looking for both elements within an array element, then you should instead use $all, which is shorter syntax:
$endUserData = $mongoDb->find(array(
'active_status' => array( '$all' => array(1,2) )
));
I should add that unless you intend to match a document like this:
{ "active_status" => [1,2] }
The you do not in fact want $and at all, but rather you want $or or better yet $in for multiple possible values on the same field:
$endUserData = $mongoDb->find(array(
'active_status' => array( '$in' => array(1,2) )
));
This matches documents like this:
{ "active_status": 1 },
{ "active_status": 2 }

MongoDB PHP Date

In my database I have a document like this.
{
"_id" : ObjectId("5465508f453c9446d228b225"),
"category" : "animals",
"lastLocation" : "Wilpattu",
"lastSeenDate" : "2013-05-26",
"company" : "53fd9a3204ac58132377f807"
}
I want to check if the given date is in between two date ranges. I am using Codeigniter for front end development. In the model class I have written,
$connection =$this->Dbconnect->GetMongoCon();
$database = $connection->etsp;
$collection = $database->trackedObjects;
$start = $AnimalArray['fyear']. '-'.$AnimalArray['fmonth'].'-'.$AnimalArray['fday'];
$end = $AnimalArray['lyear']. '-'.$AnimalArray['lmonth'].'-'.$AnimalArray['lday'];
$searchCriteria = array(
'company' => $AnimalArray["Company"],
'lastSeenDate' =>array('$gt' => $start1, '$lte' => $end1)
);
$ReturnAnimalArray=$collection->find($searchCriteria);
But I can't query the date. From internet I have tried converting date into mongodb format.
$start1 = new MongoDate(strtotime(date($start )));
$end1 = new MongoDate(strtotime(date($end)));
But that also did not work. Any hint will be highly appreciated.
lastSeenDate is not MongodbDate format, may be you should like do this:
$js = "function() {
return this.lastSeenDate > '2013-02-18' && this.lastSeenDate <= '2014-09-28';
}";
$collection->find(array('$where' => $js));

CakePHP style database query results in Zend Framework using Zend_Db_Table?

If you have a complex SQL query involving many joins (for example returning Articles with their associated many to many Tags) is there anything in Zend Framework that will produce the lovely CakePHP style database results:
Array
(
[0] => Array
(
[ModelName] => Array
(
[id] => 83
[field1] => value1
[field2] => value2
[field3] => value3
)
[AssociatedModelName] => Array
(
[id] => 1
[field1] => value1
[field2] => value2
[field3] => value3
)
)
)
I don't mind if it's an object rather than an array, I just wondered if by using Zend_Db_Table to build a SELECT JOIN query I could save some leg work and get some nicely formatted results.
Here is the kind of code I'm using to build the query:
$select = $db->select(Zend_Db_Table::SELECT_WITH_FROM_PART);
$select->from('tableName','fieldName')
->join('joinTable', 'joinTable.keyId = tableName.keyId',array())
->where('tableName.userId = ?', $userId);
$resultSet = $db->fetchAll($select);
Nothing as pretty as what you're used to just the data I asked for.
The normal Result would be a rowset object, however the ->toArray() is available for most *Zend_DbTable_Abstract* methods.
The $result->toArray() truncated and dumped using Zend_debug::dump():
Lead Tracks array(7) {
[0] => array(9) {
["trackid"] => string(2) "24"
["weekendid"] => string(1) "8"
["shiftid"] => string(1) "1"
["bidlocationid"] => string(1) "1"
["qty"] => string(1) "2"
["lead"] => string(1) "1"
["bidloc"] => string(14) "out of service"
["deptcode"] => string(3) "491"
["stationid"] => string(1) "1"
}
The query:
where = $this->select(Zend_Db_Table::SELECT_WITH_FROM_PART)
->setIntegrityCheck(FALSE);
$where->where('track.bidlocationid = ?', $bidlocationId)
->where('lead = ?', $lead)
->join('bidlocation', 'bidlocation.bidlocationid = track.bidlocationid')
->where('bidlocation.stationid = ?', $stationId)
->order('shiftid ASC')
->order('weekendid ASC');
$result = $this->fetchAll($where);
sorry, just utility :)

MongoDB & PHP - Returning a count of a nested array

Imagine I have a MonogDB collection containing documents as follows:
{name: 'Some Name', components: {ARRAY OF ITEMS}}
How can I return the name and the count of items in components?
Do I have to use a map/reduce?
I am using PHP's Mongo extension.
EDIT: Snippet of current code in PHP (working) but I just want count of the components
$fields = array(
'name', 'components'
);
$cursor = $this->collection->find(array(), $fields);
$cursor->sort(array('created_ts' => -1));
if (empty($cursor) == true) {
return array();
} else {
return iterator_to_array($cursor);
}
Thanks,
Jim
You could use map-reduce or you could use a simple group query as follows. Since I am assuming that your name property is a unique key, this should work even though it isn't a reason that you'd normally use the group function:
db.test.group({
key: { name:true },
reduce: function(obj,prev) {
var count = 0;
for(k in obj.components)
count++;
prev.count = count;
},
initial: { count: 0}
});
You mentioned that you have an array of components, but it appears that you are storing components as an object {} and not and array []. That is why I had to add the loop in the reduce function, to count all of the properties of the components object. If it were actually an array then you could simply use the .length property.
In PHP it would look something like this (from the Manual):
$keys = array('name' => 1);
$initial = array('count' => 0);
$reduce =<<<JS
function(obj,prev) {
var count = 0;
for(k in obj.components)
count++;
prev.count = count;
},
JS;
$m = new Mongo();
$db = $m->selectDB('Database');
$coll = $db->selectCollection('Collection');
$data = $coll->group($keys, $initial, $reduce);
Finally, I would strongly suggest that if you are trying to access the count of your components on a regular basis that you store the count as an additional property of the document and update it whenever it changes. If you are attempting to write queries that filter based on this count then you will also be able to add an index on that components property.
You could use db.eval() and write the calculation in JavaScript.
Jim-
These are two separate operations; Unless you want to leverage PHP's count on the results you get which you would then do something like:
$m = new Mongo();
$db = $m->selectDB('yourDB');
$collection = $db->selectCollection('MyCollection');
$cursor = $collection->find(array(), array("name"=>1, "components"=>1));
foreach($cursor as $key){
echo($key['name'].' components: '.count($key['components']);
}
Ran across this today, If your using the new driver with aggregate you can do this in php, ( given this schema )
{name: 'Some Name', components: {ARRAY OF ITEMS}}
In PHP:
$collection = (new Client())->db->my_collection;
$collection->aggregate([
'$match' => ['name' => 'Some Name'],
'$group' => [
'_id' => null,
'total'=> ['$sum' => "\$components"]
]
]);
The trick here with PHP is to escape the $ dollar sign, this is basically what the mongo documentation says when using size or sum
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
https://docs.mongodb.com/manual/reference/operator/aggregation/sum/
The problem I had is mongo puts fields in as "$field" and PHP doesn't like that at all because of the way it does variable interpolation. However, once you escape the $, it works fine.
I think for this particular case you'd need to do something similar but with $project instead of $group Like this
$collection = (new Client())->db->my_collection;
$collection->aggregate([
'$match' => ['name' => 'Some Name'],
'$project' => [
'name' => "\$name",
'total'=> ['$sum' => "\$components"]
]
]);
This is an old question but seeing as there is no answer picked, I'll just leave this here.