我一直在试图弄清楚如何从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,但尝试那些我似乎不能让他们用上面的球来打球。
答案 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)