Mongo distinct sort query using PHP driver - php

Having some trouble issuing a simple query from the PHP Mongo driver.
This is the shell query:
db.testing.distinct('summary.type').sort();
According to the documentation, I can do a db->command to use the distinct query. Here is what I've tried:
$all_types = $db->command(
array(
"distinct" => "summary.type",
"key" => "summary.type"
)
);
var_dump($all_types);
But rather than getting back the data I expect, I only get a cursor with indexes like stats, nscanned and nscannedObjects
What am I doing wrong?
Thanks,
ns

Commands don't return a MongoCursor object, but always just one document. Just like with findOne(). In this returned document, the values array element in the returned array contains an array of all your results:
<?php
$m = new Mongo; $c = $m->demo->pubs; $c->drop();
$c->insert( array( 'name' => 'Betsy Smith', 'city' => 'London' ) );
$c->insert( array( 'name' => 'London Tavern', 'city' => 'London' ) );
$c->insert( array( 'name' => 'Lammars', 'city' => 'Manchester' ) );
$c->insert( array( 'name' => 'Weatherspoons', 'city' => 'Coventry' ) );
$r = $m->demo->command( array(
'distinct' => 'pubs',
'key' => 'city',
'query' => array( 'name' => array( '$ne' => 'Weatherspoons' ) )
) );
var_dump( $r['values'] );
?>
Which returns:
array(2) {
[0] =>
string(6) "London"
[1] =>
string(10) "Manchester"
}

The result you're looking for is actually in the values key of the command's response. If you look at the entire output of var_dump(), you'll see keys for values, stats (which you referred to), and ok.
On the JavaScript console, the collection's distinct() helper function actually handles the unwrapping for you, which you can see when viewing the source:
> db.testing.distinct
function (keyString, query) {
var res = this._distinct(keyString, query);
if (!res.ok) {
throw "distinct failed: " + tojson(res);
}
return res.values;
}
Also, I believe you have an error in the command being issued through the PHP driver. The distinct option should refer to the collection name, and key should refer to the field name. See the Distinct docs for more information on that.

Related

PHP Mongo Aggregate exception: pipeline element 0 is not an object

I'm trying to get the number of users with the same email address.
I've followed the php documentation for MongoCollection Aggregate. I've also compared my query to the others on stackoverflow. As far as I can tell my pipeline matches the one in the examples. And yet I get the error:
exception: pipeline element 0 is not an object
Which I presume is the $group element, which is an object (once converted to the json format mongo uses, isn't it?
$pipeline = array(
array(
'$group' => array(
'_id' => array( 'email' => '$email' ),
'uniqueIds' => array( '$addToSet' => '$_id' ),
'count' => array( '$sum' => 1 )
)
),
array(
'$match' => array(
'count' => array( '$gt' => 1 )
)
)
);
$options = array(
'allowDiskUse' => true
);
$results = $mongo->users_collection->aggregate( $pipeline, $options );
What have I done wrong? I've even tried running the query from php.net, I of course expected no results, but it resulted in the same error!?
EDIT so turns out it's when I allowDiskUse, but if I remove the $options from the function I get an error telling me to use it.
Thanks
I found a solution to this
$results = $mongo->command(array(
'aggregate' => 'users_collection',
'pipeline' => $pipeline,
'allowDiskUse' => true
));
It seems to me that because php allows two methods of doing this (from php.net)
public array MongoCollection::aggregate ( array $pipeline [, array $options ] )
public array MongoCollection::aggregate ( array $op [, array $op [, array $... ]] )
It is seeing my $options array as another operation $op. Maybe this is to do with the version of Mongo Module we're using or something along those lines, but changing to $mongo->command() works as a workaround.

MongoDB Nested $or query

Example of a Mongo Entry:
array(
'name' => 'blog one',
'blogCategory' => array(
'displayAndLightMeasurement' => '1',
'LEDAndDisplayTestInstrument' => '0'
)
);
A Query like this works fine:
$blogInfoRaw = $collection->find(array('blogCategory' => array('displayAndLightMeasurement' => '1')));
When I try to '$or' query like this:
$blogInfoRaw = $collection->find(array('$or' => array('blogCategory' => array('displayAndLightMeasurement' => '1')),array('blogCategory' => array('LEDAndDisplayTestInstrument' => '1'))));
I get this error:
$or requires nonempty array
What am I doing wrong?
You really meant to use "dot notation" to reference the embedded fields:
$blogInfoRaw = $collection->find(
array(
'$or' => array(
array( 'blogCategory.displayAndLightMeasurement' => '1' ),
array( 'blogCategory.LEDAndDisplayTestInstrument' => '1')
)
)
);
Otherwise the notation you are using implies that the "only" elements present in the embedded level are those that you specify. This is not true since there are multiple keys. So "dot notation" solves this problem by referencing the distinct keys.
PHP array notation does not help here, but the $or needs to be a wrapping "real" array as in [] also.
Issuing a json_encode often helps when comparing to the official MongoDB examples.

MongoDB PHP accent problems

Assume that I have inserted the following document with PHP (Note the "ó".)
$dbs->insert(array('name' => 'televisión'));
In mongodb database server is saved as follows
{ "name" : "televisi��n" }
If I invoke the findOne method as follow, (NOTE THE ó)
$doc = $dbs->findOne(array('name' => "televisión"));
It return me the correct value
[name] => televisión
Everything fine until here.
So, imagine that from php I need to determine that the document televisión is into mongodb database, but I get the value from an URL without the accent "ó", i.e. television, so.
$doc = $dbs->findOne(array('name' => "television"));
findOne method is returning null, so don't match the document.
Is there any way for this not return null value and can find the document regardless of the accent?
Thanks in advance!
In mongodb database server is saved as follows
{ "name" : "televisi��n" }
That's probably because your shell doesn't show UTF-8 properly.
As for:
Is there any way for this not return null value and can find the document regardless of the accent?
You can do that with the new text search functionality:
<?php
$m = new MongoClient;
$d = $m->test;
$c = $d->so;
// Just dropping here to create a controlled output - no need to do this yourself.
$c->drop();
$c->ensureIndex(
array( 'name' => 'text' ),
array( 'default_language' => 'spanish' )
);
$c->insert( array('name' => 'televisión' ) );
$res = $d->command( array( 'text' => 'so', 'search' => 'television' ) );
var_dump( $res['results'] );
?>
Which outputs:
array(1) {
[0] =>
array(2) {
'score' =>
double(1)
'obj' =>
array(2) {
'_id' =>
class MongoId#6 (1) {
...
}
'name' =>
string(11) "televisión"
}
}
}
For text search to work, you need MongoDB 2.4.x, and you need to specifically enable it with the --setParameter textSearchEnabled=true flag to mongod or add to your code:
$d->command( array( 'setParameter' => 1, 'textSearchEnabled' => true ) );

get mongodb _id object after upsert with php

is it possible to get the new/updated _id after the query?
example code:
$key = array( 'something' => 'unique' );
$data = array( '$inc' => array( 'someint' => 1 ) );
$mongodb->db->collection->update( $key, $data, array( 'upsert' => true ) );
$key is not holding the new/old _id object and i assume that $data will not either because its just an instruction.
Yes -- It is possible using a single query.
MongoDB includes a findAndModify command that can atomically modify a document and return it (by default it actually returns the document before it's been modified).
The PHP drivers don't include a convenient method for this on the collection class (yet -- check out this bug), but it can still be used (note that my PHP is terrible, so I may very well have made a syntax error in the following snippet):
$key = array( 'something' => 'unique' );
$data = array( '$inc' => array( 'someint' => 1 ) );
$result = $mongodb->db->command( array(
'findAndModify' => 'collection',
'query' => $key,
'update' => $data,
'new' => true, # To get back the document after the upsert
'upsert' => true,
'fields' => array( '_id' => 1 ) # Only return _id field
) );
$id = $result['value']['_id'];
Just in case someone stumbles across this question like I did, Mongo will actually modify the input array when you call MongoCollection->save(); - appending the id to the end.
So, if you call:
$test = array('test'=>'testing');
mongocollection->save($test);
echo $test['_id'];
You will have the mongo id for that object.
I ran into this issue and worked around it by querying back the _id after the upsert. I thought I'd add some of my findings in case they're useful to anyone who comes here searching for info.
When the upsert results in a new document being created in the collection, the returned object contains the _id (here's a print_r of an example):
Array
(
[updatedExisting] => 0
[upserted] => MongoId Object
(
[$id] => 506dc50614c11c6ebdbc39bc
)
[n] => 1
[connectionId] => 275
[fsyncFiles] => 7
[err] =>
[ok] => 1
)
You can get the _id from this:
$id = (string)$obj['upserted'];
However, if the upsert resulted in an existing document being updated then the returned object does not contain _id.
Give this a shot :
function save($data, $id = null) {
$mongo_id = new MongoId($id);
$criteria = array('_id' => $mongo_id);
// Wrap a '$set' around the passed data array for convenience
$update = array('$set' => $data);
$collection->update($criteria, $update, array('upsert' => true));
}
So lets say the passed $id is null, a fresh MongoId is created, otherwise it just converts the existing $id to a MongoId object.
Hope this helps :D
The update method returns an array with the ID of the document UPSERTED:
Array
(
[ok] => 1
[nModified] => 0
[n] => 1
[err] =>
[errmsg] =>
[upserted] => MongoId Object
(
[$id] => 5511da18c8318aa1701881dd
)
[updatedExisting] =>
)
You can also set fsync to true in an update/upsert, to get the _id returned to the object that has been passed to the update.
$save = array ('test' => 'work');
$m->$collection->update(criteria, $save, array('fsync' => true, 'upsert' => true));
echo $save['_id']; //should have your _id of the obj just updated.

PHP / Mongo: how do you update nested data?

I've been playing around with Mongo for about a week now and I still can't work out how to modify nested arrays in Mongo with php.
So here is a sample document...
array (
'_id' => new MongoId("4cb30f560107ae9813000000"),
'email' => 'mo#maurice-campobasso.com',
'firstname' => 'Maurice',
'lastname' => 'Campobasso',
'password' => 'GOD',
'productions' =>
array (
0 =>
array (
'title' => 'a',
'date' => '1286811330.899',
),
1 =>
array (
'title' => 'b',
'date' => '1286811341.183',
),
2 =>
array (
'title' => 'c',
'date' => '1286811350.267',
),
3 =>
array (
'title' => 'd',
'date' => '1286811356.05',
),
),
)
What I wan't to do is delete an array inside the productions array, but I can't work out how. I've been playing with 'update('$pull' => ...etc)' but I haven't been able to make it work.
OK, there are a few ways to do this. In your case, I would do something like
mymongoobject.update( $unset : { "productions.2" : 1 } }
That's basically saying to unset the ".2" element of productions. Some docs here.
Now $pull should also work, but it's a little tougher because "productions" is actually an array of arrays (or objects with sub-objects). So you'd have to match arrays exactly:
mymongoobject.update( $pull : { "productions" : {'title':'d', 'date':'1286811356.05'} }
In the case above, the unset is probably the easiest option (though it will leave a "hole" in the array)
That is actually very easy, unlike traditional sql stuff you just modify the whole data and pass it back.
$cursor = $mongo->yourDB->yourCollection->findOne("_id",4cb30f560107ae9813000000);
//let's remove last item on productions
array_splice($cursor["productions"],2);
//and update the mongo document
echo $mongo->yourDB->yourCollection->update($cursor);
//it echoes 1 if successful
hope it helps.

Categories