geoNear with Pagination and sorting on created_at - mongodb

I´m having an App with the capability of listing articles within a user specified location + radius surrounding. See ruby code with mongo query below.
For Web App, it uses pagination based on "last item" (min_distance) and "excluded_ids" in combination with "limit", what I believe to be the most performant and also better than "limit" and "skip". This solution approach supports "previous page" and "next page". For "jump to page" it makes use of the combination of "limit" and "skip".
geoNear by nature sorts by "distance".
New requirement is to have, additionally, sort by "created_at" (user toggle).
My question:
For sort by "created_at" to work, will it be sufficient to add sort into the query? I´m afraid this won´t work for the current implemented pagination approach and I will need to somehow rewrite to have "last item" based on "created_at", rather than on "min_distance".
Can someone tell for sure how Mongo handles internally the sort and if simply adding sort for created_at will work with this type of pagination?
Or else, how this would look for replacing mind_distance for created_at.
Also, for supporting both sort types, what index/es is/are needed?
One for both, or one for each?
def self.articles_with_distance(params, excluded_ids=[])
if params[:min_distance].present?
Article.collection.aggregate([
{ :"$geoNear" => {
:near => {
:type => "Point",
:coordinates => [params[:lon].to_f, params[:lat].to_f],
},
:query => {_id: { :"$nin" => excluded_ids }}, # to exclude items already shown if min distance between two pages are same
:distanceField => "distance_from",
:maxDistance => params[:radius].to_i,
:minDistance => params[:min_distance].to_i, # only need to reduce the result set by exclude items had been shown previous pages
:spherical => true,
:limit => (params[:per_page].to_i || 10)
}}
]);
else
Article.collection.aggregate([
{ :"$geoNear" => {
:near => {
:type => "Point",
:coordinates => [params[:lon].to_f, params[:lat].to_f],
},
:distanceField => "distance_from",
:maxDistance => params[:radius].to_i,
:spherical => true,
#
}},
{
:"$skip" => (params[:per_page].to_i * (params[:page].to_i-1))
},
{
:"$limit" => (params[:per_page].to_i || 10)
}
]);
end
end

Related

MongoDB aggregation match conditions fails

I have a problem with aggregation function:
this is a screen from MongoDB Compass:
Category structure:
{
'title' => 'Sport'
'articles' => [
{
'title' => 'My title'
'status' => 'published'
}
]
....
}
In the screen you can see a filter in order to get all categories with at least an article with status published.
In the result I always see also category with 'status' => 'draft'. I do not understand why this filter is not working.
How can I solve this?
Thanks

How to do atomic operations like $push $pull $set etc. in Ecto MongoDB

I'm using mongodb_ecto and I want to know how can I do operations like $push or $pull on a deeply nested field? At the moment I write back the whole document which sometimes causes false data to be in the DB due to a race-condition.
Ok, I kind of figured it out. Do not use Ecto for this. In some cases you need the MongoDB positional operator and this can only be done directly via the Mongo-Adapter. Now for some usage examples:
I have a doucment with a list of options. Options have an ID, a label and list of userIDs who voted for this option.
BTW to generate an ObjectID (which is needed for talking directly to the MongoDB-Adapter) use this:
id = "584a5b9419d51d724d146e3f" # string form
value = (for <<hex::16 <- id>>, into: <<>>, do: <<String.to_integer(<<hex::16>>, 16)::8>>)
object_id = %BSON.ObjectId{value: value}
And now for some examples:
# update label of option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id}, # query
%{"$set" => %{"data.options.$.label" => new_label}} # update
)
# add new option to poll
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id},
%{"$addToSet" => %{"options" => %{label: label, id: random_new_id, votes: []}}}
)
# add user_id to option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id},
%{"$addToSet" => %{"options.$.votes" => user_id}}
)
# remove user_id form option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id},
%{"$pull" => %{"data.options.$.votes" => user_id}}
)

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 }

How to nest .or() and .and() conditions in Mongoid

I have a 'search' function where I want to pass in an arbitrary 'filter' condition and have matches returned
The following matches any name/email where the filter string is a match:
#people = Person.all
#people = #people.or(
{'name.first_name' => /#{filter}/i},
{'name.last_name' => /#{filter}/i},
{'email' => /#{filter}/i }
)
The following correctly does the same on the 'tags' array on the Person record:
#people = Person.all
#people = #people.any_in('tags' => [/#{filter}/i])
Can anyone tell me how to combine the two queries, so that a Person is matched if the filter text is found in the name, email or any of the tags?
It turns out there is a method I was missing here ... found indirectly via https://github.com/mongoid/mongoid/issues/2845
Given these two queryables:
a=Person.where({'name.first_name'=> /a/i})
b=Person.where({'name.first_name'=> /j/i})
You can combine them using .selector
Person.or(a.selector, b.selector).to_a
=> selector={"$or"=>[{"name.first_name"=>/a/i}, {"name.first_name"=>/j/i}]}
or
Person.and(a.selector, b.selector).to_a
=> selector={"$and"=>[{"name.first_name"=>/a/i}, {"name.first_name"=>/j/i}]}
You don't need to use any_in at all. If say:
:array_field => regex
then MongoDB will automatically check each element of array_field against regex for you, you don't have care about the arrayness at all. That means that you can toss the :tags check in with the other conditions:
regex = /#{filter}/i
#people = Person.where(
:$or => [
{ 'name.first_name' => regex },
{ 'name.last_name' => regex },
{ 'email' => regex },
{ 'tags' => regex }
]
)
I also pull the regex out of the query into a variable to make it clear that you're using the same one for each check and I switched to where as that's more common (at least in my experience).

Searching using a list of IDs in DBIx::Class

I have a list with IDs that are selected by the user.
What is the best way to search all rows using this list of IDs in DBIx::Class??
Use
$rs->search({
whatever_the_column_is => {
'=' => [ #a_bunch_of_ids ]
}
})
or
$rs->search({
whatever_the_column_is => {
-in => [ #a_bunch_of_ids ]
}
})
if your DB likes IN queries better. Both are documented in SQL::Abstract docs.