I have documents in mongo db, like
doc = {
name = MyName,
tags = tag1,tag2,tag3,
...
}
When I search documents by name, I also want to get analytics of tags, for docs with that name, like
{
tag1: 7,
tag2: 5,
...
tagn: 14
}
How can I aggregate it?
The data model complicates the query somewhat and the required output format complicates it even more ... but here's one way to do it.
db.collection.aggregate([
{
"$set": {
"tags": {
"$split": ["$tags", ","]
}
}
},
{"$unwind": "$tags"},
{
"$set": {
"tags": {
"$trim": {"input": "$tags"}
}
}
},
{
"$group": {
"_id": "$tags",
"count": {"$count": {}}
}
},
{
"$sort": {"_id": 1}
},
{
"$group": {
"_id": null,
"newRoot": {
"$mergeObjects": {
"$arrayToObject": [
[
{
"$reduce": {
"input": {"$objectToArray": "$$ROOT"},
"initialValue": {},
"in": {
"$mergeObjects": [
"$$value",
{
"$switch": {
"branches": [
{
"case": {"$eq": ["$$this.k", "_id"]},
"then": {"k": "$$this.v"}
},
{
"case": {"$eq": ["$$this.k", "count"]},
"then": {"v": "$$this.v"}
}
],
"default": "$$value"
}
}
]
}
}
}
]
]
}
}
}
},
{"$replaceWith": "$newRoot"}
])
Example output:
[
{
"tag1": 2,
"tag2": 2,
"tag3": 3,
"tag5": 1,
"tag7": 1
}
]
Try it on mongoplayground.net.
Related
Consider I have a timesheets collection like this:
[
{
_id: 1,
createdBy: "John",
duration: "00:30"
},
{
_id: 2,
createdBy: "John",
duration: "01:30"
},
{
_id: 3,
createdBy: "Mark",
duration: "00:30"
},
]
My input is an array of usernames:
["John", "Mark", "Rikio"]
I want to use mongodb aggregate to calculate the total duration of timesheets for each user in the usernames array and If there are no timesheets found, it should return duration: "00:00". For example, it should return:
[
{createdBy: "John", totalDuration: "02:00"},
{createdBy: "Mark", totalDuration: "00:30"},
{createdBy: "Rikio", totalDuration: "00:00"}
]
However, when I use $match query, if there are no timesheets it will not return anything so I don't know which user does not have the timesheets and return "00:00" for them.
I totally agree with #turivishal , but still can make it through mongo query with an ugly one.
db.collection.aggregate([
{
$match: {}
},
{
$set: {
minutes: {
$let: {
vars: {
time: {
$split: [
"$duration",
":"
]
}
},
in: {
"$add": [
{
"$multiply": [
{
$toInt: {
$first: "$$time"
}
},
60
]
},
{
$toInt: {
$last: "$$time"
}
}
]
}
}
}
}
},
{
$group: {
"_id": "$createdBy",
"totalMinutes": {
"$sum": "$minutes"
}
}
},
{
$group: {
"_id": null,
"docs": {
"$push": "$$ROOT"
}
}
},
{
$set: {
"docs": {
$map: {
"input": [
"John",
"Mark",
"Rikio"
],
"as": "name",
"in": {
$let: {
vars: {
findName: {
"$first": {
"$filter": {
"input": "$docs",
"as": "d",
"cond": {
"$eq": [
"$$d._id",
"$$name"
]
}
}
}
}
},
in: {
"$cond": {
"if": "$$findName",
"then": "$$findName",
"else": {
_id: "$$name",
totalMinutes: 0
}
}
}
}
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceRoot: {
"newRoot": "$docs"
}
},
{
$set: {
"hours": {
$floor: {
"$divide": [
"$totalMinutes",
60
]
}
},
"minutes": {
"$mod": [
"$totalMinutes",
60
]
}
}
},
{
$set: {
"hours": {
"$cond": {
"if": {
"$lt": [
"$hours",
10
]
},
"then": {
"$concat": [
"0",
{
"$toString": "$hours"
}
]
},
"else": {
"$toString": "$hours"
}
}
},
"minutes": {
"$cond": {
"if": {
"$lt": [
"$minutes",
10
]
},
"then": {
"$concat": [
"0",
{
"$toString": "$minutes"
}
]
},
"else": {
"$toString": "$minutes"
}
}
}
}
},
{
$project: {
duration: {
"$concat": [
"$hours",
":",
"$minutes"
]
}
}
}
])
mongoplayground
I have the following query:
[
{
"$group": {
"_id": "$Region",
"Total Sales": {
"$sum": "$Sales"
},
"Average Sales": {
"$avg": "$Sales"
}
}
}
]
This returns the response in the following format:
[
{
"_id": "Canada",
"Total Sales": 66928.17,
"Average Sales": 174.292109375,
}
.....
]
How do I refactor the query to get a response in the following format:
[{
"_id": "Canada",
"Sales":{"Total":66928.17, "Average":174.292109375},
}
......
]
So far I've tried like this but it doesn't work:
{
"$group": {
"_id": "$Region",
"Sales": {
"Total":{
"$sum": "$Sales"
},
"Average":{
"$avg": "$Sales"
}
}
}
}
Use $project to decorate the output document(s).
db.collection.aggregate([
{
"$group": {
"_id": "$Region",
"total": {
"$sum": "$Sales"
},
"average": {
"$avg": "$Sales"
}
}
},
{
$project: {
"Sales": {
"Total": "$total",
"Average": "$average"
}
}
}
])
Sample Mongo PLayground
My mongo collection name tests and whose having the following documents in it.
[
{
"title": "One",
"uid": "1",
"_metadata": {
"references": [
{
"uid": "2"
},
{
"asssetuid": 10
}
]
}
},
{
"title": "Two",
"uid": "2",
"_metadata": {
"references": [
{
"uid": "3"
},
{
"asssetuid": 11
}
]
}
},
{
"title": "Three",
"uid": "3",
"_metadata": {
"references": []
}
}
]
And I want the result in the following format (for uid:1)
[
{
"title": "One",
"uid": 1,
"_metadata": {
"references": [
{
"asssetuid": 10
},
{
"asssetuid": 11
},
{
"title": "Two",
"uid": "2",
"_metadata": {
"references": [
{
"title": "Three",
"uid": "3"
}
]
}
}
]
}
}
]
for uid:2 I want the following result
[
{
"title": "Two",
"uid": 2,
"_metadata": {
"references": [
{
"asssetuid": 11
},
{
"title": "Three",
"uid": "3"
}
]
}
}
]
Which query I used here to get a respected result. according to its uid. here I want the result in the parent-child relationship. is this possible using MongoDB graph lookup query or any other query that we can use to get the result. Please help me with this.
New Type Output
[{
"title": "One",
"uid": 1,
"_metadata": {
"assets": [{
"asssetuid": 10,
"parent": 1
}, {
"asssetuid": 11,
"parent": 2
}],
"entries": [{
"title": "Two",
"uid": "2",
"parent": 1
}, {
"title": "Three",
"uid": "3",
"parent": 2
}]
}
}]
Mongo supports the automatic reference resolution using $ref but for that, you need to change your schema a little and resolve resolution is only supported by some drivers.
You need to store your data in this format:
[
...
{
"_id": ObjectId("5a934e000102030405000000"),
"_metadata": {
"references": [
{
"$ref": "collection",
"$id": ObjectId("5a934e000102030405000001"),
"$db": "database"
},
{
"asssetuid": 10
}
]
},
"title": "One",
"uid": "1"
},
....
]
For more details on $ref refer to official documentation: label-document-references
OR
you can resolve the reference using the $graphLookup but the only problem with the $graphlookup is that you will lose the assetuid. Here is the query and it will resolve references and give output in flat map
db.collection.aggregate([
{
$match: {
uid: "1"
}
},
{
$graphLookup: {
from: "collection",
startWith: "$_metadata.references.uid",
connectFromField: "_metadata.references.uid",
connectToField: "uid",
depthField: "depth",
as: "resolved"
}
},
{
"$addFields": {
"references": "$resolved",
"metadata": [
{
"_metadata": "$_metadata"
}
]
}
},
{
"$project": {
"references._metadata": 0,
}
},
{
"$project": {
"references": "$references",
"merged": {
"$concatArrays": [
"$metadata",
"$resolved"
]
}
}
},
{
"$project": {
results: [
{
merged: "$merged"
},
{
references: "$references"
}
]
}
},
{
"$unwind": "$results"
},
{
"$facet": {
"assest": [
{
"$match": {
"results.merged": {
"$exists": true
}
}
},
{
"$unwind": "$results.merged"
},
{
"$unwind": "$results.merged._metadata.references"
},
{
"$match": {
"results.merged._metadata.references.asssetuid": {
"$exists": true
}
}
},
{
"$project": {
_id: 0,
"asssetuid": "$results.merged._metadata.references.asssetuid"
}
}
],
"uid": [
{
"$match": {
"results.references": {
"$exists": true
}
}
},
{
"$unwind": "$results.references"
},
{
$replaceRoot: {
newRoot: "$results.references"
}
}
]
}
},
{
"$project": {
"references": {
"$concatArrays": [
"$assest",
"$uid"
]
}
}
}
])
Here is the link to the playground to test it: Mongo Playground
I have a collection similar to this:
[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
I want to aggregate into something like this:
{
"breakfast":[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
}
],
"lunch":[
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
}
I have only managed to group them but I can't change the key meals to either breakfast or lunch depending on the meal.name(group name)
$group: {
_id: { meal: '$meal.name' },
meals: { $push: '$meal' },
}
Using the above code I have managed to produce the output below. My only challenge is changing the key meals to either breakfast or lunch as explained above in the subgroups.
{
"meals":[
{
"_id":1,
"name":"breakfast",
"time":"10.00"
},
{
"_id":2,
"name":"breakfast",
"time":"10.10"
}
],
"meals":[
{
"_id":3,
"name":"lunch",
"time":"12.07"
},
{
"_id":4,
"name":"lunch",
"time":"12.45"
}
]
}
Here you can have your answer .
After "grouping" to add to an array you similarly $push all that content into array by the "name" grouping key and then convert into keys of a document in a $replaceRoot with $arrayToObject:
db.collection.aggregate([
{ "$group": {
"_id": "$name",
"data": { "$push": "$$ROOT" }
}},
{ "$group": {
"_id": null,
"data": {
"$push": {
"k": "$_id",
"v": "$data"
}
}
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$data" }
}}
])
OUTPUT
[
{
"breakfast": [
{
"_id": 1,
"name": "breakfast",
"time": "10.00"
},
{
"_id": 2,
"name": "breakfast",
"time": "10.10"
}
],
"lunch": [
{
"_id": 3,
"name": "lunch",
"time": "12.07"
},
{
"_id": 4,
"name": "lunch",
"time": "12.45"
}
]
}
]
You can check the result of above query in this LINK
I have appointment collection in that i have status codes like upcoming, cancelled, completed. i want to write an api to get count of each status using mongoose or mongodb methods.
output should be like below
[{
group : "grp1",
appointments_completed :4
appointments_upcoming :5
appointments_cancelled : 7
}]
thanks in advance.
I hope it help you
db.getCollection('codelist').aggregate([
{
$group:{
_id:{status:"$status"},
count:{$sum:1}
}
}
])
The result will be
[{
"_id" : {
"status" : "cancelled"
},
"count" : 13.0
},
{
"_id" : {
"status" : "completed"
},
"count" : 20.0
}
]
I think you can process it with nodejs
Using Aggregation Pipeline $group we can get this count
db.collection_name.aggregate([
{ $group: {
_id:null,
appointments_completed: {$sum : "$appointments_completed" },
appointments_upcoming:{$sum :"$appointments_upcoming"},
appointments_cancelled:{$sum: "$appointments_cancelled"}
}
}
]);
With MongoDb 3.6 and newer, you can leverage the use of $arrayToObject operator and a $replaceRoot pipeline to get the desired result. You would need to run the following aggregate pipeline:
db.appointments.aggregate([
{ "$group": {
"_id": {
"group": <group_by_field>,
"status": { "$concat": ["appointments_", { "$toLower": "$status" }] }
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.group",
"counts": {
"$push": {
"k": "$_id.status",
"v": "$count"
}
}
} },
{ "$addFields": {
"counts": {
"$setUnion": [
"$counts", [
{
"k": "group",
"v": "$_id"
}
]
]
}
} },
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$counts" }
} }
])
For older versions, a more generic approach though with a different output format would be to group twice and get the counts as an array of key value objects as in the following:
db.appointments.aggregate([
{ "$group": {
"_id": {
"group": <group_by_field>,
"status": { "$toLower": "$status" }
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.group",
"counts": {
"$push": {
"status": "$_id.status",
"count": "$count"
}
}
} }
])
which spits out:
{
"_id": "grp1"
"counts":[
{ "status": "completed", "count": 4 },
{ "status": "upcoming", "count": 5 }
{ "status": "cancelled", "count": 7 }
]
}
If the status codes are fixed then the $cond operator in the $group pipeline step can be used effectively to evaluate the counts based on the status field value. Your overall aggregation pipeline can be constructed as follows to produce the result in the desired format:
db.appointments.aggregate([
{ "$group": {
"_id": <group_by_field>,
"appointments_completed": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "completed" ] }, 1, 0 ]
}
},
"appointments_upcoming": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "upcoming" ] }, 1, 0 ]
}
},
"appointments_cancelled": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "cancelled" ] }, 1, 0 ]
}
}
} }
])