Você pode fazer isso usando mapReduce :
Para obter apenas os nomes dos campos no nível raiz:
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 }});
Isso produzirá algo assim:
{
"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
}
Para obter nomes de campos e qualquer um ou todos (documento inteiro) seus valores:
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 }});
Isso produzirá algo assim:
{
"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
}
Para obter nomes de campos em subdocumentos (sem caminho):
Para fazer isso, você terá que usar
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])
});
}});
E altere seu mapa para:
function () {
getFields(this);
}
Agora execute
db.loadServerScripts()
para carregá-los. Para obter nomes de campos em subdocumentos (com caminho):
A versão anterior retornará apenas os nomes dos campos, não o caminho inteiro para obtê-los, o que você precisará se o que deseja fazer é renomear essas chaves. Para obter o caminho:
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 + '.')
});
}});
E altere seu mapa para:
function () {
getFields(this, '');
}
Para excluir correspondências de caminhos sobrepostos:
Observe que se você tiver um campo
fkfoo.fkbar
, ele retornará fkfoo
e fkfoo.fkbar
. Se você não quiser correspondências de caminho sobrepostas, então: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);
});
}});
Voltando à sua pergunta, renomeando esses campos:
Com esta última opção, você obtém todos os caminhos que incluem chaves que começam com
fk
, então você pode usar $rename
por isso. No entanto,
$rename
não funciona para aqueles que contêm arrays, então para aqueles você pode usar forEach
para fazer a atualização. Consulte MongoDB renomear campo de banco de dados no array Observação de desempenho:
MapReduce não é um pensamento particularmente rápido, então você pode querer especificar
{ out: "fk_fields"}
para gerar os resultados em uma nova coleção chamada fk_fields
e consulte esses resultados mais tarde, mas isso dependerá do seu caso de uso. Possíveis otimizações para casos específicos (esquema consistente):
Além disso, observe que, se você sabe que o esquema de seus documentos é sempre o mesmo, basta verificar um deles para obter seus campos, para poder fazer isso adicionando
limit: 1
para o objeto de opções ou apenas recuperando um documento com findOne
e lendo seus campos no nível de aplicação.