Count duplicate dates occurrences - mongodb

I've a collection in MongoDB of objects with this structure:
{
"_id": "ID",
"email": "EMAIL",
"name": "Foo",
"surname": "Bar",
"orders": [
{
"createdAt": "2019-09-09T07:30:25.575Z"
},
{
"createdAt": "2019-10-30T14:20:04.849Z"
},
{
"createdAt": "2019-10-30T16:38:27.271Z"
},
{
"createdAt": "2020-01-03T15:49:39.614Z"
},
],
}
I need to count all duplicates "createdAt" and distinct it with changing date format.
The result should be like below:
{
"_id": "ID",
"email": "EMAIL",
"name": "Foo",
"surname": "Bar",
"orders": [
{
"date": "2019-09-09",
"total": 1,
},
{
"date": "2019-10-30",
"total": 2,
},
{
"date": "2020-01-03",
"total": 1,
},
],
}
I tried with $unwind orders.createdAt in db.collection.aggregate() but i've no idea how can i get this result.
Thanks in advance.

Try this on for size. Given this data:
db.foo.insert([
{
"_id": "ID",
"email": "EMAIL", "name": "Foo", "surname": "Bar",
"orders": [
{ "createdAt": new Date("2019-09-09T07:30:25.575Z") },
{ "createdAt": new Date("2019-10-30T14:20:04.849Z") },
{ "createdAt": new Date("2019-10-30T16:38:27.271Z") },
{ "createdAt": new Date("2020-01-03T15:49:39.614Z") }
]
},
{
"_id": "ID2",
"email": "EMAIL2", "name": "Bin", "surname": "Baz",
"orders": [
{ "createdAt": new Date("2019-09-09T07:30:25.575Z") },
{ "createdAt": new Date("2020-10-30T14:20:04.849Z") },
{ "createdAt": new Date("2020-10-30T16:38:27.271Z") },
{ "createdAt": new Date("2020-10-30T15:49:39.614Z") }
]
}
]);
This agg:
db.foo.aggregate([
{$unwind: "$orders"}
// First $group is on just the Y-M-D part of the date plus the id.
// This will produce the basic info the OP seeks -- but not in the desired
// data structure:
,{$group: {
_id: {orig_id: "$_id", d: {$dateToString: {date: "$orders.createdAt", format: "%Y-%m-%d"}} },
n:{$sum:1} ,
email: {$first: "$email"},
name: {$first: "$name"},
surname: {$first: "$surname"}
}}
// The group is not guaranteed to preserve the order of the dates. So now that
// the basic agg is done, reorder by DATE. _id.d is a Y-M-D string but fortunately
// that sorts correctly for our purposes:
,{$sort: {"_id.d":1}}
// ...so in the second $group, we pluck just the id from the id+YMD_date key and
// take the YMD_date+n and *push* it onto a new orders array to arrive at the
// desired data structure. We are not guaranteed the order of orig_id (e.g.
// ID or ID2) but for each id, the push *will* happen in the order of arrival -- which was
// sorted correctly in the prior stage! As an experiment, try changing the
// sort to -1 (reverse) and see what happens.
,{$group: {_id: "$_id.orig_id",
email: {$first: "$email"},
name: {$first: "$name"},
surname: {$first: "$surname"},
orders: {$push: {date: "$_id.d", total: "$n"}} }}
]);
yields this output:
{
"_id" : "ID",
"email" : "EMAIL",
"name" : "Foo",
"surname" : "Bar",
"orders" : [
{
"date" : "2019-09-09",
"total" : 1
},
{
"date" : "2019-10-30",
"total" : 2
},
{
"date" : "2020-01-03",
"total" : 1
}
]
}
{
"_id" : "ID2",
"email" : "EMAIL2",
"name" : "Bin",
"surname" : "Baz",
"orders" : [
{
"date" : "2019-09-09",
"total" : 1
},
{
"date" : "2020-10-30",
"total" : 3
}
]
}
If you are willing to have a slightly more complex return structure and some dupe data in return for greater dynamic behavior by not having to enumerate each field (e.g. field: {$first: "$field"} then you can do this:
db.foo.aggregate([
{$unwind: "$orders"}
,{$group: {
_id: {orig_id: "$_id", d: {$dateToString: {date: "$orders.createdAt", format: "%Y-%m-%d"}} },
n:{$sum:1} ,
ALL: {$first: "$$CURRENT"}
}}
,{$group: {_id: "$_id.orig_id",
ALL: {$first: "$ALL"},
orders: {$push: {date: "$_id.d", total: "$n"}} }}
]);
to yield this:
{
"_id" : "ID2",
"ALL" : {
"_id" : "ID2",
"email" : "EMAIL2",
"name" : "Bin",
"surname" : "Baz",
"orders" : {
"createdAt" : ISODate("2019-09-09T07:30:25.575Z")
}
},
"orders" : [
{
"date" : "2019-09-09",
"total" : 1
},
{
"date" : "2020-10-30",
"total" : 3
}
]
}
{
"_id" : "ID",
"ALL" : {
"_id" : "ID",
"email" : "EMAIL",
"name" : "Foo",
"surname" : "Bar",
"orders" : {
"createdAt" : ISODate("2019-10-30T14:20:04.849Z")
}
},
"orders" : [
{
"date" : "2019-10-30",
"total" : 2
},
{
"date" : "2020-01-03",
"total" : 1
},
{
"date" : "2019-09-09",
"total" : 1
}
]
}

Related

how to get data in single array in mongodb?

I want to achieve my documents in a single array
My data is this:
"summary_data" : [
{
"_id" : ObjectId("60418b730ba57044ec0afddd"),
"category" : "Entertainment",
"name" : "Alia bhatt",
"profession" : "acting"
},
{
"_id" : ObjectId("60418bc838497d42a80879e3"),
"category" : "Entertainment",
"name" : "priyanka chopra",
"profession" : "acting"
},
{
"_id" : ObjectId("60418bef38497d42a80879eb"),
"category" : "Entertainment",
"name" : "radhika apte",
"profession" : "acting"
},
]
}
And i want data like this having min 10(limit) category data of each category.How can achieve this:
"summary_data" : [
{
"_id": ObjectId("60418b730ba57044ec0afddd"),
"data": [{
"Entertainment": [{
"_id": ObjectId("60418ce138497d42a8087a03"),
"category": "Entertainment",
"name": "Alia bhatt",
"profession": "acting"
}],
}]
i have tried many queries some of them are:
db.getCollection('experts').aggregate([
{
$match: {
$or: [
{ category: "Sports" },
{ category: "other" },
{ category: "Entertainment" }
]
}
},
{ $limit: 10 },
{
$group:
{
_id: null,
summary_data: {
$push: {
_id: "$_id",
category: "$category",
name: "$name",
profession: "$profession",
}
},
}
},
])
Please help me to build this query. Thanks in advance.

Join two collections by matching value present inside array of object using MongoDB

I want to join two collection using MongoDB but the local filed matching key is present inside array. I am explaining my two document below.
users:
{ "_id" : ObjectId("5ee8c77330e6a86c5e5ce69b"),
"IsDeleted" : false,
"Name" : "savitha",
"Number" : "9848868000",
"Email" : "savitha.k#edqart.com",
"Password" : "savitha1",
"RoleId" : ObjectId("5ee1d885de9b6a5ae2bae165"),
"RoleName" : "KIOSK",
"UserType" : "POS",
"IsActive" : true,
"SalesAgentName" : "",
"salesAgentEmail" : "",
"SalesAgentMobile" : "",
"SalesAgentAlternateMobile" : "",
"SalesAgentRole" : "",
"MerchantID" : "",
"MachineName" : "",
"MachineBank" : "",
"StoreDetails" : [
{
"StoreCode" : "DKAA",
"Counter" : 3,
"TerminalID" : "12345",
"CounterName" : "Counter3"
}
]
}
This is my primary collection and i want to join with below collection.
storeinfo:
{
"_id" : ObjectId("5e447571f034c748ab11bd15"),
"IsActive" : true,
"IsDeleted" : false,
"StoreCode" : "DKAA",
"StoreName" : "Deeksha Group",
"StoreDescription" : "Deeksha Store is a place where Parents can purchase all the school merchandise in one place at reasonable prices.",
"StoreBranch" : "Bengaluru",
}
Here I need to join both collection as per StoreDetails.StoreCode(users) = StoreCode(storeinfo) and then I want to add only StoreName(from storeinfo) with respective record in StoreDetails. I am explaining my query below.
db.getCollection('users').aggregate([
{
$match: {"_id" : ObjectId("5ee8c77330e6a86c5e5ce69b")}
},
{
$addFields: {
"StoreDetails": {
$ifNull : [ "$StoreDetails", [ ] ]
}
}
},
{
$lookup: {
"from": "storeinfo",
"localField": "StoreDetails.StoreCode",
"foreignField": "StoreCode",
"as": "StoreDetails.StoreCode"
}
}
])
But as per this query I am not getting the expected output. My expected output should like below.
{ "_id" : ObjectId("5ee8c77330e6a86c5e5ce69b"),
"IsDeleted" : false,
"Name" : "savitha",
"Number" : "9848868000",
"Email" : "savitha.k#edqart.com",
"Password" : "savitha1",
"RoleId" : ObjectId("5ee1d885de9b6a5ae2bae165"),
"RoleName" : "KIOSK",
"UserType" : "POS",
"IsActive" : true,
"SalesAgentName" : "",
"salesAgentEmail" : "",
"SalesAgentMobile" : "",
"SalesAgentAlternateMobile" : "",
"SalesAgentRole" : "",
"MerchantID" : "",
"MachineName" : "",
"MachineBank" : "",
"StoreDetails" : [
{
"StoreCode" : "DKAA",
"StoreName" : "Deeksha Group"
"Counter" : 3,
"TerminalID" : "12345",
"CounterName" : "Counter3"
}
]
}
Here I need only store Name will add into the respective object as per storecode. But as per my query I am getting all fields value from storeinfo. Can any body help me to resolve this issue.
Here goes the full query
db.users.aggregate([
{
$match: {
"_id": ObjectId("5ee8c77330e6a86c5e5ce69b")
}
},
{
$addFields: {
"StoreDetails": {
$ifNull: [
"$StoreDetails",
[]
]
}
}
},
{
$unwind: "$StoreDetails"
},
{
$lookup: {
"from": "storeinfo",
"localField": "StoreDetails.StoreCode",
"foreignField": "StoreCode",
"as": "StoreDetails.StoreCode"
}
},
{
$unwind: "$StoreDetails.StoreCode"
},
{
$project: {
"Email": 1,
"IsActive": 1,
"IsDeleted": 1,
"MachineBank": 1,
"MachineName": 1,
"MerchantID": 1,
"Name": 1,
"Number": 1,
"Password": 1,
"RoleId": 1,
"RoleName": 1,
"SalesAgentAlternateMobile": 1,
"SalesAgentMobile": 1,
"SalesAgentName": 1,
"SalesAgentRole": 1,
"UserType": 1,
"_id": 1,
"salesAgentEmail": 1,
"StoreDetails": {
"StoreCode": "$StoreDetails.StoreCode.StoreCode",
"Counter": 1,
"CounterName": 1,
"StoreName": "$StoreDetails.StoreCode.StoreName",
"TerminalID": 1
}
}
},
{
$group: {
_id: "$_id",
"Email": {
$first: "$Email"
},
"IsActive": {
$first: "$IsActive"
},
"IsDeleted": {
$first: "$IsDeleted"
},
"MachineBank": {
$first: "$MachineBank"
},
"MachineName": {
$first: "$MachineName"
},
"MerchantID": {
$first: "$MerchantID"
},
"Name": {
$first: "$Name"
},
"Number": {
$first: "$Number"
},
"Password": {
$first: "$Password"
},
"RoleId": {
$first: "$RoleId"
},
"RoleName": {
$first: "$RoleName"
},
"SalesAgentAlternateMobile": {
$first: "$SalesAgentAlternateMobile"
},
"SalesAgentMobile": {
$first: "$SalesAgentMobile"
},
"SalesAgentName": {
$first: "$SalesAgentName"
},
"SalesAgentRole": {
$first: "$SalesAgentRole"
},
"UserType": {
$first: "$UserType"
},
"salesAgentEmail": {
$first: "$salesAgentEmail"
},
"StoreDetails": {
$push: {
StoreCode: "$StoreDetails.StoreCode",
Counter: "$StoreDetails.Counter",
CounterName: "$StoreDetails.CounterName",
StoreName: "$StoreDetails.StoreName",
TerminalID: "$StoreDetails.TerminalID"
}
}
}
}
])
You can see the full working example here:
https://mongoplayground.net/p/6KqECy-o1U8
Thanks

Multilevel $group using mongodb

I am trying to get the count of all the different value of a key in my MongoDB. I am getting the count as well but i am getting it with 2 different objects.
{ "_id" : ObjectId("596f6e95b6a1aa8d363befeb"), produce:"potato","variety" : "abc", "state" : 'PA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befec"), produce:"potato", "variety" : "abc", "state" : 'PA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befed"), produce:"potato", "variety" : "def", "state" : 'IA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befee"), produce:"potato", "variety" : "def", "state" : 'IA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befef"), produce:"potato", "variety" : "abc", "state" : 'DA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befeg"), produce:"potato", "variety" : "abc", "state" : 'DA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befeh"), produce:"potato", "variety" : "def", "state" : 'DA' }
{ "_id" : ObjectId("596f6e95b6a1aa8d363befei"), produce:"potato", "variety" : "abc", "state" : 'IA' }
db.aggregate([
{
$match:{produce: "potato"}
},
{
"$group":{
"_id":{"variety":"$variety","state":"$state"},
"count":{"$sum":1}
}
},
{
"$group":{
"_id":null,
"counts":{
"$push": {"filterkey":"$_id.variety","state":"$_id.state","count":"$count"}
}
}
},
])
Actual Result : -
counts
[
{ filterkey: 'abc', state: 'PA', count: 2},
{ filterkey: 'abc', state: 'IA', count: 1},
{ filterkey: 'abc', state: 'DA', count: 2},
{ filterkey: 'def', state: 'IA', count: 2},
{ filterkey: 'def', state: 'DA', count: 1}
]
Expected Result : -
counts
[
{ filterkey: 'abc', states:{'PA':2,'IA':1,'DA':2},
{ filterkey: 'def', states:{'IA':2,'DA':1}
]
Is there is some way to get the data like this?
You need to use multilevel $group ing here. First you need to use $group with the variety and state fields and need to $sum to get total number of unique document per variety and state.
Then second you need to use $group with the variety to get the number of unique documents per variety.
And Finally $arrayToObject to flatten the states array.
db.collection.aggregate([
{ "$match": { "produce": "potato" }},
{ "$group": {
"_id": { "variety": "$variety", "state": "$state" },
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.variety",
"states": {
"$push": {
"k": "$_id.state",
"v": "$count"
}
}
}},
{ "$addFields": {
"states": {
"$arrayToObject": "$states"
}
}}
])
You can remove stages one by one here and can see what actually happens.
Output
[
{
"_id": "def",
"states": {
"DA": 1,
"IA": 2
}
},
{
"_id": "abc",
"states": {
"DA": 2,
"IA": 1,
"PA": 2
}
}
]

MongoDB calculate sum of $unwind document

Lets say I have 2 reports documents with an embeded line_items document:
Reports with embeded line_items
{
_id: "1",
week_number: "1",
line_items: [
{
cash: "5",
miscellaneous: "10"
},
{
cash: "20",
miscellaneous: "0"
}
]
},
{
_id: "2",
week_number: "2",
line_items: [
{
cash: "100",
miscellaneous: "0"
},
{
cash: "10",
miscellaneous: "0"
}
]
}
What I need to do is perform a set of additions on each line_item (in this case cash + miscellaneous) and have the grand total set on the reports query as a 'gross' field. I would like to end up with the following result:
Desired result
{ _id: "1", week_number: "1", gross: "35" },{ _id: "2", week_number: "2", gross: "110" }
I have tried the following query to no avail:
db.reports.aggregate([{$unwind: "$line_items"},{$group: {_id : "$_id", gross: {$sum : {$add: ["$cash", "$miscellaneous"]}}}}]);
You can't sum strings, so you'll first need to change the data type of the cash and miscellaneous fields in your docs to a numeric type.
But once you do that, you can sum them by including the line_items. prefix on those fields in your aggregate command:
db.reports.aggregate([
{$unwind: "$line_items"},
{$group: {
_id : "$_id",
gross: {$sum : {$add: ["$line_items.cash", "$line_items.miscellaneous"]}}
}}
]);
Output:
{
"result" : [
{
"_id" : "2",
"gross" : 110
},
{
"_id" : "1",
"gross" : 35
}
],
"ok" : 1
}

Mongo aggregate nested array

I have a mongo collection with following structure
{
"userId" : ObjectId("XXX"),
"itemId" : ObjectId("YYY"),
"resourceId" : 1,
"_id" : ObjectId("528455229486ca3606004ec9"),
"parameter" : [
{
"name" : "name1",
"value" : 150,
"_id" : ObjectId("528455359486ca3606004eed")
},
{
"name" : "name2",
"value" : 0,
"_id" : ObjectId("528455359486ca3606004eec")
},
{
"name" : "name3",
"value" : 2,
"_id" : ObjectId("528455359486ca3606004eeb")
}
]
}
There can be multiple documents with the same 'useId' with different 'itemId' but the parameter will have same key/value pairs in all of them.
What I am trying to accomplish is return aggregated parameters "name1", "name2" and "name3" for each unique "userId" disregard the 'itemId'. so final results would look like for each user :
{
"userId" : ObjectId("use1ID"),
"name1" : (aggregatedValue),
"name2" : (aggregatedValue),
"name3" : (aggregatedVAlue)
},
{
"userId" : ObjectId("use2ID"),
"name1" : (aggregatedValue),
"name2" : (aggregatedValue),
"name3" : (aggregatedVAlue)
}
Is it possible to accomplish this using the aggregated methods of mongoDB ? Could you please help me to build the proper query to accomplish that ?
The simplest form of this is to keep things keyed by the "parameter" "name":
db.collection.aggregate(
// Unwind the array
{ "$unwind": "$parameter"},
// Group on the "_id" and "name" and $sum "value"
{ "$group": {
"_id": {
"userId": "$userId",
"name": "$parameter.name"
},
"value": { "$sum": "$parameter.value" }
}},
// Put things into an array for "nice" processing
{ "$group": {
"_id": "$_id.userId",
"values": { "$push": {
"name": "$_id.name",
"value": "$value"
}}
}}
)
If you really need to have the "values" of names as the field values, you can do the the following. But since you are "projecting" the fields/properties then you must specify them all in your code. You cannot be "dynamic" anymore and you are coding/generating each one:
db.collection.aggregate([
// Unwind the array
{ "$unwind": "$parameter"},
// Group on the "_id" and "name" and $sum "value"
{ "$group": {
"_id": {
"userId": "$userId",
"name": "$parameter.name"
},
"value": { "$sum": "$parameter.value"}
}},
// Project out discrete "field" names with $cond
{ "$project": {
"name1": { "$cond": [
{ "$eq": [ "$_id.name", "name1" ] },
"$value",
0
]},
"name2": { "$cond": [
{ "$eq": [ "$_id.name", "name2" ] },
"$value",
0
]},
"name3": { "$cond": [
{ "$eq": [ "$_id.name", "name3" ] },
"$value",
0
]},
}},
// The $cond put "0" values in there. So clean up with $group and $sum
{ "$group": {
_id: "$_id.userId",
"name1": { "$sum": "$name1" },
"name2": { "$sum": "$name2" },
"name3": { "$sum": "$name3" }
}}
])
So while the extra steps give you the result that you want ( well with a final project to change the _id to userId ), for my mind the short version is workable enough, unless you really do need it. Consider the output from there as well:
{
"_id" : ObjectId("53245016ea402b31d77b0372"),
"values" : [
{
"name" : "name3",
"value" : 2
},
{
"name" : "name2",
"value" : 0
},
{
"name" : "name1",
"value" : 150
}
]
}
So that would be what I would use, personally. But your choice.
Not sure if I got your question but if the name field can contain only "name1", "name2", "name3" or at least you are only interested in this values, one of the possible queries could be this one:
db.aggTest.aggregate(
{$unwind:"$parameter"},
{$project: {"userId":1, "parameter.name":1,
"name1" : {"$cond": [{$eq : ["$parameter.name", "name1"]}, "$parameter.value", 0]},
"name2" : {"$cond": [{$eq : ["$parameter.name", "name2"]}, "$parameter.value", 0]},
"name3" : {"$cond": [{$eq : ["$parameter.name", "name3"]}, "$parameter.value", 0]}}},
{$group : {_id : {userId:"$userId"},
name1 : {$sum:"$name1"},
name2 : {$sum:"$name2"},
name3 : {$sum:"$name3"}}})
It firsts unwinds the parameter array, then separates name1, name2 and name3 values into different columns. There's a simple conditional statement for that. After that we can easily aggreagate by the new columns.
Hope it helps!