对于您当前的文档结构,您需要使用$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)