examples - mongodb aggregate sum
Consigue nombres de todas las claves de la colección. (18)
Me gustaría obtener los nombres de todas las claves en una colección de MongoDB.
Por ejemplo, de esto:
db.things.insert( { type : [''dog'', ''cat''] } );
db.things.insert( { egg : [''cat''] } );
db.things.insert( { type : [] } );
db.things.insert( { hello : [] } );
Me gustaría obtener las claves únicas:
type, egg, hello
Aquí está la muestra trabajada en Python: Esta muestra devuelve los resultados en línea.
from pymongo import MongoClient
from bson.code import Code
mapper = Code("""
function() {
for (var key in this) { emit(key, null); }
}
""")
reducer = Code("""
function(key, stuff) { return null; }
""")
distinctThingFields = db.things.map_reduce(mapper, reducer
, out = {''inline'' : 1}
, full_response = True)
## do something with distinctThingFields[''results'']
Con la respuesta de Kristina como inspiración, creé una herramienta de código abierto llamada Variety que hace exactamente esto: https://github.com/variety/variety
Creo que la mejor manera de hacer esto como se menciona here es en mongod 3.4.4+, pero sin usar el operador $unwind
y usar solo dos etapas en la tubería. En su lugar, podemos usar los $mergeObjects
y $objectToArrray .
En la etapa $group
, usamos el operador $mergeObjects
para devolver un solo documento donde la clave / valor son de todos los documentos de la colección.
Luego viene el $project
donde usamos $map
y $objectToArray
para devolver las claves.
let allTopLevelKeys = [
{
"$group": {
"_id": null,
"array": {
"$mergeObjects": "$$ROOT"
}
}
},
{
"$project": {
"keys": {
"$map": {
"input": { "$objectToArray": "$array" },
"in": "$$this.k"
}
}
}
}
];
Ahora bien, si tenemos un documento anidado y queremos obtener las claves también, esto es factible. Para simplificar, consideremos un documento con un simple documento incrustado que se ve así:
{field1: {field2: "abc"}, field3: "def"}
{field1: {field3: "abc"}, field4: "def"}
El siguiente pipeline muestra todas las claves (campo1, campo2, campo3, campo4).
let allFistSecondLevelKeys = [
{
"$group": {
"_id": null,
"array": {
"$mergeObjects": "$$ROOT"
}
}
},
{
"$project": {
"keys": {
"$setUnion": [
{
"$map": {
"input": {
"$reduce": {
"input": {
"$map": {
"input": {
"$objectToArray": "$array"
},
"in": {
"$cond": [
{
"$eq": [
{
"$type": "$$this.v"
},
"object"
]
},
{
"$objectToArray": "$$this.v"
},
[
"$$this"
]
]
}
}
},
"initialValue": [
],
"in": {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
},
"in": "$$this.k"
}
}
]
}
}
}
]
Con un poco de esfuerzo, podemos obtener la clave para todos los subdocumentos en un campo de matriz donde los elementos también son objetos.
Estaba tratando de escribir en nodejs y finalmente se me ocurrió esto:
db.collection(''collectionName'').mapReduce(
function() {
for (var key in this) {
emit(key, null);
}
},
function(key, stuff) {
return null;
}, {
"out": "allFieldNames"
},
function(err, results) {
var fields = db.collection(''allFieldNames'').distinct(''_id'');
fields
.then(function(data) {
var finalData = {
"status": "success",
"fields": data
};
res.send(finalData);
delteCollection(db, ''allFieldNames'');
})
.catch(function(err) {
res.send(err);
delteCollection(db, ''allFieldNames'');
});
});
Después de leer la colección recién creada "allFieldNames", elimínela.
db.collection("allFieldNames").remove({}, function (err,result) {
db.close();
return;
});
Esto funciona bien para mi:
var arrayOfFieldNames = [];
var items = db.NAMECOLLECTION.find();
while(items.hasNext()) {
var item = items.next();
for(var index in item) {
arrayOfFieldNames[index] = index;
}
}
for (var index in arrayOfFieldNames) {
print(index);
}
Extendí un poco la solución de Carlos LM para que sea más detallada.
Ejemplo de un esquema:
var schema = {
_id: 123,
id: 12,
t: ''title'',
p: 4.5,
ls: [{
l: ''lemma'',
p: {
pp: 8.9
}
},
{
l: ''lemma2'',
p: {
pp: 8.3
}
}
]
};
Escriba en la consola:
var schemafy = function(schema, i, limit) {
var i = (typeof i !== ''undefined'') ? i : 1;
var limit = (typeof limit !== ''undefined'') ? limit : false;
var type = '''';
var array = false;
for (key in schema) {
type = typeof schema[key];
array = (schema[key] instanceof Array) ? true : false;
if (type === ''object'') {
print(Array(i).join('' '') + key+'' <''+((array) ? ''array'' : type)+''>:'');
schemafy(schema[key], i+1, array);
} else {
print(Array(i).join('' '') + key+'' <''+type+''>'');
}
if (limit) {
break;
}
}
}
Correr:
schemafy(db.collection.findOne());
Salida
_id <number>
id <number>
t <string>
p <number>
ls <object>:
0 <object>:
l <string>
p <object>:
pp <number>
Para obtener una lista de todas las claves menos _id
, considere ejecutar la siguiente canalización agregada:
var keys = db.collection.aggregate([
{ "$project": {
"hashmaps": { "$objectToArray": "$$ROOT" }
} },
{ "$project": {
"fields": "$hashmaps.k"
} },
{ "$group": {
"_id": null,
"fields": { "$addToSet": "$fields" }
} },
{ "$project": {
"keys": {
"$setDifference": [
{
"$reduce": {
"input": "$fields",
"initialValue": [],
"in": { "$setUnion" : ["$$value", "$$this"] }
}
},
["_id"]
]
}
}
}
]).toArray()[0]["keys"];
Prueba esto:
doc=db.thinks.findOne();
for (key in doc) print(key);
Puede usar la agregación con el nuevo $objectToArrray
en la versión 3.4.4
para convertir todos los pares de clave y valor superiores en arreglos de documentos seguidos de $unwind
& $group
con $addToSet
para obtener claves distintas en toda la colección.
$$ROOT
para referenciar el documento de nivel superior.
db.things.aggregate([
{"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
{"$unwind":"$arrayofkeyvalue"},
{"$group":{"_id":null,"allkeys":{"$addToSet":"$arrayofkeyvalue.k"}}}
])
Puede usar la siguiente consulta para obtener claves en un solo documento.
db.things.aggregate([
{"$project":{"arrayofkeyvalue":{"$objectToArray":"$$ROOT"}}},
{"$project":{"keys":"$arrayofkeyvalue.k"}}
])
Puedes hacer esto con MapReduce:
mr = db.runCommand({
"mapreduce" : "my_collection",
"map" : function() {
for (var key in this) { emit(key, null); }
},
"reduce" : function(key, stuff) { return null; },
"out": "my_collection" + "_keys"
})
Luego ejecute distinto en la colección resultante para encontrar todas las claves:
db[mr.result].distinct("_id")
["foo", "bar", "baz", "_id", ...]
Según la documentation mongoldb, una combinación de distinct
Encuentra los valores distintos para un campo específico en una sola colección o vista y devuelve los resultados en una matriz.
y las operaciones de recopilación de indexes son lo que devolvería todos los valores posibles para una clave o índice dado:
Devuelve una matriz que contiene una lista de documentos que identifican y describen los índices existentes en la colección.
Entonces, en un método dado, se podría usar un método como el siguiente, para consultar una colección para todos sus índices registrados, y devolver, digamos un objeto con los índices para las claves (este ejemplo usa async / await para NodeJS, pero obviamente se podría usar cualquier otro enfoque asíncrono):
async function GetFor(collection, index) {
let currentIndexes;
let indexNames = [];
let final = {};
let vals = [];
try {
currentIndexes = await collection.indexes();
await ParseIndexes();
//Check if a specific index was queried, otherwise, iterate for all existing indexes
if (index && typeof index === "string") return await ParseFor(index, indexNames);
await ParseDoc(indexNames);
await Promise.all(vals);
return final;
} catch (e) {
throw e;
}
function ParseIndexes() {
return new Promise(function (result) {
let err;
for (let ind in currentIndexes) {
let index = currentIndexes[ind];
if (!index) {
err = "No Key For Index "+index; break;
}
let Name = Object.keys(index.key);
if (Name.length === 0) {
err = "No Name For Index"; break;
}
indexNames.push(Name[0]);
}
return result(err ? Promise.reject(err) : Promise.resolve());
})
}
async function ParseFor(index, inDoc) {
if (inDoc.indexOf(index) === -1) throw "No Such Index In Collection";
try {
await DistinctFor(index);
return final;
} catch (e) {
throw e
}
}
function ParseDoc(doc) {
return new Promise(function (result) {
let err;
for (let index in doc) {
let key = doc[index];
if (!key) {
err = "No Key For Index "+index; break;
}
vals.push(new Promise(function (pushed) {
DistinctFor(key)
.then(pushed)
.catch(function (err) {
return pushed(Promise.resolve());
})
}))
}
return result(err ? Promise.reject(err) : Promise.resolve());
})
}
async function DistinctFor(key) {
if (!key) throw "Key Is Undefined";
try {
final[key] = await collection.distinct(key);
} catch (e) {
final[key] = ''failed'';
throw e;
}
}
}
Por lo tanto, consultar una colección con el índice _id
básico, devolvería lo siguiente (la colección de prueba solo tiene un documento en el momento de la prueba):
Mongo.MongoClient.connect(url, function (err, client) {
assert.equal(null, err);
let collection = client.db(''my db'').collection(''the targeted collection'');
GetFor(collection, ''_id'')
.then(function () {
//returns
// { _id: [ 5ae901e77e322342de1fb701 ] }
})
.catch(function (err) {
//manage your error..
})
});
Eso sí, esto utiliza métodos nativos del controlador NodeJS. Como algunas otras respuestas han sugerido, existen otros enfoques, como el marco agregado. Personalmente, encuentro este enfoque más flexible, ya que puede crear y ajustar fácilmente cómo devolver los resultados. Obviamente, esto solo aborda los atributos de nivel superior, no los anidados. Además, para garantizar que todos los documentos estén representados en caso de que existan índices secundarios (además del principal _id), dichos índices deben configurarse según sea required
.
Si está utilizando mongodb 3.4.4 y superior, puede usar la agregación inferior utilizando $objectToArrray y la agregación de $group
db.collection.aggregate([
{ "$project": {
"data": { "$objectToArray": "$$ROOT" }
}},
{ "$project": { "data": "$data.k" }},
{ "$unwind": "$data" },
{ "$group": {
"_id": null,
"keys": { "$addToSet": "$data" }
}}
])
Aquí está el example trabajo
Si su colección objetivo no es demasiado grande, puede intentarlo en mongo shell client:
var allKeys = {};
db.YOURCOLLECTION.find().forEach(function(doc){Object.keys(doc).forEach(function(key){allKeys[key]=1})});
allKeys;
Tal vez un poco fuera de tema, pero puede imprimir de forma recursiva todas las claves / campos de un objeto:
function _printFields(item, level) {
if ((typeof item) != "object") {
return
}
for (var index in item) {
print(" ".repeat(level * 4) + index)
if ((typeof item[index]) == "object") {
_printFields(item[index], level + 1)
}
}
}
function printFields(item) {
_printFields(item, 0)
}
Útil cuando todos los objetos en una colección tienen la misma estructura.
Tengo 1 trabajo más simple alrededor ...
Lo que puede hacer es insertar datos / documentos en su colección principal "cosas", debe insertar los atributos en 1 colección separada, digamos "cosas_atributos".
así que cada vez que inserta en "cosas", obtiene de "cosas_atributos" comparar los valores de ese documento con sus nuevas claves de documento si alguna nueva clave presente lo agrega en ese documento y lo vuelve a insertar.
Por lo tanto, things_attributes tendrá solo 1 documento de claves únicas que puede obtener fácilmente cuando lo necesite utilizando findOne ()
Una solución limpia y reutilizable usando pymongo:
from pymongo import MongoClient
from bson import Code
def get_keys(db, collection):
client = MongoClient()
db = client[db]
map = Code("function() { for (var key in this) { emit(key, null); } }")
reduce = Code("function(key, stuff) { return null; }")
result = db[collection].map_reduce(map, reduce, "myresults")
return result.distinct(''_id'')
Uso:
get_keys(''dbname'', ''collection'')
>> [''key1'', ''key2'', ... ]
Utilizando python. Devuelve el conjunto de todas las claves de nivel superior de la colección:
#Using pymongo and connection named ''db''
reduce(
lambda all_keys, rec_keys: all_keys | set(rec_keys),
map(lambda d: d.keys(), db.things.find()),
set()
)
var schematodo = db.[collection].findOne();
for (var key in schematodo) { print (key) ; }