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

MongoDB, How to query subdocuments close to specific location?

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

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

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

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

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
}

如前所述,您当前的结构最接近于此的是使用 $geoNear,它是一个聚合框架运算符。这具有从子文档中解析 "match" 所需的必要投影需求。

但首先要在没有错误的情况下重新处理您的样本:

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

这将匹配 return 你的 "whole document" 但不会告诉你任何有关匹配的数组元素或距查询点的距离的信息。

让我们看看现在使用$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" 中的额外字段。这些分别是来自匹配查询点的 "distance" 和与特定距离配对的子文档匹配的 "location" 数据。

文档仍然相同,但由于这是聚合框架,您可以更进一步:

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 被用作一种方法 "filter" 数组内容仅与找到的位置相匹配的 "entries":

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

当然正如我已经说过的,每个文档的数组中可以有 "only one" 匹配,因为这就是 $geoNear 将 return.

对于其他任何需要 "flatten" 文档的方法,将您的子文档放在它们自己的集合中,也包含您需要的 "outer" 文档属性,或者做一些 "joining" 逻辑以及对该信息的额外查询。

另请注意,只有 $geoNeargeoNear 命令会将 return 投影的 "distance" 值添加到文档中。前者让您可以控制字段名称,而后者是任意的。