mongoDB通过多维数组搜索记录

时间:2015-11-10 21:47:57

标签: mongodb multidimensional-array mongodb-query aggregation-framework

我一直在试图弄清楚如何从mongoDB返回与另一个数组中的数组(键值对)匹配的结果。以mongo为例。

{
    "_id" : NumberLong(19),
    "_t" : "OsmNode",
    "uname" : "Robert Whittaker",
    "uid" : 84263,
    "version" : 3,
    "changeset" : 1.40583e+007,
    "timestamp" : ISODate("2012-11-27T12:38:46.000Z"),
    "tags" : [ 
        [ 
            "ref", 
            "SG4 90"
        ], 
        [ 
            "amenity", 
            "post_box"
        ], 
        [ 
            "box_type", 
            "lamp_box"
        ], 
        [ 
            "collection_times", 
            "Mo-Fr 16:30; Sa 09:45"
        ]
    ],
    "tagKeys" : [ 
        "ref", 
        "amenity", 
        "box_type", 
        "collection_times"
    ],
    "location" : [ 
        51.9458770751953130, 
        -0.2069800049066544
    ]
}

标签字段包含多个键值对。我想要做的是返回包含“amenity”键和“post_box”值的所有记录。做这样的事

db.getCollection('nodes').find(
    {
        "tags": [ [ 
            "amenity", 
            "post_box"
        ] ]
    }
)

不幸的是,上面只返回具有单个标签的记录,即“amenity”,“post_box”。因此,由于上述记录包含“ref”,“box_type”,“collection_times”标签以及设施标签,因此不会在查询结果中返回。环顾谷歌我发现了许多数组的例子,但没有包含另一个数组的数组。我想我需要使用$ in或£elemMatch,但尝试那些我似乎不能让他们用上面的球来打球。

1 个答案:

答案 0 :(得分:0)

使用您当前的文档结构,您需要使用$all运算符

db.collection.find( { 'tags': { '$all': [ [ 'amenity', 'post_box' ] ] } } )

但请注意,子数组中元素的顺序很重要,例如以下查询不会返回任何文档。

db.collection.find( { 'tags': { '$all': [ [ 'post_box', 'amenity' ] ] } } )

因此,解决方法是您需要使用$or运算符

db.collection.find( { 
    '$or': [ 
        { 'tags': { '$all': [ [ 'post_box', 'amenity' ] ] } }, 
        { 'tags': { '$all': [ [ 'amenity', 'post_box' ] ] } } 
    ] 
} )

因此,最好的办法是更改文档结构。为此,您需要迭代cursor并使用"bulk"操作更新每个文档以提高效率。

db.collection.find().forEach(function(doc) {
    var tags = doc.tags.map(function(element) {
        return { 'tagsKeys': element[0], 'value': element[1] }; 
    });
    bulk.find({'_id': doc._id}).updateOne({
        '$set': {'tags': tags}, 
        '$unset': {'tagKeys': ''} 
    }); 
    count++; 
    if(count % 250 === 0) { 
        // Execute per 250 operations and re-init    
        bulk.execute();     
        bulk = db.test.initializeOrderedBulkOp(); 
    } 
})
// Clean up queues
if(count > 0) bulk.execute()

您的文档如下所示:

{
        "_id" : NumberLong(19),
        "_t" : "OsmNode",
        "uname" : "Robert Whittaker",
        "uid" : 84263,
        "version" : 3,
        "changeset" : 14058300,
        "timestamp" : ISODate("2012-11-27T12:38:46Z"),
        "tags" : [
                {
                        "tagsKeys" : "ref",
                        "value" : "SG4 90"
                },
                {
                        "tagsKeys" : "amenity",
                        "value" : "post_box"
                },
                {
                        "tagsKeys" : "box_type",
                        "value" : "lamp_box"
                },
                {
                        "tagsKeys" : "collection_times",
                        "value" : "Mo-Fr 16:30; Sa 09:45"
                }
        ],
        "location" : [
                51.94587707519531,
                -0.2069800049066544
        ]
}

然后您的查询变得更加简单:

db.collection.find({'tags.tagsKeys': 'amenity', 'tags.value': 'post_box'})

现在,如果“tagKey”并不总是“tags”子阵列中的第一个元素,那么您需要使用.aggregate()方法来访问aggregation pipeline

db.collection.aggregate([
    { "$project": { 
        "element": { 
            "$map": {
                "input": "$tags", 
                "as": "tag", 
                "in": {
                    "value": { "$setDifference": [ "$$tag", "$tagKeys" ] }, 
                    "key": { "$setIntersection": [ "$$tag", "$tagKeys" ] } 
                }
            }
        }
    }}, 
    { "$unwind": "$element" }, 
    { "$unwind": "$element.key" }, 
    { "$unwind": "$element.value" }, 
    { "$group": { 
        "_id": "$_id", 
        "tags": {
            "$push": {
                "tagKey": "$element.key", 
                "value": "$element.value"
            }
        }
    }}
]).forEach(function(doc) {
    bulk.find( { '_id': doc._id } ).updateOne( {
        '$set': { 'tags': doc.tags }, 
        '$unset': { 'tagKeys': '' }
    }); 
    count++; 
    if(count % 200 === 0) { 
        // Execute per 200 operations and re-init
        bulk.execute();     
        bulk = db.collection.initializeOrderedBulkOp(); 
    } 
})

// Clean up queues
if(count > 0) bulk.execute()

现在我们的管道中发生了什么?

我们需要区分我们的代码键和它们的值,以及我们在$project阶段可以做的地方。 $setDifference$setIntersection运算符分别让我们返回出现在第一个但不出现在第二个数组“tagvalue”中的元素数组,以及出现在所有输入集中的元素数组“ tagKeys”。

$map运算符返回一组键/值对。

由于“tagKeys”和“tagvalue”是数组,因此您需要解构这些数组并使用$unwind运算符。从那里你需要$group你的文件,并使用$push累加器运算符,它返回可以用来更新文档的新“标签”数组。

最后但并非最不重要的是,您需要$unset文档中的“tagKeys”字段,因为不再需要它。您始终可以使用.distinct()方法检索“tagKeys”列表:

db.collection.distinct('tagsKeys')

MongoDB 3.2起,Bulk() API及其关联的方法已弃用,您需要使用db.collection.bulkWrite()方法。

所以这就是它的完成方式:

db.collection.aggregate([
    { "$project": { 
        "element": { 
            "$map": {
                "input": "$tags", 
                "as": "tag", 
                "in": {
                    "value": { "$setDifference": [ "$$tag", "$tagKeys" ] }, 
                    "key": { "$setIntersection": [ "$$tag", "$tagKeys" ] } 
                }
            }
        }
    }}, 
    { "$unwind": "$element" }, 
    { "$unwind": "$element.key" }, 
    { "$unwind": "$element.value" }, 
    { "$group": { 
        "_id": "$_id", 
        "tags": {
            "$push": {
                "tagKey": "$element.key", 
                "value": "$element.value"
            }
        }
    }}
]).forEach(function(doc) { 
    var operation = {
        updateOne: { 
            filter: { '_id': doc._id },
            update: { 
                '$set': { 'tags': doc.tags }, 
                '$unset': { 'tagKeys': '' }
            }
        }
    }; 
    operations.push(operation); 
})

db.collection.bulkWrite(operations)