mongodb php: count the number of field in a set of documents - php

I am using codeigniter with mongodb library
https://github.com/intekhabrizvi/Codeigniter-mongo-library
below is my collection "users".
I want to count all the badges which has badge_slug = 100_club from users collection.
What I have tried is
$this->mongo_db->where(array('badges.badge_slug'=>"100_club"))->count('users');
but it only gives the number of users have 100_club no matter its more then one time.
{
"_id" : ObjectId("57b83ae9faa76bac338b4579"),
"displayname" : "test",
"email" : "test#gmail.com",
"badges" : [
{
"awarded_at" : ISODate("2015-04-21T05:52:06Z"),
"object_id" : "",
"badge_slug" : "100_club"
},
{
"awarded_at" : ISODate("2015-04-21T06:12:14Z"),
"object_id" : "",
"badge_slug" : "100_club"
},
{
"awarded_at" : ISODate("2015-04-21T07:09:55Z"),
"object_id" : "",
"badge_slug" : "reader"
}
]
}
{
"_id" : ObjectId("57b83ae9faa76bac338b457a"),
"displayname" : "test2",
"email" : "test2#gmail.com",
"badges" : [
{
"awarded_at" : ISODate("2015-04-21T06:44:20Z"),
"object_id" : "",
"badge_slug" : "100_club"
}
]
}
Can you please let me know how can I get the total number of 100_club occurred in the users collection.

The aggregation approach follows where you can use the $filter and $size operators to get the count per document and then group all the documents to get the total count. This approach doesn't require using the $unwind operator to flatten the badges array but works with MongoDB version 3.2 and greater:
mongo shell
var ops = [
{
"$project" {
"count": {
"$size": {
"$filter": {
"input": "$badges",
"as": "badge",
"cond": { "$eq": ["$$badge.badge_slug", "100_club"] }
}
}
}
}
},
{
"$group": {
"_id": null,
"total": { "$sum": "$count" }
}
}
];
db.users.aggregate(ops);
PHP
$ops = array(
array(
"$project" => array(
"count" => array(
"$size" => array(
"$filter" => array(
"input" => "$badges",
"as" => "badge",
"cond" => array("$eq" => => array("$$badge.badge_slug", "100_club") )
)
)
)
)
),
array(
"$group" => array(
"_id" => null,
"total" => array( "$sum" => "$count" )
)
)
);
$this->mongo_db->aggregate("users", $ops);
For an approach that uses the $unwind operator to flatten the badges array first before grouping, follow this example:
mongo shell
db.users.aggregate([
{ "$match": { "badges.badge_slug": "100_club" }
{ "$unwind": "$badges" },
{ "$match": { "badges.badge_slug": "100_club" },
{
"$group": {
"_id": null,
"total": { "$sum": 1 }
}
}
])

Related

How to add element to nested array in mongo 3.4 using php

I am trying to push array element into inner array of the following document structure
{
"_id" : ObjectId("599558a4a331801fe264bbb3"),
"zone" : "asia",
"country_list" : [
{
"country_name" : "india",
"state_list" : [
{
"state_name" : "kerala"
},
{
"state_name" : "tamilnadu"
}
]
},
{
"country_name" : "pak",
"state_list" : [
{
"state_name" : "ins"
}
]
}
]
}
the structure after update i want to be as
{
"_id" : ObjectId("599558a4a331801fe264bbb3"),
"zone" : "asia",
"country_list" : [
{
"country_name" : "india",
"state_list" : [
{
"state_name" : "kerala",
"city_list" : [
{
"cityname" : "tvm"
},
{
"cityname" : "kchi"
},
]
},
{
"state_name" : "tamilnadu"
}
]
},
{
"country_name" : "pak",
"state_list" : [
{
"state_name" : "ins"
}
]
}
]
}
However when I try the following code
$result = $this->collection->findOneAndUpdate(array(
"country_list.state_list.state_name"=>$this->getState()
), array(
'$push'=>array("country_list.state_list.$.city_list"=>array("city_name"=>$this->getCity()))
));
I get the error
Uncaught MongoDB\Driver\Exception\RuntimeException: cannot use the part (country_list of country_list.state_list.0.city_list) to traverse the element ({country_list: [ { country_name: "india", state_list: [ { state_name: "kerala" }, { state_name: "tamilnadu" } ] }, { country_name: "pak", state_list: [ { state_name: "ins" } ] } ]})
I am using mongo 3.4 and php 7
Reason of the Error: country_list is an array so you have to use positional operator with $set or $push statement.
Solution: In MongoDB 3.4, positional operator ($) can be used only once so what you want cannot be achieved with positional operator. You need to loop through in the client code (PHP), prepare the updated document and then collection update.
FYI - In 3.6, positional operator is improved so you can use that after upgrading. https://jira.mongodb.org/browse/SERVER-831

MongoCursorTimeoutException with sort on _id

I have an Mongo collection containing ~7 millions events. To get the events that happend for an aggregate I have the following PHP code
$client = new MongoClient();
$db = $client->selectDB('db_name');
$collection = $db->selectCollection('events');
foreach($collection->find([
'headers.for' => '89d115f8-0b2f-470e-9495-2a07d9dfb942',
])->sort([
'headers.occurredOn' => 1,
'_id' => 1,
]) as $event) {
var_dump($event);
}
When I run the above PHP code I get an MongoCursorTimeoutException after 30 seconds.
But when I run the same code without a sort on _id, so:
$client = new MongoClient();
$db = $client->selectDB('db_name');
$collection = $db->selectCollection('events');
foreach($collection->find([
'headers.for' => '89d115f8-0b2f-470e-9495-2a07d9dfb942',
])->sort([
'headers.occurredOn' => 1,
]) as $event) {
var_dump($event);
}
The error does not occur and get instant results (which is one record).
So why does a MongoCursorTimeoutException occur when a sort on _id is added?
The indexes for the collection looks as follow
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "db.events"
},
{
"v" : 1,
"key" : {
"headers.occurredOn" : NumberLong(1),
"_id" : NumberLong(1)
},
"name" : "headers_occurredOn_1__id_1",
"ns" : "db.events"
},
{
"v" : 1,
"key" : {
"headers.for" : NumberLong(1)
},
"name" : "headers_for_1",
"ns" : "db.events"
}
]
Well the problem was the indexes I had.
{
"v" : 1,
"key" : {
"headers.occurredOn" : NumberLong(1),
"_id" : NumberLong(1)
},
"name" : "headers_occurredOn_1__id_1",
"ns" : "db.events"
}
After I dropped this one and added the following:
{
"v" : 1,
"key" : {
"headers.occurredOn" : NumberLong(1)
},
"name" : "headers.occurredOn_1",
"ns" : "db.events"
}
Everything went smooth

MongoDB update sub document issue

I'm having Mongo structure like this,
"campaigns":[{
"summary" :
[ {
"postcode" : [ {
"id" : "71",
"name" : "Recall 1",
"qty" : 3,
"history" :
[ { "timestamp" : "2014-12-16 11:15:32",
{ "timestamp": "2014-12-16 11:15:53"
} ]
} ]
},
{
"postcode" :
[ {
"id" : "72",
"name" : "Recall 2",
"qty" : 1,
"history" : [ { "timestamp" : "2014-12-16 11:15:53" } ]
} ]
}]
I'm trying to i) increment qty of postcode.id : 72 ii) insert another timestamp for that same postcode id 72.
My code:
$collection->update(array("campaigns.task.id" => $b,"_id"=> new MongoId($objectid),"campaigns.0.summary.0.postcode.0.id" => $a), array('$inc' => array('campaigns.0.summary.0.postcode.0.qty' => 1)));
$collection->update(array("campaigns.task.id" => $b,"_id"=>new MongoId($objectid),"campaigns.0.summary.0.postcode.0.id" => $a),
array('$addToSet' => array('campaigns.0.summary.0.postcode.0.history' => array("timestamp"=>$now->format('Y-m-d H:i:s')))));
but postcode.id = 72 not gets updated, i'm confused with this nested subdocument can anyone suggest me the solution ?

Mongo php Can't canonicalize query: BadValue bad geo query'

I'm trying to build a query to search geospatial data in mongo using php-mongo library.
Object structure
{
"_id" : ObjectId("536ecef59bdc8977f1e9c06f"),
"location" : {
"type" : "Point",
"coordinates" : [
[
-74.220408,
40.727703
]
]
},
"bounds" : {
"type" : "Polygon",
"coordinates" : [
[
[
-74.221403,
40.728457
],
[
-74.219413,
40.728457
],
[
-74.219413,
40.726949
],
[
-74.221403,
40.726949
],
[
-74.221403,
40.728457
]
]
]
},
"tile" : {
"type" : "Polygon",
"coordinates" : [
[
[
-74.220498,
40.727772
],
[
-74.220318,
40.727772
],
[
-74.220318,
40.727634
],
[
-74.220498,
40.727634
],
[
-74.220498,
40.727772
]
]
]
},
"status" : "2",
"dev" : "0"
}
Indexes
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "atlantic.Analyzer_Marks"
},
{
"v" : 1,
"key" : {
"location.coordinates" : "2d"
},
"name" : "location.coordinates_2d",
"ns" : "atlantic.Analyzer_Marks"
},
{
"v" : 1,
"key" : {
"bounds" : "2dsphere"
},
"name" : "bounds_2dsphere",
"ns" : "atlantic.Analyzer_Marks",
"2dsphereIndexVersion" : 2
},
{
"v" : 1,
"key" : {
"tile" : "2dsphere"
},
"name" : "tile_2dsphere",
"ns" : "atlantic.Analyzer_Marks",
"2dsphereIndexVersion" : 2
}
]
$mongo = new MongoClient("mongodb://someserver.com:27017");
$marks = $mongo->selectDB('atlantic');
$q = array('bounds' => array(
'$geoWithin' => array(
'$geometry' => array(
'type' => 'Polygon',
'coordinates' => array(array(
array(40.125246,-74.327963),
array(40.125246,-74.325989),
array(40.123738,-74.325989),
array(40.123738,-74.327963),
array(40.125246,-74.327963)
))
)
)
)
);
$marks = new MongoCollection($marks,'Analyzer_Marks');
$marks = $marks->find($q);
//var_dump($marks);
$results = array();
if(!empty($marks)){
foreach($marks as $mark) {
$results[] = array(
"location" => $mark['location'],
"tile" => $mark['tile'],
"status" => $mark['status']
);
}
}
This is the error I get:
Fatal error: Uncaught exception 'MongoCursorException' with
message 'someserver.com:27017: Can't canonicalize query: BadValue bad
geo query' in /var/www/html/one-call/lib/somefile.php:97
MongoDB Version 2.6.1
I can reproduce that error. I think you are missing one more array call wrapping around the array of coordinates:
$mongo = new MongoClient("mongodb://someserver.com:27017");
$marks = $mongo->selectDB('atlantic');
$q = array('bounds' => array(
'$geoWithin' => array(
'$geometry' => array(
'type' => 'Polygon',
'coordinates' => array(
array(
array(40.125246,-74.327963),
array(40.125246,-74.325989),
array(40.123738,-74.325989),
array(40.123738,-74.327963),
array(40.125246,-74.327963)
)
)
)
)
)
);
after modifying the code I don't receive that error anymore

I can not get data to aggregate It. PHP + mongoDB

It works like I need:
$out = $collection->aggregate(
array(
'$match' => array('type' => 'chair')
),
array(
'$project' => array(
'chairtype' => 1,
'mijczjeqeo'=>1
)
),
array(
'$group' => array(
'_id' => '$chairtype',
'MIDDLE_mijczjeqeo' => array('$avg' => '$mijczjeqeo'),
'SUMMA__mijczjeqeo' => array('$sum' => '$mijczjeqeo')
)
)
);
my_dump($out);
But i need to get true data for aggregation from array in the same documents: versions[0][content][mijczjeqeo]
Please correct my script. It does not work:
$out = $collection->aggregate(
array(
'$match' => array('type' => 'chair')
),
array(
'$project' => array(
'chairtype' => 1,
'versions.0.content.mijczjeqeo'=>1
)
),
array(
'$group' => array(
'_id' => '$chairtype',
'MIDDLEmijczjeqeo' => array('$avg' => '$versions.0.content.mijczjeqeo'),
'SUMMAmijczjeqeo' => array('$sum' => '$versions[0]["content"]["mijczjeqeo"]')
)
)
);
no one method does not work:
'MIDDLEmijczjeqeo' => array('$avg' => '$versions.0.content.mijczjeqeo')
'SUMMAmijczjeqeo' => array('$sum' => '$versions[0]["content"]["mijczjeqeo"]')
I think the problem near .0.
I try to do it in mongo console...
db.documents.aggregate({$match:{'type':'chair'}},{$project:{'chairtype': 1, 'mijczjeqeo':1}},{$group:{'_id':'$chairtype','MID':{$avg:'$mijczjeqeo'}}})
{
"result" : [
{
"_id" : "T",
"MID" : 6.615384615384615
},
{
"_id" : "G",
"MID" : 8.310344827586206
},
{
"_id" : "E",
"MID" : 6.9523809523809526
}
],
"ok" : 1
}
db.documents.aggregate({$match:{'type':'chair'}},{$project:{'chairtype': 1, 'versions.0.content.mijczjeqeo':1}},{$group:{'_id':'$chairtype','MID':{$avg:'$versions.0.content.mijczjeqeo'}}})
{
"result" : [
{
"_id" : "T",
"MID" : 0
},
{
"_id" : "G",
"MID" : 0
},
{
"_id" : "E",
"MID" : 0
}
],
"ok" : 1
}
Well you cannot project like that in the aggregation pipeline. If you want to act on array elements within an aggregation statement you first need to $unwind the array and then either $match the required element(s) or as in your case choose the $first item using an additional $group stage.
Your question does not show the structure of a document so I'll just use a sample, as my "chairs" collection:
{
"_id": 1,
"type": "chair",
"chairtype": "A",
"versions": [
{
"revision": 1,
"content": {
"name": "ABC",
"value": 10
}
},
{
"revision": 2,
"content": {
"name": "BBB",
"value": 15
}
}
]
}
{
"_id": 2,
"type": "chair",
"chairtype": "A",
"versions": [
{
"revision": 1,
"content": {
"name": "CCC",
"value": 20
}
},
{
"revision": 2,
"content": {
"name": "BAB",
"value": 12
}
}
]
}
Minimal, but enough to get the point. Now the aggregate statement:
db.chairs.aggregate([
// Normal query matching, which is good
{ "$match": { "type": "chair" } },
// Unwind the array to de-normalize
{ "$unwind": "$versions" },
// Group by the document in order to get the "first" array element
{ "$group": {
"_id": "$_id",
"chairtype": { "$first": "$chairtype" },
"versions": { "$first": "$versions" }
}},
// Then group by "chairtype" to get your average values
{ "$group": {
"_id": "$chairtype",
"MID": {"$avg": "$versions.content.value"}
}}
])
Of course if your actual document has nested arrays then you will be "unwinding" and "matching" the required elements. But that is the general process of "narrowing down" the array contents to the elements you need.

Categories