PHP, Mongo query using or and dot notation - mongodb

I've a problem that's getting me crazy!
I need to translate the following Mongo query in PHP:
db.mycollection.find({$or: [ {'field.a': 45.4689, 'field.b': 9.18103}, {'field.a' : 40.71455, 'field.b': -74.007124} ]})
It works perfectly from the shell.
I think the query should be translated to PHP in the following way (var_dump):
Array
(
[$or] => Array
(
[0] => Array
(
[field.a] => 45.468945
[field.b] => 9.18103
)
[1] => Array
(
[field.a] => 40.71455
[field.b] => -74.007124
)
)
)
but I get no results in PHP!
Why?
What's wrong?
What's the correct syntax?
Thank you!

Your syntax seems fine to me, I think the main problem is is that you are using floating point numbers which are not always as accurate as you think—especially if you mix up 45.4689 and 45.468945 yourself. For direct comparing of floating point numbers, you should always add a small fuzzing factor.
In this case you seem to be using coordinates? If that's the case, I suggest you:
swap the a and b fields (so that you get longitude, then latitude)
create a 2d index on "field": ensureIndex( array( 'field' => '2d' ) );
use geoNear with a small max distance and a limit of 1,
That should give a much better way of scanning for points. You'd have to run the query twice though, as the geoNear command can't do $or.
If you only have a discrete set of points (like your comments seems to indicate), then I would recommend to not query on floating point numbers but simply add a field naming the city as well.

Related

Algolia Facet sorting numerically

I'm using the algoliasearch and algoliasearchHelper libraries to build an instant search interface using the hogan template example on the algolia site.
I'm having an issue with sorting a numerical facet. I'm populating the index using the algoliasearch-client-php installed via composer. I'm passing an integer into the index object like so:
"cost_to_build" => (int) $project->data['approximate_cost'],
But in the index, I'm getting something like:
cost_to_build: "15.00"
which then results in a facet order like:
15, 25, 3, 5, 6.
Even thoguh {sortBy: ['name:asc']}. If I manually change all of my index values to integers from strings (too many to really do manually, plus we update it regularly), the sorting works as desired.
Anyone have any tips?
Thanks!
The fact that the value gets transformed from integer to strings is really surprising in itself and I don't have a clue on why this would happen.
However, there is still an easy solution without fixing the root cause. The sortBy parameter is also able to accept a comparison function, so you can cast those values to integers in your front-end instead of your back-end.
Something along those lines should work :
helper.on('results', function(content){
//get values ordered only by count ascending using a function
content.getFacetValues('cost_to_build', {
sortBy: function(a, b) {
return parseInt(a.name, 10) - parseInt(b.name, 10);
}
});
});

MONGODB: Inconsistent query results (corrupted db?)

Our app is running smoothly for about 2 years now (at least in the following aspect).
But some weeks ago some of our clients started complaining about a relevant count in the system. We investigated it, reduced the example to a minumum case, and ended up with this weird situation:
db.my_collection.find().count()
=> 250088
db.my_collection.distinct("field_1")
=> [false, true]
db.my_collection.find({"field_1":{$nin:[true, false]}}).count()
=> 0
db.my_collection.find({"field_1":{$in:[true, false]}}).count()
=> 250088
db.my_collection.find({"field_1":true}).count()
=> 140357
db.my_collection.find({"field_1":false}).count()
=> 109731
(140357 + 109731 = 250088, great!)
db.my_collection.find({"field_2":null}).count()
=> 4638
db.my_collection.find({"field_2":{$ne:null}}).count()
=> 245450
(4638 + 245450 = 250088, great!)
db.my_collection.find({"field_2":{$ne:null}, "field_1":{$in:[true, false]}}).count()
=> 245450 (great!)
But look at this:
db.my_collection.find({"field_2":null, "field_1":false}).count()
=> 2669
db.my_collection.find({"field_2":null, "field_1":true}).count()
=> 790
2669 + 790 = 3459 (should sum 4638 !!)
db.my_collection.find({"field_2":{$ne:null}, "field_1":false}).count()
=> 75795
db.my_collection.find({"field_2":{$ne:null}, "field_1":true}).count()
=> 107298
75795 + 107298 = 183093 (should sum 245450 !!)
Also1: This problem only happens with this specific fields composition in the query. If we compose each of these two fields with others, everything works fine (we didn't make an exaustive search here. But we tried a bunch of them)
Also2: We tried to make the queries with various diffeten hint values (including {$natural:1}), to see if it could be a corrupted index, but it is still broken.
Also3: We resync'd one of our seccondaries, and the error persists.
Also4: We coppied the collection, document by document, through the console and a find().forEach({insert}) query, (same db version) and IT WORKS CORRECTLY!!!
Also5: we're using mongodb 2.4.8.
WTF might be happening?
What should I do to correct it RIGHT?
What should I do to correct it FAST?
===== UPDATE 18-sep
We found out that we had some indexes with a key that was too large and mongodb 2.4 just logs and ignores it. So the index was silently breaking. (looks like mongo 2.6 solves this in a more polished way: http://docs.mongodb.org/master//release-notes/2.6-compatibility/#index-key-length-incompatibility )
We dropped those indexes (they were just some tests we made and forgot) and the queries came back home.
We just did not realize yet why we could not get right results when giving hints, even the $natural:1...

Mongoid limit parameter ignored

I tried to grab the latest N records with a unique value (first_name).
So far:
#users = User.all(:limit => 5, :sort => [:created_at, :desc]).distinct(:first_name)
almost works..But ignores the limit and sort order
Also:
#users = User.limit(5).desc(:created_at).distinct(:first_name)
Ignores both 'limit' and 'desc'
#users = User.limit(5)
Works..
What am I doing wrong?
Any help would be greatly appreciated!
I played with this for a little while and this is the best I could come up with.
Good luck.
#users = User.desc(:created_at).reduce([]) do |arr, user|
unless arr.length == 5 || arr.detect{ |u| u.first_name == user.first_name }
arr << user
end
arr
end
Have you tried using a pagination gem such as amatsuda / kaminari and limiting the results using page().per()?
Both distinct and count ignore the limit command in Mongoid. With count you can pass true (i.e. User.limit(5).count(true)) to force it to pay attention to the scope. Unfortunately there is no such trick for distinct as far as I'm aware (see docs/source here).
If you want to just grab the first 5 first_name's you can do this (not distinct):
User.desc(:created_at).limit(5).map(&:first_name)
This will respect the limit, but still load 5 full objects from the database (then discard the rest of the object to give you full name). If you actually need to run distinct, you're better off heading toward an aggregation framework solution.
I haven't tested, but this seems to be what you're looking for: https://stackoverflow.com/a/17568267/127311
I played with some result I found this.
User.limit(2).count => 10 #but in array I found only two results
User.limit(2).to_a.count => 2
May be limit gives the correct result, but count query gives wrong result.

PHP mongoDb driver , fetching data and then deleting it , is not working

This is the sample code :
$r = $coll->findOne();
$coll->remove(array("_id"=>$r["_id"])); // use the same object id as retreived from DB
$ret=$coll->findOne(array("_id"=>($r["_id"])));
var_dump($ret); // dumps the records that was supposed to be deleted
The records in the collection have MongoDB objectId, and not strings.
Same logic on console works fine and deleted the record correctly.
This is working for me. Here's the code:
$coll->drop();
print("Now have ".$coll->count()." items\n");
$coll->insert(array("x" => 'blah'));
$coll->insert(array("x" => "blahblah"));
print("Inserted ".$coll->count()." items\n");
$x = $coll->findOne();
print("Object X\n");
print_r($x);
$query_x = array('_id' => $x['_id']);
$coll->remove($query_x);
print("Removed 1 item, now have ".$coll->count()." items\n");
$y = $coll->findOne($query_x);
print("Object Y\n");
print_r($y);
Here's the output:
Now have 0 items
Inserted 2 items
Object X
Array
(
[_id] => MongoId Object
(
[$id] => 4d8d124b6803fa623b000000
)
[x] => blah
)
Removed 1 item, now have 1 items
Object Y
Are you sure there's not a typo somewhere?
Unlike the php == operator, mongo's equality operator always uses "Object equality" which is like php's identical comparison operator (===) or java's .equals(). While your code looks as though it should work (and it does work fine for me with a test dataset), something about your dataset may be causing php to cast the returned MongoId to a string. Read more about MongoId here.
Make sure that your query is supplying a MongoId for comparison by doing a var_dump of the query itself. Also, make sure that you are running the latest version of the PHP Mongo driver.
Since PHP is loosely typed it is most important to ensure that you cast all input values and search values to the expected and consistent data type otherwise it will surely not locate your expected document.
While using MongoDB within PHP I make it a point to cast everything intentionally in order to avoid any possible confusion or error.
Also, the mongodb-user group at groups.google.com is very good and responsive so if you are not already utilizing that resource I would definitely consider joining it.

restrict documents for mapreduce with mongoid

I implemented the pearson product correlation via map / reduce / finalize. The missing part is to restrict the documents (representing users) to be processed via a filter query. For a simple query like
mapreduce(mapper, reducer, :finalize => finalizer, :query => { :name => 'Bernd' })
I get this to work.
But my filter criteria is a little bit more complicated: I have one set of preferences which need to have at least one common element and another set of preferences which may not have a common element. In a later step I also want to restrict this to documents (users) within a certain geographical distance.
Currently I have this code working in my map function, but I would prefer to separate this into either query params as supported by mongoid or a javascript function. All my attempts to solve this failed since the code is either ignored or raises an error.
I did a couple of tests.
[results deleted, see below]
I'm using ruby 1.9.2, mongodb 1.6.5-x86_64, and the mongoid 2.0.0.beta.20, mongo 1.1.5 and bson 1.1.5 gems on MacOS.
What am I doing wrong?
Thanks in advance.
More Tests (problem partly solved):
User.where(:name.in => ["Arno", "Bernd", "Claudia"])
works, however when using mapreduce like this
mapreduce(..., :query => { :name.in => ['Arno', 'Bernd', 'Claudia'] })
results in the bson error serialize: keys must be strings or symbols (TypeError) mentioned above.
Replacing :name.in with 'name.in' or 'name.$in' makes the query return no results (guess this is passed as is to mongodb).
However
mapreduce(..., :query => { :name => { '$in' => ['Arno', 'Bernd', 'Claudia'] } })
works, but I didn't have any success with my geospatial query attempts, no matter how I wrote the expression.
mapreduce(..., :query => {:location => { "$near" => [ 13, 52, 1 ] } })
results in this error_message: Database command 'mapreduce' failed: {"assertion"=>"manual matcher config not allowed",....
If anyone could give me an idea how to write a geospatial query using near or within and working with mapreduce I would be very happy. (I didn't play with sets yet, see above).