Count ignoring duplicate documents - mongodb

I'd like to count all emails within the specific project (ID: 7), but ignoring duplicate rows in ONE campaign.
Here's the example of my collection structure:
{
"_id" : ObjectId("581a9054c274f7b512e8ed94"),
"email" : "a#example.com",
"IDproject" : 7,
"IDcampaign" : 10
}
{
"_id" : ObjectId("581a9064c274f7b512e8ed95"),
"email" : "b#example.com",
"IDproject" : 7,
"IDcampaign" : 10
}
{
"_id" : ObjectId("581a9068c274f7b512e8ed96"),
"email" : "b#example.com",
"IDproject" : 7,
"IDcampaign" : 10
}
{
"_id" : ObjectId("581a906cc274f7b512e8ed97"),
"email" : "b#example.com",
"IDproject" : 7,
"IDcampaign" : 11
}
{
"_id" : ObjectId("581a9072c274f7b512e8ed98"),
"email" : "c#example.com",
"IDproject" : 7,
"IDcampaign" : 11
}
{
"_id" : ObjectId("581a9079c274f7b512e8ed99"),
"email" : "d#example.com",
"IDproject" : 7,
"IDcampaign" : 12
}
This is what the result should be:
a#example.com
b#example.com
b#example.com
c#example.com
d#example.com
Total: 5 (of 6). Note that b#example.com is mentioned twice. That's because b#example.com has campaigns 10, 10 and 11. We're ignoring one 10.
This is what I've tried:
db.mycollection.aggregate([
{$match : {IDproject : 7}},
{$group : {_id : "$email", total : {$sum : 1}}}
])
But it returns only unique emails ignoring IDcampaign. Also, I can get unique number of emails with the following query:
db.mycollection.distinct('email', {IDproject : 7})
But again, it shows only unique emails ignoring IDcampaign.
Could someone give me a hint how to count emails including IDcampaign?
Thanks.
p.s. I use MongoDB with PHP, and I can solve the problem with PHP calculations, but that's not the solution.

Include it as part of your group key, as in the following example:
db.mycollection.aggregate([
{ "$match": { "IDproject": 7 } },
{
"$group": {
"_id": {
"email" : "$email",
"IDcampaign" : "$IDcampaign"
},
"count": { "$sum": 1 }
}
}
])
Sample Output
/* 1 */
{
"_id" : {
"email" : "a#example.com",
"IDcampaign" : 10
},
"count" : 1
}
/* 2 */
{
"_id" : {
"email" : "d#example.com",
"IDcampaign" : 12
},
"count" : 1
}
/* 3 */
{
"_id" : {
"email" : "b#example.com",
"IDcampaign" : 11
},
"count" : 1
}
/* 4 */
{
"_id" : {
"email" : "b#example.com",
"IDcampaign" : 10
},
"count" : 2
}
/* 5 */
{
"_id" : {
"email" : "c#example.com",
"IDcampaign" : 11
},
"count" : 1
}
To answer your follow-up question on getting the count only since you don't need the list of emails, you could run the following pipeline
db.mycollection.aggregate([
{ "$match": { "IDproject": 7 } },
{
"$group": {
"_id": null,
"count": { "$sum": 1 },
"emails": {
"$addToSet": {
"email" : "$email",
"IDcampaign" : "$IDcampaign"
}
}
}
},
{
"$project": {
"_id": 0,
"count": 1,
"total": { "$size": "$emails" }
}
}
])
which gives you the result
{
"total" : 5,
"count" : 6
}
that you can interpret as Total 5 (of 6)

Related

how to show 0 for week when no record is matching that week in $week mongodb query

My collection looks like below with details
/* 1 createdAt:6/13/2018, 5:17:07 PM*/
{ "_id" : ObjectId("5b21043b18f3bc7c0be3414c"),
"Number" : 242,
"State" : "2",
"City" : "3",
"Website" : "",
"Contact_Person_Name" : "Ajithmullassery",
"CreatedById" : "Admin",
"UpdatedById" : "Admin",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-13T17:17:07.313+05:30"),
"CreatedOn" : ISODate("2018-06-13T17:17:07.313+05:30")
},
/* 2 createdAt:6/13/2018, 6:45:42 PM*/
{
"_id" : ObjectId("5b2118fe18f3bc7c0be3415b"),
"Number" : 243,
"State" : "1",
"City" : "143",
"Website" : "",
"Contact_Person_Name" : "sachitkumar",
"CreatedById" : "vinoth",
"UpdatedById" : "Admin",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-13T18:45:42.590+05:30"),
"CreatedOn" : ISODate("2018-06-13T18:45:42.590+05:30")
},
/* 3 createdAt:6/18/2018, 5:34:33 PM*/
{
"_id" : ObjectId("5b279fd118f3bc7c0be34166"),
"Number" : 244,
"State" : "0",
"City" : "8",
"Website" : "",
"Contact_Person_Name" : "Akshay",
"CreatedById" : "vinoth",
"UpdatedById" : "Admin",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-18T17:34:33.270+05:30"),
"CreatedOn" : ISODate("2018-06-18T17:34:33.270+05:30")
},
/* 4 createdAt:6/20/2018, 1:02:21 PM*/
{
"_id" : ObjectId("5b2a030518f3bc7c0be3416d"),
"Number" : 245,
"State" : "5",
"City" : "6",
"Website" : "",
"Contact_Person_Name" : "Dr DS Mithra",
"CreatedById" : "vinoth",
"UpdatedById" : "Admin",
"FacilityID" : "594387f5e2de7be83be5d5f1",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-20T13:02:21.887+05:30"),
"CreatedOn" : ISODate("2018-06-20T13:02:21.887+05:30")
},
/* 5 createdAt:6/20/2018, 1:08:58 PM*/
{
"_id" : ObjectId("5b2a049218f3bc7c0be3416e"),
"Number" : 245,
"State" : "5",
"City" : "6",
"Website" : "",
"Contact_Person_Name" : "Ramaswamy Manickam",
"CreatedById" : "vinoth",
"UpdatedById" : "Admin",
"IsActive" : true,
"UpdatedOn" : ISODate("2018-06-20T13:08:58.040+05:30"),
"CreatedOn" : ISODate("2018-06-20T13:08:58.040+05:30")
}
I have the query like below
db.collectionName.aggregate([
//where query
{ "$match": { $and:[{CreatedOn:{$lte:ISODate("2018-07-14T13:59:08.266+05:30")}},{CreatedOn:{$gte:ISODate("2018-06-10T13:59:08.266+05:30")}}] } },
//distinct column
{
"$group": {
_id: {$week: '$CreatedOn'},
documentCount: {$sum: 1}
}
}
])
The query will return the weeknumber and number of documents created as below
/* 1 */
{
"_id" : 26,
"documentCount" : 1
},
/* 2 */
{
"_id" : 25,
"documentCount" : 1
},
/* 3 */
{
"_id" : 24,
"documentCount" : 9
},
/* 4 */
{
"_id" : 23,
"documentCount" : 2
}
In above _id is the weeknumber. If in case in above results weekNumber : 23 no records are created then the query gives only 3 records removing the "_id":23.
How to get the records with documentcount as zero when there is no records created.
Like in above example when no records for _id: 23 should get like below
/* 4 */
{
"_id" : 23,
"documentCount" : 0
}
As $week can return a value between 0 and 53 I assume you expect 54 documents as a result with 0 or non-zero values for documentCount. To achieve that you should collect all your documents into one ($group-ing by null) and then generate the output.
To generate a range of numbers you can use $range operator and then you can generate the output using $map. To transform an array of documents into multiple docs you can use $unwind.
db.collectionName.aggregate([
//where query
{ "$match": { $and:[{CreatedOn:{$lte:ISODate("2018-07-14T13:59:08.266+05:30")}},{CreatedOn:{$gte:ISODate("2018-06-10T13:59:08.266+05:30")}}] } },
//distinct column
{
"$group": {
_id: {$week: '$CreatedOn'},
documentCount: {$sum: 1}
}
},
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$project: {
docs: {
$map: {
input: { $range: [ {$week:ISODate("2018-06-10T13:59:08.266+05:30")}, {$week:ISODate("2018-07-14T13:59:08.266+05:30")}]},
as: "weekNumber",
in: {
$let: {
vars: { index: { $indexOfArray: [ "$docs._id", "$$weekNumber" ] } },
in: {
$cond: {
if: { $eq: [ "$$index", -1 ] },
then: { _id: "$$weekNumber", documentCount: 0 },
else: { $arrayElemAt: [ "$docs", "$$index" ] }
}
}
}
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceRoot: {
newRoot: "$docs"
}
}
])
Using $indexOfArray to check if array of current docs contains the document (-1 otherwise) and $arrayElemAt to get existing document from docs. Last step ($replaceRoot) is just to get rid of one level of nesting (docs). Outputs:
{ "_id" : 0, "documentCount" : 0 }
{ "_id" : 1, "documentCount" : 0 }
{ "_id" : 2, "documentCount" : 0 }
...
{ "_id" : 22, "documentCount" : 0 }
{ "_id" : 23, "documentCount" : 2 }
{ "_id" : 24, "documentCount" : 9 }
{ "_id" : 25, "documentCount" : 1 }
{ "_id" : 26, "documentCount" : 1 }
{ "_id" : 27, "documentCount" : 0 }
...
{ "_id" : 52, "documentCount" : 0 }
{ "_id" : 53, "documentCount" : 0 }
You can easily customize returned results modifying the input of $map stage. For instance you can pass an array of consts like input: [21, 22, 23, 24] as well.
EDIT: To get the weeks between specified dates you can use $week for start and end date to get the numbers.

MongoDB: Aggregation Group operation over a dynamic key-value pair

I have a simple document with one field as a key-value pair. I want to just perform a group operation in Aggregation over those keys and add their values. But the keys in the pair are not fixed and can be anything.
Here is a sample document.
{
_id: 349587843,
matchPair: {
3 : 21,
9 : 4,
7 : 32
}
},
{
_id: 349587478,
matchPair: {
7 : 11,
54 : 32,
9 : 7,
2 : 19
}
}
And I want a result something like the following.
{
_id : 3,
count : 21
},
{
_id : 9,
count : 11
},
{
_id : 7,
count : 43
},
{
_id : 54,
count : 32
},
{
_id : 2,
count : 19
}
I have the following query in mind and tried using $unwindoperation but it doesn't work probably because "matchPair" isn't an array and I don't know what to specify for the $sumoperation.
db.MatchPairs.aggregate([
{ "$unwind" : "$matchPair" },
{ "$group" : {
_id: "$matchPair",
count : { $sum : $matchPair }
} }
]);
I could also try Map-Reduce but for that too I need to emit() keys and values by name.
I'm sure there's a simple solution to this but I can't figure it out.
:
You could start by projecting and reshaping the matchPair field with $objectToArray
New in version 3.4.4.
{
$project: {
matchPair: { $objectToArray: '$matchPair' }
}
}
which would give
{
matchPair: [{ k: 3, v: 21 }, { k: 9, v: 4 }, ...]
}
Then $unwind based on matchPair
{
$unwind: '$matchPair'
}
which would give
{
matchPair: { k: 3, v: 21 }
}
Then $project
{
$project: {
_id: '$matchPair.k',
count: '$matchPair.v'
}
}
That should give the output you want. Altogether would be
.aggregate([
{
$project: {
matchPair: { $objectToArray: '$matchPair' }
}
},
{ $unwind: '$matchPair' },
{
$project: {
_id: '$matchPair.k',
count: '$matchPair.v'
}
}
])
In the mongoDb documentation for $unwind:
Deconstructs an array field from the input documents to output a
document for each element.
So you have to change your schema for something like:
{
"_id" : ObjectId("5880b57b039a3c89c1db145a"),
"matchPair" : [
{
"_id" : "3",
"count" : 21
},
{
"_id" : "9",
"count" : 4
},
{
"_id" : "7",
"count" : 32
}
]
},
{
"_id" : ObjectId("5880b58c039a3c89c1db145b"),
"matchPair" : [
{
"_id" : "7",
"count" : 11
},
{
"_id" : "54",
"count" : 32
},
{
"_id" : "9",
"count" : 7
},
{
"_id" : "2",
"count" : 19
}
]
}
Then doing:
db.MatchPairs.aggregate([
{ $unwind : "$matchPair" }
]);
will return:
{
"_id" : ObjectId("5880b57b039a3c89c1db145a"),
"matchPair" : {
"_id" : "3",
"count" : 21
}
},
{
"_id" : ObjectId("5880b57b039a3c89c1db145a"),
"matchPair" : {
"_id" : "9",
"count" : 4
}
},
{
"_id" : ObjectId("5880b57b039a3c89c1db145a"),
"matchPair" : {
"_id" : "7",
"count" : 32
}
},
{
"_id" : ObjectId("5880b58c039a3c89c1db145b"),
"matchPair" : {
"_id" : "7",
"count" : 11
}
},
{
"_id" : ObjectId("5880b58c039a3c89c1db145b"),
"matchPair" : {
"_id" : "54",
"count" : 32
}
},
{
"_id" : ObjectId("5880b58c039a3c89c1db145b"),
"matchPair" : {
"_id" : "9",
"count" : 7
}
},
{
"_id" : ObjectId("5880b58c039a3c89c1db145b"),
"matchPair" : {
"_id" : "2",
"count" : 19
}
}
Then you just have to do your grouping.

mongodb $unwind empty array

With this data:
{
"_id" : ObjectId("576948b4999274493425c08a"),
"virustotal" : {
"scan_id" : "4a6c3dfc6677a87aee84f4b629303c40bb9e1dda283a67236e49979f96864078-1465973544",
"sha1" : "fd177b8c50b457dbec7cba56aeb10e9e38ebf72f",
"resource" : "4a6c3dfc6677a87aee84f4b629303c40bb9e1dda283a67236e49979f96864078",
"response_code" : 1,
"scan_date" : "2016-06-15 06:52:24",
"results" : [
{
"sig" : "Gen:Variant.Mikey.29601",
"vendor" : "MicroWorld-eScan"
},
{
"sig" : null,
"vendor" : "nProtect"
},
{
"sig" : null,
"vendor" : "CAT-QuickHeal"
},
{
"sig" : "HEUR/QVM07.1.0000.Malware.Gen",
"vendor" : "Qihoo-360"
}
]
}
},
{
"_id" : ObjectId("5768f214999274362f714e8b"),
"virustotal" : {
"scan_id" : "3d283314da4f99f1a0b59af7dc1024df42c3139fd6d4d4fb4015524002b38391-1466529838",
"sha1" : "fb865b8f0227e9097321182324c959106fcd8c27",
"resource" : "3d283314da4f99f1a0b59af7dc1024df42c3139fd6d4d4fb4015524002b38391",
"response_code" : 1,
"scan_date" : "2016-06-21 17:23:58",
"results" : [
{
"sig" : null,
"vendor" : "Bkav"
},
{
"sig" : null,
"vendor" : "ahnlab"
},
{
"sig" : null,
"vendor" : "MicroWorld-eScan"
},
{
"sig" : "Mal/DrodZp-A",
"vendor" : "Qihoo-360"
}
]
}
}
I'm trying to group by and count the vendor when sig is not null in order to obtain something like:
{
"_id" : "Qihoo-360",
"count" : 2
},
{
"_id" : "MicroWorld-eScan",
"count" : 1
},
{
"_id" : "Bkav",
"count" : 0
},
{
"_id" : "CAT-QuickHeal",
"count" : 0
}
At the moment with this code:
db.analysis.aggregate([
{ $unwind: "$virustotal.results" },
{
$group : {
_id : "$virustotal.results.vendor",
count : { $sum : 1 }
}
},
{ $sort : { count : -1 } }
])
I'm getting everything:
{
"_id" : "Qihoo-360",
"count" : 2
},
{
"_id" : "MicroWorld-eScan",
"count" : 2
},
{
"_id" : "Bkav",
"count" : 1
},
{
"_id" : "CAT-QuickHeal",
"count" : 1
}
How can I count 0 if the sig is null?
You need a conditional expression in your $sum operator that will check if the "$virustotal.results.sig" key is null by using the comparison operator $gt (as specified in the documentation's BSON comparsion order)
You can restructure your pipeline by adding this expression as follows:
db.analysis.aggregate([
{ "$unwind": "$virustotal.results" },
{
"$group" : {
"_id": "$virustotal.results.vendor",
"count" : {
"$sum": {
"$cond": [
{ "$gt": [ "$virustotal.results.sig", null ] },
1, 0
]
}
}
}
},
{ "$sort" : { "count" : -1 } }
])
Sample Output
/* 1 */
{
"_id" : "Qihoo-360",
"count" : 2
}
/* 2 */
{
"_id" : "MicroWorld-eScan",
"count" : 1
}
/* 3 */
{
"_id" : "Bkav",
"count" : 0
}
/* 4 */
{
"_id" : "CAT-QuickHeal",
"count" : 0
}
/* 5 */
{
"_id" : "nProtect",
"count" : 0
}
/* 6 */
{
"_id" : "ahnlab",
"count" : 0
}
I changed the null with None and the numbers increased but seems not correct yet.
Basically doing the query in mongoshell I get like
{
"_id" : "Kaspersky",
"count" : 176.0
}
from python:
Kaspersky 64
one of these 2 is wrong :)
So I'm trying to investigate what part of the query in python is not correctly written compared to the mongo shell one.
I did a simple query:
In mongoshell:
rtmp = results_db.analysis.count( { "virustotal.results" : { "$elemMatch" : { "vendor": "Kaspersky", "sig": {"$ne": "null"} } }})
results: 176
db.analysis.count( { "virustotal.results" : { $elemMatch : { "vendor": "Kaspersky", "sig": {$gt: null} } }})
results: 0
Then I tried in python:
rtmp = results_db.analysis.count( { "virustotal.results" : { "$elemMatch" : { "vendor": "Kaspersky", "sig": {"$ne": "null"} } }})
results: 568
rtmp = results_db.analysis.count( { "virustotal.results" : { "$elemMatch" : { "vendor": "Kaspersky", "sig": {"$ne": "None"} } }})
results: 568
rtmp = results_db.analysis.count( { "virustotal.results" : { "$elemMatch" : { "vendor": "Kaspersky", "sig": {"$gt": "None"} } }})
results: 64
rtmp = results_db.analysis.count( { "virustotal.results" : { "$elemMatch" : { "vendor": "Kaspersky", "sig": {"$gt": "null"} } }})
results: 6
hard to says what is the correct value! I suppose 176 but not able to reproduce in python...

Sum the different grades by date in MongoDB [duplicate]

This question already has an answer here:
Mongodb count distinct with multiple group fields
(1 answer)
Closed 6 years ago.
I'm using the restaurants dataset from the MongoDB website. A document has arrays like the following:
{
"grades" : [
{
"date" : ISODate("2014-06-10T00:00:00.000Z"),
"grade" : "A"
},
{
"date" : ISODate("2013-06-05T00:00:00.000Z"),
"grade" : "B",
"score" : 7
},
{
"date" : ISODate("2012-04-13T00:00:00.000Z"),
"grade" : "A"
},
{
"date" : ISODate("2011-10-12T00:00:00.000Z"),
"grade" : "A"
}
]
}
I'm trying to get a list of all dates, with a count of how many of each grade there was on that day.
I've got this far:
db.restaurants.aggregate([{
$unwind : {
path: '$grades'
}
}, {
$group: {
_id: '$grades.date',
grades: {
$push: '$grades.grade'
}
}
}])
Which gives me each date and the grades on that date.
How do I now count the number of each unique grade?
Figured it out with thanks to this question.
The solution is actually much simpler than I was thinking:
db.restaurants.aggregate([{
$unwind : {
path: '$grades'
}
}, {
$group: {
_id: {
date: '$grades.date',
grade: '$grades.grade'
},
count: {
$sum: 1
}
}
}])
This gives a result like:
/* 1 */
{
"_id" : {
"date" : ISODate("2014-06-23T00:00:00.000Z"),
"grade" : "C"
},
"count" : 4
}
/* 2 */
{
"_id" : {
"date" : ISODate("2011-11-01T00:00:00.000Z"),
"grade" : "C"
},
"count" : 3
}
/* 3 */
{
"_id" : {
"date" : ISODate("2014-05-06T00:00:00.000Z"),
"grade" : "A"
},
"count" : 121
}
/* 4 */
{
"_id" : {
"date" : ISODate("2012-08-21T00:00:00.000Z"),
"grade" : "C"
},
"count" : 5
}
/* 5 */
{
"_id" : {
"date" : ISODate("2013-09-04T00:00:00.000Z"),
"grade" : "C"
},
"count" : 4
}

MongoDB: Sort in combination with Aggregation group

I have a collection called transaction with below documents,
/* 0 */
{
"_id" : ObjectId("5603fad216e90d53d6795131"),
"statusId" : "65c719e6727d",
"relatedWith" : "65c719e67267",
"status" : "A",
"userId" : "100",
"createdTs" : ISODate("2015-09-24T13:15:36.609Z")
}
/* 1 */
{
"_id" : ObjectId("5603fad216e90d53d6795134"),
"statusId" : "65c719e6727d",
"relatedWith" : "65c719e6726d",
"status" : "B",
"userId" : "100",
"createdTs" : ISODate("2015-09-24T13:14:31.609Z")
}
/* 2 */
{
"_id" : ObjectId("5603fad216e90d53d679512e"),
"statusId" : "65c719e6727d",
"relatedWith" : "65c719e6726d",
"status" : "C",
"userId" : "100",
"createdTs" : ISODate("2015-09-24T13:13:36.609Z")
}
/* 3 */
{
"_id" : ObjectId("5603fad216e90d53d6795132"),
"statusId" : "65c719e6727d",
"relatedWith" : "65c719e6726d",
"status" : "D",
"userId" : "100",
"createdTs" : ISODate("2015-09-24T13:16:36.609Z")
}
When I run the below Aggregation query without $group,
db.transaction.aggregate([
{
"$match": {
"userId": "100",
"statusId": "65c719e6727d"
}
},
{
"$sort": {
"createdTs": -1
}
}
])
I get the result in expected sorting order. i.e Sort createdTs in descending order (Minimal result)
/* 0 */
{
"result" : [
{
"_id" : ObjectId("5603fad216e90d53d6795132"),
"createdTs" : ISODate("2015-09-24T13:16:36.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795131"),
"createdTs" : ISODate("2015-09-24T13:15:36.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795134"),
"createdTs" : ISODate("2015-09-24T13:14:31.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d679512e"),
"createdTs" : ISODate("2015-09-24T13:13:36.609Z")
}
],
"ok" : 1
}
If I apply the below aggregation with $group, the resultant is inversely sorted(i.e Ascending sort)
db.transaction.aggregate([
{
"$match": {
"userId": "100",
"statusId": "65c719e6727d"
}
},
{
"$sort": {
"createdTs": -1
}
},
{
$group: {
"_id": {
"statusId": "$statusId",
"relatedWith": "$relatedWith",
"status": "$status"
},
"status": {$first: "$status"},
"statusId": {$first: "$statusId"},
"relatedWith": {$first: "$relatedWith"},
"createdTs": {$first: "$createdTs"}
}
}
]);
I get the result in inverse Order i.e. ** Sort createdTs in Ascending order**
/* 0 */
{
"result" : [
{
"_id" : ObjectId("5603fad216e90d53d679512e"),
"createdTs" : ISODate("2015-09-24T13:13:36.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795134"),
"createdTs" : ISODate("2015-09-24T13:14:31.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795131"),
"createdTs" : ISODate("2015-09-24T13:15:36.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795132"),
"createdTs" : ISODate("2015-09-24T13:16:36.609Z")
}
],
"ok" : 1
}
Where am I wrong ?
The $group stage doesn't insure the ordering of the results. See here the first paragraph.
If you want the results to be sorted after a $group, you need to add a $sort after the $group stage.
In your case, you should move the $sort after the $group and before you ask the question : No, the $sort won't be able to use an index after the $group like it does before the $group :-).
The internal algorithm of $group seems to keep some sort of ordering (reversed apparently), but I would not count on that and add a $sort.
You are not doing anything wrong here, Its a $group behavior in Mongodb
Lets have a look in this example
Suppose you have following doc in collection
{ "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2, "date" : ISODate("2014-01-01T08:00:00Z") }
{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1, "date" : ISODate("2014-02-03T09:00:00Z") }
{ "_id" : 3, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : ISODate("2014-02-03T09:05:00Z") }
{ "_id" : 4, "item" : "abc", "price" : 10, "quantity" : 10, "date" : ISODate("2014-02-15T08:00:00Z") }
{ "_id" : 5, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : ISODate("2014-02-15T09:05:00Z") }
{ "_id" : 6, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : ISODate("2014-02-15T12:05:10Z") }
{ "_id" : 7, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : ISODate("2014-02-15T14:12:12Z") }
Now if you run this
db.collection.aggregate([{ $sort: { item: 1,date:1}} ] )
the output will be in ascending order of item and date.
Now if you add group stage in aggregation pipeline it will reverse the order.
db.collection.aggregate([{ $sort: { item: 1,date:1}},{$group:{_id:"$item"}} ] )
Output will be
{ "_id" : "xyz" }
{ "_id" : "jkl" }
{ "_id" : "abc" }
Now the solution for your problem
change "createdTs": -1 to "createdTs": 1 for group