Możesz to zrobić za pomocą mapReduce :
Aby uzyskać tylko nazwy pól na poziomie głównym:
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 }});
Spowoduje to wyświetlenie czegoś takiego:
{
"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
}
Aby uzyskać nazwy pól i dowolne lub wszystkie (cały dokument) ich wartości:
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 }});
Spowoduje to wyświetlenie czegoś takiego:
{
"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
}
Aby uzyskać nazwy pól w dokumentach podrzędnych (bez ścieżki):
Aby to zrobić, będziesz musiał użyć 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])
});
}});
I zmień swoją mapę na:
function () {
getFields(this);
}
Teraz uruchom db.loadServerScripts()
aby je załadować.
Aby uzyskać nazwy pól w dokumentach podrzędnych (ze ścieżką):
Poprzednia wersja zwróci tylko nazwy pól, a nie całą ścieżkę do ich uzyskania, co będzie potrzebne, jeśli chcesz zmienić nazwy tych kluczy. Aby uzyskać ścieżkę:
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 + '.')
});
}});
I zmień swoją mapę na:
function () {
getFields(this, '');
}
Aby wykluczyć nakładające się dopasowania ścieżek:
Zauważ, że jeśli masz pole fkfoo.fkbar
, zwróci fkfoo
i fkfoo.fkbar
. Jeśli nie chcesz, aby pasujące ścieżki pokrywały się, to:
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);
});
}});
Wracając do pytania, zmieniając nazwy tych pól:
Dzięki tej ostatniej opcji otrzymujesz wszystkie ścieżki zawierające klucze zaczynające się od fk
, więc możesz użyć $rename
za to.
Jednak $rename
nie działa dla tych, które zawierają tablice, więc dla tych możesz użyć forEach
zrobić aktualizację. Zobacz Pole bazy danych MongoDB zmiany nazwy w tablicy
Uwaga dotycząca wydajności:
MapReduce nie jest szczególnie szybki, więc możesz chcieć określić { out: "fk_fields"}
aby wyprowadzić wyniki do nowej kolekcji o nazwie fk_fields
i przeszukuj te wyniki później, ale będzie to zależeć od przypadku użycia.
Możliwe optymalizacje dla konkretnych przypadków (spójny schemat):
Pamiętaj też, że jeśli wiesz, że schemat Twoich dokumentów jest zawsze taki sam, wystarczy zaznaczyć jeden z nich, aby uzyskać jego pola, więc możesz to zrobić, dodając limit: 1
do obiektu opcji lub po prostu pobranie jednego dokumentu za pomocą findOne
i odczytywanie jego pól na poziomie aplikacji.