简体   繁体   English

MongoDB,如何查询靠近特定位置的子文档?

[英]MongoDB, How to query subdocuments close to specific location?

I have MongoDb database with collection users containing documents structured as below: 我的MongoDb数据库的集合users包含以下结构的文档:

{
firstName: "firstname",
"phone": "123456",
"places":[

{
            "name" : "somename",
            "address" : "Woollahra, New South Wales, Australia",
            "loc" : {
                "type" : "Point",
                "coordinates" : [
                    151.23721839999996,
                    -33.8884085
                ]
            },
            "url" : "ttttt2",
            "registeredOn" : ISODate("2015-06-17T20:14:10.986Z"),
            "id" : ObjectId("5517632982ae879883216fe2b2")
        },
{
            "name" : "somename",
            "address" : "something else, Australia",
            "loc" : {
                "type" : "Point",
                "coordinates" : [
                    151.23721839999996,
                    -33.8884085
                ]
            },
            "url" : "ttttt2",
            "registeredOn" : ISODate("2015-06-17T20:14:10.986Z"),
            "id" : ObjectId("5517632982ae879883216fe2b2")
        }
]}

Each document has bunch of properties eg firstName , phone etc. It also has places property that is an array of subdocuments. 每个文档都有一堆属性,例如firstNamephone等。它还具有places属性,该属性是子文档的数组。

Each subdocument has loc property that stores coordinates of the "place" subdocument describes. 每个子文档都有loc属性,该属性存储“ place”子文档描述的坐标。 I basically need to pull out places objects in order of distance from specific location I pass to query. 我基本上需要按从传递给查询的特定位置的距离的顺序拉出place对象。

I cannot figure out how can I run collection.find $near queries to get list of places based on its location. 我不知道如何运行collection.find $near查询以根据其位置获取位置列表。 I figured first of all I need to set up 2dsphere index on places.loc and tried: 我想首先我需要建立2dsphere对指数places.loc和尝试:

db.users.createIndex({"places.loc":"2dsphere"})

But I'm getting "errmsg" : "exception: Can't extract geo keys . 但是我得到了"errmsg" : "exception: Can't extract geo keys

Is this even possible with structure I already have in database? 使用数据库中已有的结构,这甚至可能吗? If so how would I do it? 如果可以,我该怎么办? My documents sample is below, thank you in advance for any help. 我的文档样本如下,在此先感谢您的帮助。 BTW I'm using NodeJs with native mongoDB driver. 顺便说一句,我正在使用带有本机mongoDB驱动程序的NodeJ。

EDIT: 编辑:

I tried: 我试过了:

db.users.createIndex({"loc":"2dsphere"})

and this result in: 结果是:

{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 3,
    "numIndexesAfter" : 3,
    "note" : "all indexes already exist",
    "ok" : 1
}

and that gave me hope but then when I try to run query: 那给了我希望,但是当我尝试运行查询时:

db.users.find({
            'places.loc': {
                $near: {
                    $geometry: {
                        type: "Point",
                        coordinates: [-73.965355, 40.782865]
                    },
                    $maxDistance: 20000
                }
            }
        })

I get this: 我得到这个:

Error: error: {
    "$err" : "Unable to execute query: error processing query: ns=marankings.users limit=0 skip=0\nTree: GEONEAR  field=places.loc maxdist=20000 isNearSphere=0\nSort: {}\nProj: {}\n planner returned error: unable to find index for $geoNear query",
    "code" : 17007
}

As stated, the closest you can get to this with your current structure is using $geoNear which is an aggregation framework operator. 如前所述,最接近当前结构的是使用$geoNear ,它是一个聚合框架运算符。 This has the necessary projection needs required to resolve the "match" from the sub-documents. 这具有解决子文档中“匹配”所需的必要投影需求。

But first a reworking of your sample without the errors: 但是首先要对样本进行重做,而不会出现错误:

{
    "firstName": "firstname",
    "phone": "123456",
    "places":[
        {
            "name" : "somename",
            "address" : "Woollahra, New South Wales, Australia",
            "loc" : {
                "type" : "Point",
                "coordinates" : [
                    151.23721839999996,
                    -33.8884085
                ]
            },
            "url" : "ttttt2",
            "registeredOn" : ISODate("2015-06-17T20:14:10.986Z"),
       },
       {
            "name" : "somename",
            "address" : "something else, Australia",
            "loc" : {
                "type" : "Point",
                "coordinates" : [
                    151.23721839999996,
                    -36.8884085
                ]
            },
            "url" : "ttttt2",
            "registeredOn" : ISODate("2015-06-17T20:14:10.986Z"),
        }
    ]
 }

I'll create that in a collection called "places" and then places the index on that collection like so: 我将在名为“ places”的集合中创建该集合,然后将索引放在该集合上,如下所示:

db.places.ensureIndex({ "places.loc": "2dsphere" })

Now let's try a basic .find() operation: 现在让我们尝试一个基本的.find()操作:

db.places.find({
    "places.loc": {
        "$near": {
            "$geometry": {
                "type": "Point",
                "coordinates": [
                    151.23721839999996,
                    -33.8884085
                ]
            }
        }
    }
})

That will match and return your "whole document" but does not tell you anything about the array element matched, or the distance from the queried point. 这将匹配并返回您的“整个文档”,但不会告诉您有关匹配的数组元素或到查询点的距离的任何信息。

Lets see the operation using $geoNear now: 让我们现在使用$geoNear来查看操作:

db.places.aggregate([
    { "$geoNear": {
        "near": {
            "type": "Point",
            "coordinates": [
                151.23721839999996,
                -33.8884085
            ]
        },
        "distanceField": "dist",
        "includeLocs": "locs",
        "spherical": true
    }}
])

Which at this stage gives us the result: 在这个阶段可以给我们带来以下结果:

{
    "_id" : ObjectId("558299b781483914adf5e423"),
    "firstName" : "firstname",
    "phone" : "123456",
    "places" : [
            {
                    "name" : "somename",
                    "address" : "Woollahra, New South Wales, Australia",
                    "loc" : {
                            "type" : "Point",
                            "coordinates" : [
                                    151.23721839999996,
                                    -33.8884085
                            ]
                    },
                    "url" : "ttttt2",
                    "registeredOn" : ISODate("2015-06-17T20:14:10.986Z")
            },
            {
                    "name" : "somename",
                    "address" : "something else, Australia",
                    "loc" : {
                            "type" : "Point",
                            "coordinates" : [
                                    151.23721839999996,
                                    -36.8884085
                            ]
                    },
                    "url" : "ttttt2",
                    "registeredOn" : ISODate("2015-06-17T20:14:10.986Z")
            }
    ],
    "dist" : 0,
    "locs" : {
            "type" : "Point",
            "coordinates" : [
                    151.23721839999996,
                    -33.8884085
            ]
    }
}

Note the extra fields in there for "dist" and "locs". 请注意其中的“ dist”和“ locs”额外字段。 These are respectively the "distance" from the queried point for the match and the "location" data that was matched from the sub-document paired to that particular distance. 这些分别是与匹配查询点的“距离”和与子文档匹配到该特定距离的“位置”数据。

The document is still the same, but since this is the aggregation framework you can take that further: 该文档仍然相同,但是由于这是聚合框架,因此您可以进一步:

db.places.aggregate([
    { "$geoNear": {
        "near": {
            "type": "Point",
            "coordinates": [
                151.23721839999996,
                -33.8884085
            ]
        },
        "distanceField": "dist",
        "includeLocs": "locs",
        "spherical": true
    }},
    { "$redact": {
        "$cond": {
            "if": { "$eq": [ 
                 { "$ifNull": [ "$loc", "$$ROOT.locs" ] },
                 "$$ROOT.locs"
             ]},
             "then": "$$DESCEND",
             "else": "$$PRUNE"
        }
    }}
])

So $redact is used as a method to "filter" the array contents to only the "entries" that match the found location: 因此, $redact用作一种将数组内容“过滤”为仅与找到位置匹配的“条目”的方法:

{
    "_id" : ObjectId("558299b781483914adf5e423"),
    "firstName" : "firstname",
    "phone" : "123456",
    "places" : [
            {
                    "name" : "somename",
                    "address" : "Woollahra, New South Wales, Australia",
                    "loc" : {
                            "type" : "Point",
                            "coordinates" : [
                                    151.23721839999996,
                                    -33.8884085
                            ]
                    },
                    "url" : "ttttt2",
                    "registeredOn" : ISODate("2015-06-17T20:14:10.986Z")
            }
    ],
    "dist" : 0,
    "locs" : {
            "type" : "Point",
            "coordinates" : [
                    151.23721839999996,
                    -33.8884085
            ]
    }
}

Of course as I already said, there can be "only one" match in the array per document because that is all $geoNear will return. 当然,正如我已经说过的那样,每个文档的数组中只能有“一个”匹配项,因为这将返回$geoNear全部。

For anything else you need to "flatten" the document by placing your sub-documents in their own collection also containing your "outer" document properties where you need them, or do some "joining" logic with additional queries for that information. 对于其他任何事情,您都需要通过将子文档放置在它们自己的集合中来“拉平”文档,这些子文档集还包含您需要的“外部”文档属性,或者对这些信息进行附加查询的“合并”逻辑。

Also note that only $geoNear and the geoNear commands will return a projected "distance" value into the document. 还要注意,只有$geoNeargeoNear命令会将投影的“距离”值返回到文档中。 The former gives you control over the field name and the latter is arbitrary. 前者使您可以控制字段名称,而后者则是任意的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM