Puoi farlo usando mapReduce :
Per ottenere solo i nomi dei campi a livello radice:
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 }});
Questo produrrà qualcosa del genere:
{
"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
}
Per ottenere i nomi dei campi e alcuni o tutti (intero documento) i suoi valori:
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 }});
Questo produrrà qualcosa del genere:
{
"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
}
Per ottenere i nomi dei campi nei documenti secondari (senza percorso):
Per farlo dovrai usare 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 cambia la tua mappa in:
function () {
getFields(this);
}
Ora esegui db.loadServerScripts()
per caricarli.
Per ottenere i nomi dei campi nei documenti secondari (con percorso):
La versione precedente restituirà solo i nomi dei campi, non l'intero percorso per ottenerli, di cui avrai bisogno se quello che vuoi fare è rinominare quelle chiavi. Per ottenere il percorso:
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 cambia la tua mappa in:
function () {
getFields(this, '');
}
Per escludere corrispondenze di percorsi sovrapposte:
Nota che se hai un campo fkfoo.fkbar
, restituirà fkfoo
e fkfoo.fkbar
. Se non vuoi che le corrispondenze di percorso si sovrappongano, allora:
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);
});
}});
Tornando alla tua domanda, rinominando quei campi:
Con quest'ultima opzione, ottieni tutti i percorsi che includono chiavi che iniziano con fk
, quindi puoi utilizzare $rename
per quello.
Tuttavia, $rename
non funziona per quelli che contengono array, quindi per quelli puoi usare forEach
per fare l'aggiornamento. Vedere Campo del database di ridenominazione di MongoDB all'interno dell'array
Nota sul rendimento:
MapReduce non è particolarmente veloce, quindi potresti voler specificare { out: "fk_fields"}
per generare i risultati in una nuova raccolta chiamata fk_fields
e interroga questi risultati in un secondo momento, ma ciò dipenderà dal tuo caso d'uso.
Possibili ottimizzazioni per casi specifici (schema coerente):
Inoltre, nota che se sai che lo schema dei tuoi documenti è sempre lo stesso, devi solo selezionarne uno per ottenere i suoi campi, quindi puoi farlo aggiungendo limit: 1
all'oggetto options o semplicemente recuperando un documento con findOne
e leggerne i campi a livello di applicazione.