How to find a mongodb collection entry by ObjectId in php - php

I have a mongodb database which contains two connected collections.
The first one has a dataset which looks like this:
{
"_id": ObjectId("5326d2a61db62d7d2f8c13c0"),
"reporttype": "visits",
"country": "AT",
"channel": "wifi",
"_level": NumberInt(3)
}
The ObjectId is connected to several datasets in the second collection which look like this:
{
"_id": ObjectId("54c905662d0a99627efe17a9"),
"avg": NumberInt(0),
"chunk_begin": ISODate("2015-01-28T12:00:00.0Z"),
"count": NumberInt(15),
"max": NumberInt(0),
"min": NumberInt(0),
"sum": NumberInt(0),
"tag": ObjectId("5326d2a61db62d7d2f8c13c0")
}
As you can see it the "_id" from the first dataset the same as the "tag" from the second.
I want to write a routine in php which gets the ids from the first collection and finds by them datasets in a certain timeframe in the second collection for deletion.
I get the id from the first collection ok, but I suspect I use it wrongly in the the query for the second collection because nothing is ever found or deleted.
Code looks like this:
// select a collection (analog to a relational database's table)
$tagCollection = $db->tags;
$orderCollection = $db->orders;
// formulate AND query
$aTagCriteria = array(
'reporttype' => new MongoRegex('/[a-z]+/'),
);
// retrieve only _id keys
$fields = array('_id');
$cursor = $tagCollection->find($aTagCriteria, $fields);
$startOfTimeperiod = new MongoDate(strtotime('2015-01-05 00:00:00'));
$endOfTimeperiod = new MongoDate(strtotime('2015-01-07 13:20:00'));
// iterate through the result set
foreach ($cursor as $obj) {
echo '_id: '.$obj['_id'].' | ';
// Until here all is ok, I get the _id as output.
$aOrdercriteria = array(
'tag' => new MongoId($obj['_id']),
'date' => array(
'$lte' => $endOfTimeperiod,
'$gte' => $startOfTimeperiod
),
);
$iCount = $orderCollection->count($aOrdercriteria);
if ($iCount > 0) {
echo PHP_EOL.$iCount.' document(s) found.'.PHP_EOL;
$result = $orderCollection->remove($aOrdercriteria);
echo __FUNCTION__.'|'.__LINE__.' | '.json_encode($result).PHP_EOL;
echo 'Removed document with ID: '.$aOrdercriteria['tag'].PHP_EOL;
}
}
What is the correct way for the search condition so it looks for tag Objects
with the previously found id?
PS:
I tried
'tag' => $obj['_id'],
instead of
'tag' => new MongoId($obj['_id']),
which didn't work either.

So two things had to be changed.
The first one was like EmptyArsenal hinted:
tag' => new MongoId($obj['_id']),
is wrong since $obj['_id'] is already an object.
So
'tag' => $obj['_id'],
is correct.
And if I change my condition from "date" to "chunk_begin" yahooo.... it works. Stupid me.

Related

Displaying all aggregated results from Elasticsearch query in PHP

I have a field called "arrivalDate" and this field is a string. Each document has an arrivalDate in string format (ex: 20110128). I want my output to be something like this (date and the number of records that have that date):
Date : how many records have that date
20110105 : 5 records
20120501 : 2 records
20120602 : 15 records
I already have the query to get these results.
I am trying to display aggregated results in PHP from Elasticsearch. I want my output to be something like this:
Date : how many records have that date
20110105 : 5 records
20120501 : 2 records
20120602 : 15 records
This is what I have so far:
$json = '{"aggs": { "group_by_date": { "terms": { "field": "arrivalDate" } } } }';
$params = [
'index' => 'pickups',
'type' => 'external',
'body' => $json
];
$results = $es->search($params);
However, I don't know how to display the results in PHP. For example, if I wanted to display the total number of documents I would do echo $results['hits']['total'] How could I display all the dates with the number of records they have in PHP?
I'd suggest using aggregations in the same way you construct the query, from my experience it seems to work quicker. Please see the below code:
'aggs' => [
'group_by_date' => [
'terms' => [
'field' => 'arrivalDate',
'size' => 500
]
]
]
Following that, instead of using the typical results['hits']['hits'] you would switch out the hits parts to results['aggregations']. Then access the returning data by accessing the buckets in the response.
For accessing the data from the aggregation shown above, it would likely be something along the lines of:
foreach ($results as $result){
foreach($result['buckets'] as $record){
echo($record['key']);
}
}
There will be a better way of accessing the array within the array, however, the above loop system works well for me. If you have any issues with accessing the data, let me know.

PhpMongo - how to apply AND condition for a single document present in an array?

My Mongo collection has two documents
{
"_id":ObjectId("567168393d5c6cd46a00002a"),
"type":"SURVEY",
"description":"YOU HAVE AN UNANSWERED SURVEY.",
"user_to_notification_seen_status":[
{
"user_id":1,
"status":"UNSEEN",
"time_updated":1450272825
},
{
"user_id":2,
"status":"SEEN",
"time_updated":1450273798
},
{
"user_id":3,
"status":"UNSEEN",
"time_updated":1450272825
}
],
"feed_id":1,
"time_created":1450272825,
"time_updated":1450273798
}
Here is the query I used to fetch only if the user_id is 2 & status is "UNSEEN".
**$query = array('$and' => array(array('user_to_notification_seen_status.user_id'=> 2,'user_to_notification_seen_status.status' => "UNSEEN")));**
$cursor = $notification_collection->find($query);
Ideally the above query shouldn't retrieve results but it returning results. If I give an invalid id or invalid status, it is not returning any record.
You're misunderstanding how the query works. It matches your document because user_to_notification_seen_status contains elements with user_id: 2 and status: UNSEEN.
What you can do to get the desired results is use the aggregation framework; unwind the array and then match both conditions. That way you'll only get the unwinded documents with the array element satisfying both conditions.
Run this in mongo shell (or convert to PHP equivalent). Also, change YourCollection to your actual collection name:
db.YourCollection.aggregate([ { $unwind: "$user_to_notification_seen_status" }, { $match: { "user_to_notification_seen_status.status": "UNSEEN", "user_to_notification_seen_status.user_id": 2 } } ] );
This will return no records, but if you change the id to 3 for example, it will return one.
Try:
$query = array(
array('$unwind' => '$user_to_notification_seen_status'),
array(
'$match' => array('user_to_notification_seen_status.status' => 'UNSEEN', 'user_to_notification_seen_status.user_id' => 2),
),
);
$cursor = $notification_collection->aggregate($query);

MongoDB: Advanced query on array

Suppose I have the following objects in my collection:
{id:'123', tags:['berry', 'apple']}
{id:'456', tags:['salad', 'tomatoe']}
{id:'789', tags:['bread', 'rice']}
My search term is "Strawberry". I want to find all objects, where one of the tags is part of search term. In this case it's the object with id '123', since 'berry' is part of 'Strawberry'.
I wanted to use Regex, like this (I'm using php btw):
$regex = new MongoRegex("/.*berry.*/i");
$results = $mongodb->data->find(array("tags" => array('$in' => array($regex))));
but the problem is that the regex is applied on the tags and not on the search result. So i'd need something like a reverse Regex.
Is a query like this somehow possible? Right now I'm doing it like this:
$search = "Strawberry";
$js = "function() { var i = 0; for (; i < this.tags.length; i++) { if ('".$search."'.indexOf(this.tags[i]) != -1) { return true; } } }";
$results = $mongodb->data->find($js);
That's OK for now, since the dataset isn't very large, but will be slow in the future.
Does anyone have a suggestion? Thanks.
UPDATE:
Sorry if this is still not clear.
My search Term is "Strawberry", not "berry". The php code I posted that contains the Regex was just to show that this is not a solution and does not work.
So again: My search term is "Strawberry" and I want to find all objects, where on of the tags is part of the search term, not the other way around
UPDATE 2:
To make it even clearer, in SQL this would be:
SELECT * FROM data WHERE 'Strawberry' LIKE CONCAT('%', tag, '%')
This query will match strawberry if you have in tags
db.collection.aggregate(
[
{$unwind: "$tags"},
{$match : {tags: /.*berry.*/i }}
]
)
Tested output'
{
"result" : [
{
"_id" : ObjectId("537373c17c3639c32fe515fb"),
"id" : "123",
"tags" : "berry"
},
{
"_id" : ObjectId("537375337c3639c32fe515fe"),
"id" : "789",
"tags" : "strawberry"
}
],
"ok" : 1
}
In terms of PHP,
$result = $mongodb->aggregate(array(
array(
'$unwind' => "$tags",
),
array(
'$match' => array(
'tags' => /.*berry.*/i
),
),
));

Json_Encode outputting with additional arrays

I'm struggling with the output of json_encode. I'm attempting to speed up our large dropdown navigation menu by storing everything in a json file that gets updated once a day and calling that when required.
I'm producing the json using json_encode, but it seems to be looping everything into additional, unneccessary, arrays and I can't figure out how to prevent this.
I've even tried fiddling with str_replace but had no success in generating valid json (though clearly this isn't really a long term solution in any case). I've also tried to figure out what combination of "each" I would need to get into the nestled arrays, but haven't found the right combination.
Below is the json I'm ending up with (I've reduced the number of entries to make it easier to see, the format is the same... just within each of Film, Gaming etc there are more items).
[
[
"Film",
[
{
"title": "13 Awkward Moments That Must Have Happened After The End Of Famous Movies",
"link": "http:\/\/whatculture.com\/film\/13-awkward-moments-that-must-have-happened-after-the-end-of-famous-movies.php",
"image": [
"http:\/\/cdn3.whatculture.com\/wp-content\/uploads\/2013\/08\/HP-100x60.jpg",
100,
60,
true
]
}
]
],
[
"TV",
[
{
"title": "10 Awesome TV Twists You Never Saw Coming",
"link": "http:\/\/whatculture.com\/tv\/10-awesome-tv-twists-you-never-saw-coming.php",
"image": [
"http:\/\/cdn3.whatculture.com\/wp-content\/uploads\/2013\/08\/lost-locke-100x60.jpg",
100,
60,
true
]
}
]
],
[
"Gaming",
[
{
"title": "WWE 2K14: Every Possible Classic Match",
"link": "http:\/\/whatculture.com\/gaming\/wwe-2k14-every-possible-classic-match.php",
"image": [
"http:\/\/cdn3.whatculture.com\/wp-content\/uploads\/2013\/08\/444-100x60.jpg",
100,
60,
true
]
}
]
]
]
And this is the script I'm using to generate said code:
I've included everything for completeness. A lot of the below is just the Wordpress query to pull back my relevant data:
$cats = array("Film","TV","Gaming","Sport","Music");
function filter_where($where = '') {
$where .= " AND post_date > '" . date('Y-m-d', strtotime('-3 days')) . "'";
return $where;
}
add_filter('posts_where', 'filter_where');
foreach($cats as $cat) {
$the_query = array(
'numberposts' => 5,
'category_name' => $cat,
'meta_key' => "visitcount",
'orderby' => "meta_value_num",
'suppress_filters' => false );
$special_query_results = get_posts($the_query);
foreach( $special_query_results as $post ) {
setup_postdata($post);
$myposts[] = array('title'=> html_entity_decode(get_the_title()),'link'=>get_permalink(get_the_ID()),'image'=>wp_get_attachment_image_src( get_post_thumbnail_id(get_the_ID()), 'smallthumb' ));
}
$pop_posts[] = array($cat,$myposts);
unset($myposts);
} // foreach cats as cat1000
wp_reset_postdata();
remove_filter('posts_where', 'filter_where');
$json_pop = json_encode($pop_posts,JSON_PRETTY_PRINT);
This is what I'm using to pull it back when user hovers on the nav item:
$.getJSON('http://whatculture.com/data/wc6.json', function(popular) {
$.each(popular.Sport, function() {
$('.popularMenu').append("<li><img src="+this.image[0]+" />"+this.title+"</li>");
});
});
This is a bit of a guess (see my comment regarding a need to clarify which arrays you see as "unnecessary") but the line that stands out to me is this:
$pop_posts[] = array($cat,$myposts);
This can be translated as "create a 2-element array, whose first member is $cat (the name of the category) and whose second member is $myposts (an array of posts); add this 2-element array as the last member of the array $pop_posts". The result is that $pop_posts is an array of two-element arrays.
Perhaps what you wanted to say was "set the key $cat of the associative array $pop_posts to the value $myposts (an array of posts)", which would be this:
$pop_posts[$cat] = $myposts;
That would make the resulting structure simpler, as instead of an array of 2-element arrays, you would have a single hash (PHP associative array, JS object) whose keys were categories, and whose values were the popular posts for that category.
However, there are two disadvantages:
It wouldn't work if the category names were not unique, since a key cannot exist more than once in a hash. I don't think this applies here.
JSON hashes (and the JS object they create) are unordered key-value collections. So if you want to preserve the order of your categories, you need a different mechanism (either the array-of-arrays you already have, or an additional array storing the "correct" order to visit the keys). In your example, you reference Sport specifically, so this may not be an issue.

Get top 5 documents with newest nested objects

I have the following data structure in MongoDB:
{ "_id" : ObjectId( "xy" ),
"litter" : [
{ "puppy_name" : "Tom",
"birth_timestamp" : 1353963728 },
{ "puppy_name" : "Ann",
"birth_timestamp" : 1353963997 }
]
}
I have many of these "litter" documents with varying number of puppies. The highter the timestamp number, the younger the puppy is (=born later).
What I would like to do is to retrieve the five youngest puppies from the collection accross all litter documents.
I tried something along
find().sort('litter.birth_timestamp' : -1).limit(5)
to get the the five litters which have the youngest puppies and then to extract the youngest puppy from each litter in the PHP script.
But I am not sure if this will work properly. Any idea on how to do this right (without changing the data structure)?
You can use the new Aggregation Framework in MongoDB 2.2 to achieve this:
<?php
$m = new Mongo();
$collection = $m->selectDB("test")->selectCollection("puppies");
$pipeline = array(
// Create a document stream (one per puppy)
array('$unwind' => '$litter'),
// Sort by birthdate descending
array('$sort' => array (
'litter.birth_timestamp' => -1
)),
// Limit to 5 results
array('$limit' => 5)
);
$results = $collection->aggregate($pipeline);
var_dump($results);
?>

Categories