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

时间:2015-06-18 10:40:18

标签: node.js mongodb geojson

我有一个集合users的MongoDb数据库,其中包含如下结构的文档:

{
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")
        }
]}

每个文档都有一堆属性,例如firstNamephone等。它还有places属性,它是一个子文档数组。

每个子文档都有loc属性,用于存储" place"的坐标。子文档描述。我基本上需要按照从我传递给查询的特定位置的距离的顺序拉出位置对象。

我无法弄清楚如何运行collection.find $near查询以根据其位置获取地点列表。我想首先我需要在2dsphere上设置places.loc索引并尝试:

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

但我得到"errmsg" : "exception: Can't extract geo keys

这是否可能与数据库中已有的结构一样?如果是这样,我该怎么做?我的文档样本如下,请提前感谢您的帮助。顺便说一句,我使用NodeJs和本机mongoDB驱动程序。

编辑:

我试过了:

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

,结果如下:

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

这给了我希望但是当我尝试运行查询时:

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

我明白了:

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
}

1 个答案:

答案 0 :(得分:2)

如上所述,您可以使用当前结构获得的最接近的是$geoNear,它是一个聚合框架运算符。这具有解决子文档中“匹配”所需的必要预测需求。

但首先要重新处理样本而不出错:

{
    "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"),
        }
    ]
 }

我将在名为“places”的集合中创建它,然后将索引放在该集合上,如下所示:

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

现在让我们尝试一下基本的.find()操作:

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

这将匹配并返回您的“整个文档”,但不会告诉您有关匹配的数组元素或查询点的距离。

让我们现在使用$geoNear查看操作:

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

在这个阶段给出了结果:

{
    "_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
            ]
    }
}

请注意“dist”和“locs”中的额外字段。这些分别是匹配的查询点的“距离”和从配对的子文档到该特定距离匹配的“位置”数据。

该文档仍然相同,但由于这是聚合框架,您可以采取进一步的措施:

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"
        }
    }}
])

因此,$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
            ]
    }
}

当然,正如我已经说过的,每个文档的数组中只能有“一个”匹配,因为所有$geoNear都会返回。

对于其他任何事情,您需要通过将子文档放在他们自己的集合中来“展平”文档,该集合中还包含您需要的“外部”文档属性,或者对该信息执行一些“加入”逻辑以及其他查询

另请注意,只有$geoNeargeoNear命令会将预计的“距离”值返回到文档中。前者使您可以控制字段名称,后者是任意的。