MongoDB - Aggregation, group by an array value - mongodb

I have the following document structure:
{
_id: ...,
name: "Item1",
Props: [
{
Key: "numberKey",
Val: 1234
},
{
Key: "dateKey",
Val: Date("2013-09-09")
}]
}
This is simplified and there can be various Keys and Values in Props field in the real application.
My question - is it possible to $group and $sum "numberKey"s by "dateKey"s?
What structure should I use if this is not possible? I need users to let add keys and values so I need something flexible.

Unfortunately, that isn't possible using aggregation with your schema. The problem is that aggregation is meant to operate over values in an array that are being selected by the $group clause and those elements have all the data needed. Your setup separates what you want to group by and what you want to sum. You could use a mapReduce job to do what you want with your schema. http://docs.mongodb.org/manual/core/map-reduce/ should be able to get you started.
Let me know if you have any other questions.
Best,
Charlie

Related

what is the proper way to use $nin operator with mongoDB

I want to find entries in my MongoDB collection that match some filters.
Each entry in my mongo collection looks like this data:
{
type: "admin"
senderId: "6131e7c597f50700160703fe"
read_by: [
{
Object_id: 614dbbf83ad51412f16c0757
readerId: "60b968dc5150a20015d6fcae"
}
]
},
{
type: "admin"
senderId: "6131e7c597f50700160703fe"
read_by: [
{}
]
}
What I want to achieve properly, is to filter on the collection and get only the entries that match 'admin' as type and that don't have the current user's ID in the read_by array (that is an array of objects)
I wrote this (and tried some other combinations with errors :) )
but it is not working, I get 0 entries on the end, but I expect to get one of the two as the second have it's read_by array empty.
Thank you very much!
I validated my solution using cloud.mongodb.com interface and the simplest following filter seems to do the job:
{ "read_by.readerId": {$ne:"60b968dc5150a20015d6fcae"}}
Only the record with empty array is being returned.
$nin operator works fine as well but if there is only single value for comparision then $ne should be enough.
{ "read_by.readerId": {$nin: ["60b968dc5150a20015d6fcae"]}}

MongoDB: concatinate multiple number values to string

I have a document (inside aggregation, after $group stage) which have an object (but I could form array, if I needed it to) with number values.
MongoPlayground example with data and my aggregate query available here.
And I want to make a new _id field during next $project stage, consisted of this three number values, like:
item_id | unix time | pointer
_id: 453435-41464556645#1829
The problem is, that when I am trying to use $concat, the query returns me an error like:
$concat only supports strings, not int
So here is my question: is it possible to achieve such results? I have seen the relevant question MongoDB concatenate strings from two fields into a third field, but it didn't cover my case.
The $concat only concatenate strings, these fields $_id.item_id contains int value and $_id.last_modified double value,
The $toString converts a value to a string,
_id: {
$concat: [
{
$toString: "$_id.item_id"
},
" - ",
{
$toString: "$_id.last_modified"
}
]
}
Playground: https://mongoplayground.net/p/SSlXW4gIs_X

sorting documents in mongodb

Let's say I have four documents in my collection:
{u'a': {u'time': 3}}
{u'a': {u'time': 5}}
{u'b': {u'time': 4}}
{u'b': {u'time': 2}}
Is it possible to sort them by the field 'time' which is common in both 'a' and 'b' documents?
Thank you
No, you should put your data into a common format so you can sort it on a common field. It can still be nested if you want but it would need to have the same path.
You can use use aggregation and the following code has been tested.
db.test.aggregate({
$project: {
time: {
"$cond": [{
"$gt": ["$a.time", null]
}, "$a.time", "$b.time"]
}
}
}, {
$sort: {
time: -1
}
});
Or if you also want the original fields returned back: gist
Alternatively you can sort once you get the result back, using a customized compare function ( not tested,for illustration purpose only)
db.eval(function() {
return db.mycollection.find().toArray().sort( function(doc1, doc2) {
var time1 = doc1.a? doc1.a.time:doc1.b.time,
time2 = doc2.a?doc2.a.time:doc2.b.time;
return time1 -time2;
})
});
You can, using the aggregation framework.
The trick here is to $project a common field to all the documents so that the $sort stage can use the value in that field to sort the documents.
The $ifNull operator can be used to check if a.time exists, it
does, then the record will be sorted by that value else, by b.time.
code:
db.t.aggregate([
{$project:{"a":1,"b":1,
"sortBy":{$ifNull:["$a.time","$b.time"]}}},
{$sort:{"sortBy":-1}},
{$project:{"a":1,"b":1}}
])
consequences of this approach:
The aggregation pipeline won't be covered by any of the index you
create.
The performance will be very poor for very large data sets.
What you could ideally do is to ask the source system that is sending you the data to standardize its format, something like:
{"a":1,"time":5}
{"b":1,"time":4}
That way your query can make use of the index if you create one on the time field.
db.t.ensureIndex({"time":-1});
code:
db.t.find({}).sort({"time":-1});

Fields ordering using find() in mongodb

All!
My document has such structure:
{
fname: value,
lname: value,
city: value
}
When I use find() method, I get result in default order fname, lname, city.
But I want to get result in other order of field, such as: city, fname, lname.
Does mongodb allow fields ordering in result?
Yes and no. To really do this sort of manipulation you need the aggregation framework. Even then it's a bit tricky since you must "rename" the fields in order to change their order. This is because there are general optimizations in place that "copy" content from one "pipeline" stage to another. This is considered optimal.
But you can always do this:
db.collection.aggregate([
{ "$project": {
"tcity": "$city",
"tfname": "$fname",
"tlname": "$lanme"
}},
{ "$project": {
"city": "$tcity",
"fname": "$tfname",
"lname": "$tlname"
}}
])
Very contrived, but that is the way you have to do it. Or otherwise just live with a single projection for the "renamed" fields in the order you want and then just "rename" again in code.
Or of course, your code can simply "re-map" the field names from the result of a query.
But the basic point is that MongoDB itself "preserves" the original order of fields as an "optimization" to how they are stored and does not mess with the output otherwise. If you want to you can, but you need to take the steps as shown in order to do so.
You can use .sort(). eg:
db.stocks.find().sort( { ticker: 1, date: -1 } )
For more info see http://docs.mongodb.org/manual/reference/method/cursor.sort/
I assume you are referring to the output as it shows up in the Mongo shell? If so, you can assign the returned output to a variable (cursor, I believe) and step through your cursor afterwards while outputting the individual field variables in any order you like.

Mongo - most efficient way to $inc values in this schema

I'm a Mongo newbie, and trying to make this schema work.
Its intended for logging events as they happen on a high traffic website - just incrementing the number of times a certain action happens.
{
_id: "20110924",
Vals:[
{ Key: "SomeStat1": Value: 1},
{ Key: "SomeStat2": Value: 2},
{ Key: "SomeStat3": Value: 3}
]
}
,
{
_id: "20110925",
Vals:[
{ Key: "SomeStat1": Value: 3},
{ Key: "SomeStat8": Value: 13},
{ Key: "SomeStat134": Value: 63}
]
}, etc.
So _id here is the date, then an array of the different stats, and the number of times they've occurred. There may be no stats for that day, and the stat keys can be dynamic.
I'm looking for the most efficient way to achieve these updates, avoiding race conditions...so ideally all atomic.
I've got stuck when trying to do the $inc. When I specify that it should upsert, it tries to match the whole document as the conditional, and it fails on duplicate keys. Similarly for $addToSet - if I addToSet with { Key:"SomeStat1" }, it won't think it's a duplicate as it's matching against the entire document, and hence insert it alongside the existing SomeStat1 value.
What's the best approach here? Is there a way to control how $addToSet matches? Or do I need a different schema?
Thanks in advance.
You're using a bad schema, it's impossible to do atomic updates on it. Do it like this:
{
_id: "20110924",
Vals: {
SomeStat1: 1,
SomeStat2: 2,
SomeStat3: 3,
}
}
or you can skip The Vals subdocument and embed the stats in the main document.
Take a look at this article which explains the issues around serializing a Dictionary using the MongoDB C# driver. See also this work item on the C# driver.