MongoDb group by two conditions in aggregate - mongodb

I would like to group my data by different conditions but I do not understand how I could do it.
My data:
[
{"Id": "1", "Info": "X" "Date": 10/1},
{"Id": "2", "Info": "X" "Date": 13/2},
{"Id": "3", "Info": "Y" "Date": 13/2},
{"Id": "4", "Info": "X" "Date": 10/1},
{"Id": "5", "Info": "X" "Date": 10/1},
{"Id": "6", "Info": "X" "Date": 13/2},
]
And I would like to group them by Info and by Date, a result similar to this one:
[
{"Id": ["1","4","5"], "Info": "X" "Date": 10/1},
]
[
{"Id": ["2", "6"], "Info": "X" "Date": 13/2},
]
[
{"Id": ["3"], "Info": "Y" "Date": 13/2},
]
I am using aggregate and I just know how to use aggregate to group them just by one condition, I donĀ“t know how to continue and how to use date in $group, this is what I have and how I group it by info:
.aggregate([
{ "$match" : { "$or": [{"status": "downloading"}, {"status": "calculating"}]}},
{ "$project": {"Id": 1, "Date": 1, "info": 1}},
{ "$group" : { "_id" : "$Info", "Id" : { "$addToSet" : "$Id"}}},
{ "$project": {"_id": 0, "Info": "$_id", "Id": 1 }}

You can give the _id in the $group stage multiple fields, like so:
db.collection.aggregate([
{
"$project": {
"Id": 1,
"Date": 1,
"info": 1
}
},
{
"$group": {
"_id": {
date: "$Date",
info: "$info"
},
"Id": {
"$addToSet": "$Id"
}
}
},
{
"$project": {
"_id": 0,
"Info": "$_id.info",
"Date": "$_id.date",
"Id": 1
}
}
])
Mongo Playground

Related

Strange MongoDB $setIntersection behaviour

I want to query a match between records in my db based on certain tags. The match would be calculated based on a formula and the intersection of the tags. Now, even querying the intersection doesn't work...always. Sometimes it does, sometimes it doesn't. In my example, if I change the displayName attribute to something else (add or remove one character, the query works. In its current state (for demo purposes) it doesn't as it does not deliver the one intersection match for the last doc with id 3.
https://mongoplayground.net/p/KAYPoV29RFO
That's my query:
db.collection.aggregate([
{
$match: {
"_id": "1"
}
},
{
"$lookup": {
from: "collection",
let: {
"criteria": "$tags"
},
pipeline: [
{
$project: {
"match": {
$setIntersection: [
"$tags",
"$$criteria"
]
},
}
}
],
as: "result"
}
},
{
$project: {
"tags": 0
}
},
])
Here is the example data (simplified):
[
{ "_id": "1", "tags": [{ "_id": "a", "displayName": "a", "level": 1}, {"_id": "b", "displayName": "b", "level": 2}, {"_id": "c", "displayName": "c", "level": 3}]},
{"_id": "2", "tags": [{"_id": "a", "displayName": "a", "level": 1}, {"_id": "b", "displayName": "b", "level": 2}]},
{"_id": "3", "tags": [{"_id": "a", "displayName": "a", "level": 1}, {"_id": "d", "displayName": "d", "level": 4}]}
]
and the result as it is: (expected is three matches for id 1, 2 matches for id 2 and one for the last id. However, the last result has 0 elements in the intersection result. Again, when i change "displayName" to "displayNam" or "displayNames" (obviously in all docs), it give the correct result...
[{
"_id": "1", "result": [
{"_id": "1", "match": [{"_id": "a", "displayName": "a", "level": 1}, {"_id": "b", "displayName": "b", "level": 2},{"_id": "c","displayName": "c","level": 3}]},
{"_id": "2", "match": [{"_id": "a", "displayName": "a", "level": 1}, {"_id": "b", "displayName": "b", "level": 2}]},
{"_id": "3","match": [*here should be the match to _id: "a", but it's not (always) there*]}
]
}]
Does anyone have an idea what I am missing here?

MongoDB query: aggregate with "findOne"

I'm trying to make a query on MongoDB 3.4 where I add a field for one specific element of an array. Example of the object:
{
"_id": 1,
"people": [
{
"name": "Jhon Smith",
"age": "30",
"state": "NY"
},{
"name": "Clint Mercer",
"age": "50",
"state": "NY"
},{
"name": "Walter Smith",
"age": "40",
"state": "WI"
}
]
}
And I want to make a query where I'll add to this document an attribute with the first person with "Smith" in it's name. Example:
{
"_id": 1,
"people": [
{
"name": "Jhon Smith",
"age": "30",
"state": "NY"
},{
"name": "Clint Mercer",
"age": "50",
"state": "NY"
},{
"name": "Walter Smith",
"age": "40",
"state": "WI"
}
],
"firstSmith": {
"name": "Jhon Smith",
"age": "30",
"state": "NY"
}
}
I already have the _id of the document I want, but I can't understand how to make a query like this. I'm trying using aggregate with "$match" for the id and "$addFields" after, but I can't make a query that works for this field to find exactly what I want. I think it would be similar to the "findOne" query, but I can't find anything that works on "$addFields".
Obs: I DON'T want the "firstSmith" to be an array with just one "people" inside, I want it as is in the example.
I'd appreciate some help with this one.
$match - to filter the relevant document
$filter with $regexMatch - to filter people array by the name property
arrayElemAt - to get only the first element of above array
$addFields - to add new field with value from above result
db.collection.aggregate([
{
"$match": {
"_id": 1
}
},
{
"$addFields": {
"firstSmith": {
"$arrayElemAt": [
{
"$filter": {
"input": "$people",
"cond": {
"$regexMatch": {
"input": "$$this.name",
"regex": "Smith"
}
}
}
},
0
]
}
}
}
])
Working example

Group by an optional field in mongodb

I would like to independently group the results of an or clause, including overlap. The data set is rather large so running 2 queries sequentially will result in an undesirable wait time. I am hoping I can somehow project which clause returned the corresponding data. Given this data set:
[
{
"_id": 1,
"item": "abc",
"name": "Michael",
"price": NumberDecimal("10"),
"quantity": NumberInt("2"),
"date": ISODate("2014-03-01T08:00:00Z")
},
{
"_id": 2,
"item": "jkl",
"name": "Toby",
"price": NumberDecimal("20"),
"quantity": NumberInt("1"),
"date": ISODate("2014-03-01T09:00:00Z")
},
{
"_id": 3,
"item": "xyz",
"name": "Keith",
"price": NumberDecimal("5"),
"quantity": NumberInt("10"),
"date": ISODate("2014-03-15T09:00:00Z")
},
{
"_id": 4,
"item": "abc",
"name": "Dwight",
"price": NumberDecimal("5"),
"quantity": NumberInt("20"),
"date": ISODate("2014-04-04T11:21:39.736Z")
},
{
"_id": 5,
"item": "abc",
"name": "Ryan",
"price": NumberDecimal("10"),
"quantity": NumberInt("10"),
"date": ISODate("2014-04-04T21:23:13.331Z")
},
{
"_id": 6,
"item": "def",
"name": "Jim",
"price": NumberDecimal("7.5"),
"quantity": NumberInt("5"),
"date": ISODate("2015-06-04T05:08:13Z")
},
{
"_id": 7,
"item": "abc",
"name": "Keith",
"price": NumberDecimal("7.5"),
"quantity": NumberInt("10"),
"date": ISODate("2015-09-10T08:43:00Z")
},
{
"_id": 8,
"item": "abc",
"name": "Michael",
"price": NumberDecimal("10"),
"quantity": NumberInt("5"),
"date": ISODate("2016-02-06T20:20:13Z")
},
]
I would like to receive this result:
[{
"_id": {
"name": "Keith"
},
"count": 2
},
{
"_id": {
"item": "abc",
},
"count": 5
}]
Here is what I have tried so far:
db.collection.aggregate([
{
$match: {
$or: [
{
item: "abc"
},
{
name: "Keith"
}
]
}
},
{
$group: {
_id: {
item: "$item",
name: "$name"
},
count: {
$sum: 1
}
}
}
])
You can use $facet to get multiple aggregation pipelines into the same stage in this way:
Using $facet there are two "outputs" one group by name and other by item.
In each one there are multiple stages:
First $match to process only documents you want.
Then $group with _id name or item, and $count to get the total.
db.collection.aggregate([
{
"$facet": {
"groupByName": [
{
"$match": {"name": "Keith"}
},
{
"$group": {"_id": "$name","count": {"$sum": 1}}
}
],
"groupByItem": [
{
"$match": {"item": "abc"}
},
{
"$group": {"_id": "$item","count": {"$sum": 1}}
}
]
}
}
])
Example here
The output is:
{
"groupByItem": [
{
"_id": "abc",
"count": 5
}
],
"groupByName": [
{
"_id": "Keith",
"count": 2
}
]
}
Here it is:
mongos> db.n.aggregate([ { $facet:{ names:[ {$match:{name:"Keith"}} , {$group:{_id:{name:"$name"}, count:{$sum:1}}} ] , items:[ {$match:{item:"abc"}},{ $group:{_id:{item:"$item"}, count:{$sum:1}} } ] } } , {$project:{ "namesANDitems":{$concatArrays:[ "$names","$items" ]} }} ,{$unwind:"$namesANDitems"} ,{$replaceRoot:{newRoot:"$namesANDitems"} } ]).pretty()
{ "_id" : { "name" : "Keith" }, "count" : 2 }
{ "_id" : { "item" : "abc" }, "count" : 5 }
mongos>
explained:
You create two pipes via $facet
Match in every facet pipe what you need to group pipe1=names , pipe2=items
Join the arrays from the two pipes in single array named "namesANDitems"
Convert the array to object with $unwind
Remove the temporary object name namesANDitems so you have only the two objects as requested

How to do aggregation with sum in a Mongo query?

I'm new to MongoDB and need to write this query. My JSON Request is like this. I need to build the query in mongodb with aggregation
JSON
[
{
"_id": {
"user_id": "1",
"name": "Madhu",
"LeadName": "Test",
"status": "Lost",
"lead_value": 78000
}
},
{
"_id": {
"user_id": "1",
"name": "Madhu",
"LeadName": "Trail",
"status": "Trial",
"lead_value": 75200
}
},
{
"_id": {
"user_id": "1",
"name": "Madhu",
"LeadName": "Teja",
"status": "Customer",
"lead_value": 45000
}
},
{
"_id": {
"user_id": "1",
"name": "Madhu",
"LeadName": "Kushbu",
"status": "Trial",
"lead_value": 20000
}
},
{
"_id": {
"user_id": "1",
"name": "Madhu",
"LeadName": "Krishna",
"status": "Trial",
"lead_value": 18000
}
},
{
"_id": {
"user_id": "1",
"name": "Madhu",
"LeadName": "Test1",
"status": "Trial",
"lead_value": 12000
}
},
{
"_id": {
"user_id": "89",
"name": "Prashanth Reddy",
"LeadName": "Ganesh",
"status": "Trial",
"lead_value": 12000
}
},
{
"_id": {
"user_id": "89",
"name": "Prashanth Reddy",
"LeadName": "Hima Sree",
"status": "Customer",
"lead_value": 1750
}
}
]
I want the response like sum of the all the "Lead_values" except "Customer" and count the Status also
[
{
"_id": {
"user_id": "1",
"name": "Madhu",
"lead_value": 120354,
"count" : 5
}
},
{
"_id": {
"user_id": "89",
"name": "Prashanth Reddy",
"lead_value": 12000,
"count" : 1
}
}
]
How to write in MongoDB Aggregation?
You can use this query, to get your perfect result.
Here is it.
db.getCollection('YOUR_COLLECTION').aggregate([
{
$match: {
"_id.status": {$ne: "Customer"}
}
},
{
$group: {
_id: "$_id.user_id",
lead_values: {$sum: "$_id.lead_value"},
name: {$first: "$_id.name"},
count: {$sum: 1}
}
}
])
Here you can see, how we can use $sum with $group
You just need a simple $group stage:
db.collection.aggregate([
{
$match: {
"_id.status": {$ne: "Customer"}
}
},
{
$group: {
_id: "$_id.user_id",
lead_values: {$sum: "$_id.lead_value"},
name: {$first: "$_id.name"},
count: {$sum: 1}
}
}
])
You can now reformat the data as you wish.

select documents grouped by field

I have this documents of movie showing time and date:
`{
"_id": ObjectId("5628668c3e82c49245b7acdc"),
"ticketID": ObjectId("5606d36b5fbd7d76028b4b08"),
"uid": "50000",
"day": "Friday",
"date": "2015-10-23 21:05:00",
"adult": NumberLong(550),
"student": NumberLong(550),
"children": NumberLong(250),
"limit": NumberLong(20),
"sold": NumberLong(0)
},{
"_id": ObjectId("562866013e82c49045b7acdc"),
"ticketID": ObjectId("5606d36b5fbd7d76028b4b08"),
"uid": "50000",
"day": "Friday",
"date": "2015-10-23 19:30:00",
"adult": NumberLong(1050),
"student": NumberLong(800),
"children": NumberLong(550),
"limit": NumberLong(20),
"sold": NumberLong(0)
},{
"_id": ObjectId("562865013e82c49845b7acda"),
"ticketID": ObjectId("5606d36b5fbd7d76028b4b08"),
"uid": "50000",
"day": "Friday",
"date": "2015-10-23 18:45:00",
"adult": NumberLong(1500),
"student": NumberLong(750),
"children": NumberLong(750),
"limit": NumberLong(20),
"sold": NumberLong(0)
}
`
I want to group the final result by "day" where "ticketID" matches, "date" as an object and adding "adult","student","children","limit" to an array
-- UPDATE --
I would like the returned structure to follow:
{
"_id": "ticketID",
"day": "Friday",
"items": [
{
"date": date,
"time": time"adult": price,
"children": price,
"student": price,
"limit": value
},
{
"date": date,
"time": time"adult": price,
"children": price,
"student": price,
"limit": value
},
{
"date": date,
"time": time"adult": price,
"children": price,
"student": price,
"limit": value
}
]
}
Run the following aggregation pipeline which uses the $group operator to group your documents by the specified fields, add the items array by using the accumulator operator $push that returns an array of expression values for each group. The $project pipeline operator then reshapes the documents by amending the fields to get the final desired structure:
var pipeline = [
{
"$group": {
"_id": {
"ticketID": "$ticketID",
"day": "$day"
},
"items": {
"$push": {
"date": "$date",
"time": "$time",
"adult": "$adult",
"children": "$children",
"student": "$student",
"limit": "$limit"
}
}
}
},
{
"$project": {
"_id": "$_id.ticketID",
"day": "$_id.day",
"items": 1
}
}
];
db.collection.aggregate(pipeline);