Listing, counting factors of unique Mongo DB values over all keys - mongodb

I'm preparing a descriptive "schema" (quelle horreur) for a MongoDB I've been working with.
I used the excellent variety.js to create a list of all keys and show coverage of each key. However, in cases where the values corresponding to the keys have a small set of values, I'd like to be able to list the entire set as "available values." In R, I'd be thinking of these as the "factors" for the categorical variable, ie, gender : ["M", "F"].
I know I could just use R + RMongo, query each variable, and basically do the same procedure I would to create a histogram, but I'd like to know the proper Mongo.query()/javascript/Map,Reduce way to approach this. I understand the db.collection.aggregate() functions are designed for exactly this.
Before asking this, I referenced:
http://docs.mongodb.org/manual/reference/aggregation/
http://docs.mongodb.org/manual/reference/method/db.collection.distinct/
How to query for distinct results in mongodb with python?
Get a list of all unique tags in mongodb
http://cookbook.mongodb.org/patterns/count_tags/
But can't quite get the pipeline order right. So, for example, if I have documents like these:
{_id : 1, "key1" : "value1", "key2": "value3"}
{_id : 2, "key1" : "value2", "key2": "value3"}
I'd like to return something like:
{"key1" : ["value1", "value2"]}
{"key2" : ["value3"]}
Or better, with counts:
{"key1" : ["value1" : 1, "value2" : 1]}
{"key2" : ["value3" : 2]}
I recognize one problem with doing this will be any values that have a wide range of different values---so, text fields, or continuous variables. Ideally, if there were more than x different possible values, it would be nice to truncate, say to no more than 20 unique values. If I find it's actually more, I'd query that variable directly.
Is this something like:
db.collection.aggregate(
{$limit: 20,
$group: {
_id: "$??varname",
count: {$sum: 1}
}})
First, how can I reference ??varname? for the name of each key?
I saw this link which had 95% of it:
Binning and tabulate (unique/count) in Mongo
with...
input data:
{ "_id" : 1, "age" : 22.34, "gender" : "f" }
{ "_id" : 2, "age" : 23.9, "gender" : "f" }
{ "_id" : 3, "age" : 27.4, "gender" : "f" }
{ "_id" : 4, "age" : 26.9, "gender" : "m" }
{ "_id" : 5, "age" : 26, "gender" : "m" }
This script:
db.collection.aggregate(
{$project: {gender:1}},
{$group: {
_id: "$gender",
count: {$sum: 1}
}})
Produces:
{"result" :
[
{"_id" : "m", "count" : 2},
{"_id" : "f", "count" : 3}
],
"ok" : 1
}
But what I don't understand is how could I do this generically for an unknown number/name of keys with a potentially large number of return values? This sample knows the key name is gender, and that the response set will be small (2 values).

If you already ran a script that outputs the names of all keys in the collection, you can generate your aggregation framework pipeline dynamically. What that means is either extending the variety.js type script or just writing your own.
Here is what it might look like in JS if passed an array called "keys" which has several non-"_id" named fields (I'm assuming top level fields and that you don't care about arrays, embedded documents, etc).
keys = ["key1", "key2"];
group = { "$group" : { "_id" : null } } ;
keys.forEach( function(f) {
group["$group"][f+"List"] = { "$addToSet" : "$" + f }; } );
db.collection.aggregate(group);
{
"result" : [
{
"_id" : null,
"key1List" : [
"value2",
"value1"
],
"key2List" : [
"value3"
]
}
],
"ok" : 1
}

Related

Mongo aggregation on array elements

I have a mongo document like
{ "_id" : 12, "location" : [ "Kannur","Hyderabad","Chennai","Bengaluru"] }
{ "_id" : 13, "location" : [ "Hyderabad","Chennai","Mysore","Ballary"] }
From this how can I get the location aggregation (distinct area count).
some thing like
Hyderabad 2,
Kannur 1,
Chennai 2,
Bengaluru 1,
Mysore 1,
Ballary 1
Using aggregation you cannot get the exact output that you want. One of the limitations of aggregation pipeline is its inability to transform values to keys in the output document.
For example, Kannur is one of the values of the location field, in the input document. In your desired output structure it needs to be the key("kannur":1). This is not possible using aggregation. While, this can be used achieving map-reduce, you can however get a very closely related and useful structure using aggregation.
Unwind the location array.
Group by the location fields, get the count of individual locations
using the $sum operator.
Group again all the documents once again to get a consolidated array
of results.
Code:
db.collection.aggregate([
{$unwind:"$location"},
{$group:{"_id":"$location","count":{$sum:1}}},
{$group:{"_id":null,"location_details":{$push:{"location":"$_id",
"count":"$count"}}}},
{$project:{"_id":0,"location_details":1}}
])
Sample o/p:
{
"location_details" : [
{
"location" : "Ballary",
"count" : 1
},
{
"location" : "Mysore",
"count" : 1
},
{
"location" : "Bengaluru",
"count" : 1
},
{
"location" : "Chennai",
"count" : 2
},
{
"location" : "Hyderabad",
"count" : 2
},
{
"location" : "Kannur",
"count" : 1
}
]
}

How sum in MongoDB nested document when the KEY is uncertain ?

First of all the status codes("200","404" or other) and time("1000","2000"..) are uncertain,
I want to calculate the number(5, 6 ...) for each status codes.
For example: {"200" : 11}, {"404" :11} or {"total" : 22}
Data Structure :
"_id" : "xxxxx"
"domain" : "www.test.com"
"status" : [
{"200" : [ {"1000" : 5}, {"2000": 6} ...]},
{"404" : [ {"1000" : 5}, {"2000": 6} ...]}
....
]
Any fantastic methods in MongoDB ?
Thank you for your help
Don't use data, like dates, as keys. Data belongs in values. The HTTP status codes are enumerated - you know all the possibilities - so you can use those as keys if you want to. From the look of the documents, you are storing information about requests to a page in a page document with the requests in an array. It's not a great idea to have an unbounded, constantly growing array in a document. I'd suggest refactoring the data to be request documents with the address denormalized into each:
{
"_id" : ObjectId(...),
"status" : 404,
"date" : ISODate("2014-10-30T18:23:09.471Z"),
"domain" : "www.test.com"
}
and then you can get the total number of 404 requests to test.com with the aggregation
db.requests.aggregate([
{ "$match" : { "domain" : "www.test.com" } },
{ "$group" : { "_id" : "$status", "count" : { "$sum" : 1 } } }
])
Index on domain to make it fast.
I think you can use the aggregation framework to pull something like that.
Check this:
db.errors.aggregate([{$unwind: "$status"}, {$group: {_id: "$status", total:{$sum:1}}}])
It will render a result like this:
...
"result" : [
{
"_id" : {
"500" : [
{
"1000" : 5
},
{
"2000" : 6
}
]
},
"total" : 1
},
...
The "total" field has the count that you're looking for.
Hope this helps.
Regards!

Using the db.collection.find query in a sub-document

Is there a way to use db.collection.find() to query for a specific value in a sub-document and find those documents that match. For example:
{
{ 'Joe' : {eyecolor : 'brown'},
{ 'Mary' : {eyecolor : 'blue'},
....
}
I want to return the names of all people whose eyecolor is blue.
You need to specify the full path to a value for search to work:
db.people.find({ "Joe.eyecolor" : "brown" })
You can't switch to an array of people instead of an associative array style you're using now, as there is no way to return only array elements that match conditions. You can use $elemMatch to return the first match, but that's not likely what you'd want. Or, you could still use arrays, but you'd need to filter the array further within your client code (not the database).
You might be able to use the Aggregation framework, but it wouldn't use indexes efficiently, as you'd need to $unwind the entire array, and then do filtering, brute force. And if the data contained is more complex, the fact that projections when using the AF require you to manually specify all fields, it becomes a bit cumbersome.
To most efficiently do the query you're showing, you'd need to not use subdocuments, and instead place the people as individual documents:
{
name: "Joe",
eyecolor: "brown"
}
Then, you could just do a simple search like:
db.people.find({eyecolor: "brown"})
Yes and no. You can query for all documents that have a matching person, but you can't query for all persons directly. In other words, subdocuments are not virtual collections, you'll always have the 'parent' document returned.
The example you posted comes with the additional complexity that you're using the name as a field key, which prevents you from using the dot notation.
In general, if you have a number of similar things, it's best to put them in a list, e.g.
{
"_id" : 132,
"ppl" : [ { "Name" : "John", "eyecolor" : "blue" },
{ "Name" : "Mary", "eyecolor" : "brown" },
...
]
}
Then, you can query using the aggregation framework:
db.collection.aggregate([
// only match documents that have a person w/ blue eyes (can use indexing)
{$match : { "ppl.eyecolor" : "blue" } },
// unwind the array of people
{$unwind : "$ppl" },
// match only those with blue eyes
{$match : { "ppl.eyecolor" : "blue" }},
// optional projection to make the result a list of people
{$project : { Name : "$ppl.Name", EyeColor: "$ppl.eyecolor" }} ]);
Which gives a result like
"result" : [
{
"_id" : 132,
"Name" : "John",
"EyeColor" : "blue"
},
{
"_id" : 12,
"Name" : "Jimmy",
"EyeColor" : "blue"
},
{
"_id" : 4312,
"Name" : "Jimmy",
"EyeColor" : "blue"
},
{
"_id" : 4312,
"Name" : "Marc",
"EyeColor" : "blue"
}
],
"ok" : 1

mongodb change $group output format

I have the following document structure
{
"timestamp" : 13512493603565120,<br>
"value" : 1,<br>
"y" : 42,<br>
"M" : 513,<br>
"w" : 2234,<br>
"d" : 15639,<br>
"S" : 46918,<br>
"h" : 375347,<br>
"m" : 22520822,<br>
"s" : 1351249360,<br>
"_id" : ObjectId("508aa61100b5457c04000001"),<br>
"__v" : 0<br>
}
I have a mongodb aggregate as follows to sum up values grouping by field y:
aggregate({
$group : {_id : "$y", value:{$sum:4}}
})
This will give me
[
{
"_id": 42,
"value": 16
}
]
What I want now is to format this output so that it looks like this:
[
[13512493603565100, 2],
[13512493605167900, 1]
]
ie:
[
[<timestamp>,<sum of value grouped by field y>],
[<timestamp>,<sum of value grouped by field y>]
]
I looked at $project but I still cant figure out how I can use it to get the desired output
Not possible as of now apparently (using mapreduce or aggregate).
Hope this will be added soon.

Map reduce in mongodb

I have mongo documents in this format.
{"_id" : 1,"Summary" : {...},"Examples" : [{"_id" : 353,"CategoryId" : 4},{"_id" : 239,"CategoryId" : 28}, ... ]}
{"_id" : 2,"Summary" : {...},"Examples" : [{"_id" : 312,"CategoryId" : 2},{"_id" : 121,"CategoryId" : 12}, ... ]}
How can I map/reduce them to get a hash like:
{ [ result[categoryId] : count_of_examples , .....] }
I.e. count of examples of each category.
I have 30 categories at all, all specified in Categories collection.
If you can use 2.1 (dev version of upcoming release 2.2) then you can use Aggregation Framework and it would look something like this:
db.collection.aggregate( [
{$project:{"CatId":"$Examples.CategoryId","_id":0}},
{$unwind:"$CatId"},
{$group:{_id:"$CatId","num":{$sum:1} } },
{$project:{CategoryId:"$_id",NumberOfExamples:"$num",_id:0 }}
] );
The first step projects the subfield of Examples (CategoryId) into a top level field of a document (not necessary but helps with readability), then we unwind the array of examples which creates a separate document for each array value of CatId, we do a "group by" and count them (I assume each instance of CategoryId is one example, right?) and last we use projection again to relabel the fields and make the result look like this:
"result" : [
{
"CategoryId" : 12,
"NumberOfExamples" : 1
},
{
"CategoryId" : 2,
"NumberOfExamples" : 1
},
{
"CategoryId" : 28,
"NumberOfExamples" : 1
},
{
"CategoryId" : 4,
"NumberOfExamples" : 1
}
],
"ok" : 1