在MongoDB

时间:2017-05-19 20:45:42

标签: javascript mongodb aggregation-framework

我想查找集合中与某个字符串部分匹配的所有键名。

我最接近的是检查某个密钥是否存在,但这是一个完全匹配:

db.collection.find({ "fkClientID": { $exists:1 }})

我希望获得所有以fk开头的密钥。

2 个答案:

答案 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,则会返回fkfoofkfoo.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}}值,因此文档将保持不变。