我想查找集合中与某个字符串部分匹配的所有键名。
我最接近的是检查某个密钥是否存在,但这是一个完全匹配:
db.collection.find({ "fkClientID": { $exists:1 }})
我希望获得所有以fk
开头的密钥。
答案 0 :(得分:3)
您可以使用mapReduce执行此操作:
只获取根级别的字段名称:
db.collection.mapReduce(function () {
Object.keys(this).map(function(key) {
if (key.match(/^fk/)) emit(key, null);
// OR: key.indexOf("fk") === 0
});
}, function(/* key, values */) {
// No need for params or to return anything in the
// reduce, just pass an empty function.
}, { out: { inline: 1 }});
这将输出如下内容:
{
"results": [{
"_id": "fkKey1",
"value": null
}, {
"_id": "fkKey2",
"value": null
}, {
"_id": "fkKey3",
"value": null
}],
"timeMillis": W,
"counts": {
"input": X,
"emit": Y,
"reduce": Z,
"output": 3
},
"ok" : 1
}
要获取字段名称以及任何或所有(整个文档)的值:
db.test.mapReduce(function () {
var obj = this;
Object.keys(this).map(function(key) {
// With `obj[key]` you will get the value of the field as well.
// You can change `obj[key]` for:
// - `obj` to return the whole document.
// - `obj._id` (or any other field) to return its value.
if (key.match(/^fk/)) emit(key, obj[key]);
});
}, function(key, values) {
// We can't return values or an array directly yet:
return { values: values };
}, { out: { inline: 1 }});
这将输出如下内容:
{
"results": [{
"_id": "fkKey1",
"value": {
"values": [1, 4, 6]
}
}, {
"_id": "fkKey2",
"value": {
"values": ["foo", "bar"]
}
}],
"timeMillis": W,
"counts": {
"input": X,
"emit": Y,
"reduce": Z,
"output": 2
},
"ok" : 1
}
获取子文档中的字段名称(不带路径):
为此,您必须使用store JavaScript functions on the Server
:
db.system.js.save({ _id: "hasChildren", value: function(obj) {
return typeof obj === "object";
}});
db.system.js.save({ _id: "getFields", value: function(doc) {
Object.keys(doc).map(function(key) {
if (key.match(/^fk/)) emit(key, null);
if (hasChildren(doc[key])) getFields(doc[key])
});
}});
将地图更改为:
function () {
getFields(this);
}
现在运行db.loadServerScripts()
加载它们。
获取子文档中的字段名称(带路径):
以前的版本只返回字段名称,而不是返回获取它们的完整路径,如果您要执行的操作是重命名这些键,则需要使用它们。要获得路径:
db.system.js.save({ _id: "getFields", value: function(doc, prefix) {
Object.keys(doc).map(function(key) {
if (key.match(/^fk/)) emit(prefix + key, null);
if (hasChildren(doc[key]))
getFields(doc[key], prefix + key + '.')
});
}});
将地图更改为:
function () {
getFields(this, '');
}
排除重叠路径匹配:
请注意,如果您有字段fkfoo.fkbar
,则会返回fkfoo
和fkfoo.fkbar
。如果您不想要重叠路径匹配,那么:
db.system.js.save({ _id: "getFields", value: function(doc, prefix) {
Object.keys(doc).map(function(key) {
if (hasChildren(doc[key]))
getFields(doc[key], prefix + key + '.')
else if (key.match(/^fk/)) emit(prefix + key, null);
});
}});
回到您的问题,重命名这些字段:
使用最后一个选项,您将获得包含以fk
开头的键的所有路径,因此您可以使用$rename
。
但是,$rename
对包含数组的用户不起作用,因此对于那些可以使用forEach
进行更新的用户。见MongoDB rename database field within array
效果说明:
MapReduce的思路并不是特别快,因此您可能希望指定{ out: "fk_fields"}
将结果输出到名为fk_fields
的新集合中,稍后查询这些结果,但这取决于您的用例。
特定案例的可能优化(一致架构):
另外,请注意,如果您知道文档的架构始终相同,那么您只需要检查其中一个以获取其字段,这样就可以将limit: 1
添加到选项对象中或者只是使用findOne
检索一个文档并在应用程序级别中读取其字段。
答案 1 :(得分:2)
如果您拥有最新的MongoDB 3.4.4,那么您可以在$objectToArray
的汇总语句中使用db[collname].aggregate([
{ "$redact": {
"$cond": {
"if": {
"$gt": [
{ "$size": { "$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "doc",
"cond": {
"$eq": [ { "$substr": [ "$$doc.k", 0, 2 ] }, "fk" ]
}
}}},
0
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
作为最超快速的方式,这可以做到这一点与本地运营商。不是扫描集合是“快”。但是你得到的速度最快:
$objectToArray
当前未记录的{ "a": 1, "b": 2 }
将“对象”转换为数组中的“键”和“值”形式。所以这个:
[{ "k": "a", "v": 1 }, { "k": "b", "v": 2 }]
成为这个:
$$ROOT
与"k"
一起使用,这是一个引用当前文档“对象”的特殊变量,我们将其转换为数组,以便可以检查$filter
的值。
然后,只需应用$substr
并使用db[collname].aggregate([
{ "$redact": {
"$cond": {
"if": {
"$gt": [
{ "$size": { "$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "doc",
"cond": {
"$eq": [ { "$substr": [ "$$doc.k", 0, 2 ] }, "fk" ]
}
}}},
0
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$project": {
"j": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "doc",
"cond": {
"$eq": [ { "$substr": [ "$$doc.k", 0, 2 ] }, "fk" ]
}
}
}
}},
{ "$unwind": "$j" },
{ "$group": { "_id": "$j.k" }}
])
来获取“key”字符串的前面字符。
对于记录,这将是MongoDB 3.4.4获得匹配密钥的唯一列表的最佳方式:
$group
这是安全规定,它考虑到密钥可能不存在于所有文档中,并且文档中可能存在多个密钥。
如果您完全确定您“始终”拥有文档中的密钥并且只有一个密钥,那么您可以缩短为db[colname].aggregate([
{ "$group": {
"_id": {
"$arrayElemAt": [
{ "$map": {
"input": { "$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "doc",
"cond": {
"$eq": [ { "$substr": [ "$$doc.k", 0, 2 ] }, "fk" ]
}
}},
"as": "el",
"in": "$$el.k"
}},
0
]
}
}}
])
:
db[collname].find(function() { return Object.keys(this).some( k => /^fk/.test(k) ) })
早期版本中最有效的方法是使用允许JavaScript表达式进行评估的$redact
语法。并不是说评估JavaScript的任何东西都是你能做的“最有效”的事情,但分析“密钥”而不是“数据”并不是任何数据存储的最佳选择:
function
内联db[collname].find({ "$where": "return Object.keys(this).some( k => /^fk/.test(k) )" })
只有shell简写,这也可以写成:
true
$where
的唯一要求是表达式为您要返回的任何文档返回{{1}}值,因此文档将保持不变。