With MongoDB is there a way to perform a bounds query (within a box) and order the results from the center point and have them return with the distance calculation..
I realize doing a near with a radius can provide me with a distance ordered set but I want I'm trying to identify if it is possible within a box not a circle.
Unfortunately, this is not possible exactly as you described. As per the "Bounds Queries" section of the "Geospatial Indexing" documentation: "Results [of $within queries] are not sorted by distance"
http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-BoundsQueries
There are a few possible work-arounds.
1) You can perform a $within query and have your application calculate the distance of each point returned from the center of the box.
2) You could first perform a $within query, save the points in an array, and then run a $near query combined with an $in [].
For example,
Imagine a box with boundaries [0,0] and [8,8]:
> var box = [ [ 0, 0 ], [ 8, 8 ] ]
and some points:
> db.points.find()
{ "_id" : 1, "name" : "a", "loc" : [ 5, 4 ] }
{ "_id" : 2, "name" : "b", "loc" : [ 4, 2 ] }
{ "_id" : 3, "name" : "c", "loc" : [ 1, 4 ] }
{ "_id" : 4, "name" : "d", "loc" : [ 2, 7 ] }
{ "_id" : 5, "name" : "e", "loc" : [ 7, 7 ] }
{ "_id" : 6, "name" : "f", "loc" : [ 9, 4 ] }
First the $within query is done, and the points that are returned are saved:
> var c = db.points.find({loc:{$within:{"$box":box}}})
> var boxResults = []
> while(c.hasNext()){boxResults.push(c.next().loc)}
Then a $near query is combined with an $in query:
> db.points.find({loc:{$near:[4,4], $in:boxResults}})
{ "_id" : 1, "name" : "a", "loc" : [ 5, 4 ] }
{ "_id" : 2, "name" : "b", "loc" : [ 4, 2 ] }
{ "_id" : 3, "name" : "c", "loc" : [ 1, 4 ] }
{ "_id" : 4, "name" : "d", "loc" : [ 2, 7 ] }
{ "_id" : 5, "name" : "e", "loc" : [ 7, 7 ] }
The above solution was taken from a similar question that was asked on Google Groups:
groups.google.com/group/mongodb-user/browse_thread/thread/22d1f0995a628c84/1f2330694a7cf969
Hopefully this will allow you to perform the operation that you need to do.
#nick - Marc is incorrect. It is possible. Seem similar question asked here: Mongo Bounding Box Query with Limit
The secret is to combine both a point-radius query with a bounding box one. You get all the benefits of both (sorting, distance, performance).
Related
The mongo documentation on update change events says that the update description will have an array of removed fields, a document of update fields and an array of truncated arrays. The removed and updated fields are pretty straight forward, but I'm having trouble understanding what the truncated arrays are.
The documentation says
An array of documents which record array truncations performed with
pipeline-based updates using one or more of the following stages:
$addFields
$set
$replaceRoot
$replaceWith
But try as I might, I can't seem to figure out how to even cause an update event that includes truncated arrays.
Any help understanding what this field is for and / or an example of how to cause an update that includes it would be greatly appreciated.
I did not know that the change stream document had truncatedArrays field. So, I tried to set up the change stream in MongoDB version 4 and 5.
MongoDB Enterprise rs0:PRIMARY> db.coll.find();
{ "_id" : ObjectId("63b2d783"), "a" : 1, "b" : [ { "c" : 1, "d" : "qwq" }, { "c" : 2, "d" : "mlo" } ] }
{ "_id" : ObjectId("63b2d784"), "a" : 2, "b" : [ { "c" : 4, "d" : "hyt" }, { "c" : 5, "d" : "nhw" } ] }
In another window,
MongoDB Enterprise rs0:PRIMARY> cs = db.coll.watch([], {"fullDocument": "updateLookup"});
MongoDB Enterprise rs0:PRIMARY> while(!cs.isExhausted()){
... if(cs.hasNext()){
... print(tojson(cs.next()));
... }
... }
Then I ran an update.
MongoDB Enterprise rs0:PRIMARY> db.coll.update({},{$set:{"a":3}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
There was no such field in the change stream.
{
"_id" : {"_data" : "8263B2D474000000012B022C0100296E5A100439C39"},
"operationType" : "update",
"clusterTime" : Timestamp(1672664180, 1),
"fullDocument" : {
"_id" : ObjectId("63b2d783"),
"a" : 3,
"b" : [
{"c" : 1,"d" : "qwq"},
{"c" : 2,"d" : "mlo"}
]
},
"ns" : {
"db" : "test","coll" : "coll"
},
"documentKey" : {"_id" : ObjectId("63b2d783")},
"updateDescription" : {
"updatedFields" : {
"a" : 3
},
"removedFields" : [ ]
}
}
Next, I updated the server to version 6 and executed this update query to slice the array.
db.coll.update(
{},
[
{$set: {"b": {$slice: ["$b",1]}}}
]
);
And, there it was. Showing the array field name and it's new size.
{
"_id" : {"_data" : "8263B2D756000000012B022C0100296E5A1004A7FD82"},
"operationType" : "update",
"clusterTime" : Timestamp(1672664918, 1),
"wallTime" : ISODate("2023-01-02T13:08:38.584Z"),
"fullDocument" : {
"_id" : ObjectId("63b2d1d6"),
"a" : 1,
"b" : [
{"c" : 1,"d" : "qwq"}
]
},
"ns" : {
"db" : "test","coll" : "coll"
},
"documentKey" : {"_id" : ObjectId("63b2d1d6")},
"updateDescription" : {
"updatedFields" : {
},
"removedFields" : [ ],
"truncatedArrays" : [
{
"field" : "b",
"newSize" : 1
}
]
}
}
I couldn't find any other way to cause this using $replaceRoot/$replaceWith.
I have a mongo db record in a table car with structure like given below
name: Ferrari
color: [ red, blue, green ]
How would I run a find query in mongo db that would display one record each for the values present in color ?
Sample Output:
name: Ferrari
color: red
name: Ferrari
color: blue
name: Ferrari
color: green
It better if you take a look about $unwind, it's actually what you looking for. Here is example how to use it.
Let's say you have collection that include this:
[
{ "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] },
{ "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] },
{ "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" },
{ "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") },
{ "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null }
]
you can get the result for same _id but each size from sizes , so you can do it by running one of the two options:
db.inventory2.aggregate( [ { $unwind: "$sizes" } ] )
db.inventory2.aggregate( [ { $unwind: { path: "$sizes" } } ] )
then you will get:
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
But what important to you is to get custom result but other object.. You can use $match to complete you query, using aggregate you can get the specific result. Lets say you want to find the item "ABC" and split all sizes for that specific item:
db.inventory2.aggregate( [
//Stage one - find items named "ABC"
{$match:{"item":"ABC"}},
//Stage two - split result of sizes array
{ $unwind: "$sizes" }
] )
The result for that query is:
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
By using aggregate you can query by steps and make changes in your result, but since you using some extra filters, the result will take more time from normal query.
I was going through mongo db indexes and found this when i create index on multi key field and try to sort the result the behavior is strange.
For example:
> db.testIndexes.find();
{ "_id" : ObjectId("584e6ca8d23d3b48f9cb819d"), "type" : "depart", "item" : "aaa", "ratings" : [ 5, 8, 9 ] }
{ "_id" : ObjectId("584e6cb2d23d3b48f9cb819e"), "type" : "depart", "item" : "aaa", "ratings" : [ 2, 3, 4 ] }
{ "_id" : ObjectId("584e6cbdd23d3b48f9cb819f"), "type" : "depart", "item" : "aaa", "ratings" : [ 10, 6, 1 ] }
db.testIndexes.createIndex({ratings:1});
Now if i sue these queries :
db.testIndexes.find().sort({ratings:1}).pretty();
Result is like this
{
"_id" : ObjectId("584e6cbdd23d3b48f9cb819f"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
10,
6,
1
]
}
{
"_id" : ObjectId("584e6cb2d23d3b48f9cb819e"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
2,
3,
4
]
}
{
"_id" : ObjectId("584e6ca8d23d3b48f9cb819d"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
5,
8,
9
]
}
and for query
db.testIndexes.find().sort({ratings:-1}).pretty();
Results are:
{
"_id" : ObjectId("584e6cbdd23d3b48f9cb819f"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
10,
6,
1
]
}
{
"_id" : ObjectId("584e6ca8d23d3b48f9cb819d"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
5,
8,
9
]
}
{
"_id" : ObjectId("584e6cb2d23d3b48f9cb819e"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
2,
3,
4
]
}
As results does not seems to follow and order so can anyone help how mongo is sorting these results.
Thanks
Virendra
Well it does seem like the results are not following any order but actually they are. In your first sort {ratings:1}, what's happening here is the results are ordered by the smallest element in ratings. Since these are your lists:
[ 10, 6, 1 ] [ 2, 3, 4 ] [ 5, 8, 9 ]
So the list [ 10, 6, 1 ] smallest element is 1, the list [ 2, 3, 4 ] smallest element is 2 and the list [ 5, 8, 9 ] smallest element is 5. So the results are ordered in that way.
When you sort by descending, the same order happens but by maximum element in ratings.
Hope this helps.
I have mongodb documents like this:
[{ "_id" : 5, "type" : "food", "item" : "aaa", "ratings" : [ 5, 8, 9 ] },
{ "_id" : 7, "type" : "food", "item" : "bbb", "ratings" : [ 9, 8, 7 ] }]
I want to get only the field "rating" with its elements to be limited using $slice.
I am able to apply both of the operation individually like as given below:
a) for getting only rating field:
>db.test.find( { _id: 5 }, { ratings: 1} )
{ "_id" : 5, "ratings" : [ 5, 8, 9 ] }
b) for slicing the number of sub-records in rating array:
>db.test.find( { _id: 5 }, { ratings: { $slice: 2 } } )
{ "_id" : 5, "type" : "food", "item" : "aaa", "ratings" : [ 5, 8 ] }
My desired result is :
{ "_id" : 5, "ratings" : [ 5, 8] }
How to combine these two operations in an efficient way in a single query?
Thanks in advance.
there are two ways.
use agrregate with $project and $slice.
As shown by Chridam. in a find command you can tell which field you want to show.
when you do db.test.find({_id:5}) it is like -> select * from test where _id = 5.
but when you do db.test.find({_id:5},{type:0, item:0}) that make it selective on columns you want to view -> select ratings from test where _id = 5.
type:0 and item:0 means you are not interested in fetching these fields.
I have the following data:
{
"_id" : ObjectId("55a8c1ba3996c909184d7a22"),
"uid" : "1db82e8a-2038-4818-b805-76a46ba62639",
"createdate" : ISODate("2015-07-17T08:50:02.892Z"),
"palce" : "aa",
"sex" : 1,
"longdis" : 1,
"location" : [ 106.607312, 29.575281 ]
}
{
"_id" : ObjectId("55a8c1ba3996c909184d7a24"),
"uid" : "1db82e8a-2038-4818-b805-76a46ba62639",
"createdate" : ISODate("2015-07-17T08:50:02.920Z"),
"palce" : "bbb",
"sex" : 1,
"longdis" : 1,
"location" : [ 106.589896, 29.545098 ]
}
{
"_id" : ObjectId("55a8c1ba3996c909184d7a25"),
"uid" : "1db82e8a-2038-4818-b805-76a46ba62639",
"createdate" : ISODate("2015-07-17T08:50:02.922Z"),
"palce" : "ccc",
"sex" : 1,
"longdis" : 1,
"location" : [ 106.590758, 29.566713 ]
}
{
"_id" : ObjectId("55a8c1ba3996c909184d7a26"),
"uid" : "1db82e8a-2038-4818-b805-76a46ba62639",
"createdate" : ISODate("2015-07-17T08:50:02.923Z"),
"palce" : "ddd",
"sex" : 1,
"longdis" : 1,
"location" : [ 106.637039, 29.561436 ]
}
{
"_id" : ObjectId("55a8c1bc3996c909184d7a27"),
"uid" : "1db82e8a-2038-4818-b805-76a46ba62639",
"createdate" : ISODate("2015-07-17T08:50:04.499Z"),
"palce" : "eee",
"sex" : 1,
"longdis" : 1,
"location" : [ 106.539522, 29.57929 ]
}
{
"_id" : ObjectId("55a8d12e78292fa3837ebae4"),
"uid" : "1db82e8a-2038-4818-b805-76a46ba62639",
"createdate" : ISODate("2015-07-17T09:55:58.947Z"),
"palce" : "fff",
"sex" : 1,
"longdis" : 1,
"location" : [ 106.637039, 29.561436 ]
}
I want to first of all, sort by the distance, if the distance is the same, sort by the time.
my command :
db.runCommand( {
geoNear: "paging",
near: [106.606033,29.575897 ],
spherical : true,
maxDistance : 1/6371,
minDistance:0/6371,
distanceMultiplier: 6371,
num:2,
query: {'_id': {'$nin': []}}
})
or
db.paging.find({
'location':{
$nearSphere: [106.606033,29.575897],
$maxDistance:1
}
}).limit(5).skip((2 - 1) * 2).sort({createdate:-1})
How can I sort on both "nearest" and "createddate"?
The correct query to use here uses the aggregation framework which has the $geoNear pipeline stage to assist with this. It's also the only place you get to "sort" by multiple keys, as unforntunately the "geospatial" $nearSphere does not have a "meta" projection for "distance" like $text has a "score".
Also the geoNear database command you are using can also not be used with "cursor" .sort() in that way either.
db.paging.aggregate([
{ "$geoNear": {
"near": [106.606033,29.575897 ],
"spherical": true,
"distanceField": "distance",
"distanceMuliplier": 6371,
"maxDistance": 1/6371
}},
{ "$sort": { "distance": 1, "createdate": -1 } },
{ "$skip": ( 2-1 ) * 2 },
{ "$limit": 5 }
])
That is the equivalent of what you are trying to do.
With the aggregation framework you use the "pipeline operators" instead of "cursor modifiers" to do things like $sort, $skip and $limit. Also these must be in a Logical order, whereas the cursor modifiers generally work it out.
It's a "pipeline", just like "Unix pipe". |
Also, be careful with "maxDistance" and "distanceMuliplier". Since your co-ordinates are in "legacy co-ordinate pairs" and not GeoJSON format, then the distances are measured in "radians". If you have GeoJSON stored location data then the result is returned in "meters".