Including additional fields in a Mongodb aggregate query - mongodb

I have a data structure like this. Each student will have multiple entries based on when they enter the classrooms. The query needs to get the latest record of each student based on a list of student ids and department name. It also should show the teacher id and last timestmap
[
{
"studentid": "stu-1234",
"dept": "geog",
"teacher_id": 1,
"LastSwipeTimestamp": "2021-11-25T10:50:00.5230694Z"
},
{
"studentid": "stu-1234",
"dept": "geog",
"teacher_id": 2,
"LastSwipeTimestamp": "2021-11-25T11:50:00.5230694Z"
},
{
"studentid": "stu-abc",
"dept": "geog",
"teacher_id": 11,
"LastSwipeTimestamp": "2021-11-25T09:15:00.5230694Z"
},
{
"studentid": "stu-abc",
"dept": "geog",
"teacher_id": 21,
"LastSwipeTimestamp": "2021-11-25T11:30:00.5230694Z"
}
]
Here is what I have, but it doesn't show teacher id or the last swipe timestamp. What do I need to change or add?

Maybe you need something like this
db.collection.aggregate([
{
$match: {
"studentid": {
"$in": [
"stu-abc",
"stu-1234"
]
},
"dept": "geog"
}
},
{
$sort: {
"LastSwipeTimestamp": -1
}
},
{
$group: {
"_id": {
"studentid": "$studentid",
"dept": "$dept"
},
"teacher_id": {
$first: "$teacher_id"
},
"LastSwipeTimestamp": {
$first: "$LastSwipeTimestamp"
}
}
},
{
$project: {
_id: 0,
"studentid": "$_id.studentid",
"dept": "$_id.dept",
"teacher_id": "$teacher_id",
"LastSwipeTimestamp": "$LastSwipeTimestamp"
}
}
])
explained:
You need to consider the not grouped fields in the $group stage so they are also available to the next $project stage...

Related

Restructuring a collection in MongoDB

My collection called "sets" currently looks like this:
[{
"_id": {
"$oid": "61c2c90b04a5c1fd873bca6c"
},
"exercise": "Flat Barbell Bench Press",
"repetitions": 8,
"rpe": 8,
"__v": 0,
"weight": 90,
"createdAt": {
"$date": {
"$numberLong": "1640155403594"
}
}
}]
It's an array with about 1500 documents several months worth of workouts.
What I'm trying to accomplish is this:
[{
"_id": {
"$oid": "62f3cee8d149f0c3534d848c"
},
"user": {
"$oid": "62d11eaa0caf6d2b3133b4b9"
},
"sets": [
{
"weight": 50,
"exercise": "Bench Press",
"repetitions": 8,
"rpe": 8,
"notes": "some note",
"_id": {
"$oid": "62f3cee8d149f0c3534d848d"
}
},
{},
{}
],
"createdAt": {
"$date": {
"$numberLong": "1660145384923"
}
}
}]
Essentially, what I'm trying to accomplish here is embedding an array of "set" objects as a field value for "sets" field. So that instead of a list of sets I have a list of workouts where sets are stored as an array of objects in a field called "sets".
Each "set" object has a date stamp and what I also need to do is to group these sets by day. So at the end of the day each new document represents one workout and has an id, user and sets fields, where each set is from that day.
My stackoverflow research tells me that I need to use aggregation, but I can't quite wrap my mind around how exactly I would do that.
Any help would be greatly appreciated!
/* UPDATE */
Here's the final query I came up with, hope someone will find it useful.
db.collection.aggregate([
{
$group: {
_id: {
$dateToString: {
format: "%Y-%m-%d",
date: "$createdAt"
}
},
sets: {
$push: {
_id: "$_id",
exercise: "$exercise",
repetitions: "$repetitions",
rpe: "$rpe",
__v: "$__v",
weight: "$weight",
createdAt: "$createdAt"
}
}
}
},
{
"$addFields": {
"user": "UserID",
"date": "$_id"
}
},
{
$project: {
"_id": 0
}
}
])

Query maximum N records of each group base on a condition in MongoDB?

I have a question regarding querying data in MongoDB. Here is my sample data:
{
"_id": 1,
"category": "fruit",
"userId": 1,
"name": "Banana"
},
{
"_id": 2,
"category": "fruit",
"userId": 2,
"name": "Apple"
},
{
"_id": 3,
"category": "fresh-food",
"userId": 1,
"name": "Fish"
},
{
"_id": 4,
"category": "fresh-food",
"userId": 2,
"name": "Shrimp"
},
{
"_id": 5,
"category": "vegetable",
"userId": 1,
"name": "Salad"
},
{
"_id": 6,
"category": "vegetable",
"userId": 2,
"name": "carrot"
}
The requirements:
If the category is fruit, returns all the records match
If the category is NOT fruit, returns maximum 10 records of each category grouped by user
The category is known and stable, so we can hard-coded in our query.
I want to get it done in a single query. So the result expected should be:
{
"fruit": [
... // All records of
],
"fresh-food": [
{
"userId": 1,
"data": [
// Top 10 records of user 1 with category = "fresh-food"
]
},
{
"userId": 2,
"data": [
// Top 10 records of user 2 with category = "fresh-food"
]
},
...
],
"vegetable": [
{
"userId": 1,
"data": [
// Top 10 records of user 1 with category = "vegetable"
]
},
{
"userId": 2,
"data": [
// Top 10 records of user 2 with category = "vegetable"
]
},
]
}
I've found the guideline to group by each group using $group and $slice, but I can't apply the requirement number #1.
Any help would be appreciated.
You need to use aggregation for this
$facet to categorize incoming data, we categorized into two. 1. Fruit and 2. non_fruit
$match to match the condition
$group first group to group the data based on category and user. Second group to group by its category only
$objectToArray to make the object into key value pair
$replaceRoot to make the non_fruit to root with fruit
Here is the code
db.collection.aggregate([
{
"$facet": {
"fruit": [
{ $match: { "category": "fruit" } }
],
"non_fruit": [
{
$match: {
$expr: {
$ne: [ "$category", "fruit" ]
}
}
},
{
$group: {
_id: { c: "$category", u: "$userId" },
data: { $push: "$$ROOT" }
}
},
{
$group: {
_id: "$_id.c",
v: {
$push: {
uerId: "$_id.u",
data: { "$slice": [ "$data", 3 ] }
}
}
}
},
{ $addFields: { "k": "$_id", _id: "$$REMOVE" } }
]
}
},
{ $addFields: { non_fruit: { "$arrayToObject": "$non_fruit" } }},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ "$$ROOT", "$non_fruit" ]
}
}
},
{ $project: { non_fruit: 0 } }
])
Working Mongo playground

Grouping by array elements and find sum of common array fields

The document has an array field and each of them has a unique Id field. After doing unwind on the array, I need to add a count field on each array document instead of Id. The count should be the sum of similar array fields (grouped by fields other than Id).
Sample document - https://mongoplayground.net/p/LUPqVw07unP
Expected result:
{
"originId": 123,
"taskMetric": {
"count": 2,
"status": "OPEN",
"assignee": "ABC"
}
}, {
"originId": 1,
"taskMetric": {
"count": 1,
"status": "COMPLETED",
"assignee": "CDE"
}
}, {
"originId": 1,
"taskMetric": {
"count": 1,
"status": "COMPLETED",
"assignee": "EFG"
}
}
Play
You have to add one more projection stage to avoid _id and add id to group stage as other fields to the following query.
db.collection.aggregate([
{
$unwind: "$tasks"
},
{
$group: {
"_id": {
"status": "$tasks.status",
"assignee": "$tasks.assignee"
},
"count": {
$sum: 1
},
"status": {
$first: "$tasks.status"
},
"assignee": {
$first: "$tasks.assignee"
}
}
}
])

Aggregation at each document level mongodb

I have a list of documents like this
[{
"_id": "5dbc95f921d7625303fe2369",
"name": "John",
"itemsPurchased": [{
"offer": "o1",
"items": ["p1"]
},{
"offer": "o1",
"items": ["p1"]
},
{
"offer": "o1",
"items": ["p2"]
},
{
"offer": "o2",
"items": ["p1"]
}, {
"offer": "o7",
"items": ["p1"]
}
]
},
{
"_id": "zbc95f921d7625303fe2363",
"name": "Doe",
"itemsPurchased": [{
"offer": "o1",
"items": ["p11"]
},{
"offer": "o1",
"items": ["p11"]
},
{
"offer": "o2",
"items": ["p13"]
},
{
"offer": "o1",
"items": ["p22"]
},
{
"offer": "o2",
"items": ["p11"]
}, {
"offer": "o3",
"items": ["p11"]
}
]
}
]
And i am trying to compute unique offers on unique products by each customer, expecting the resultant to be like:
[
{
"_id": "5dbc95f921d7625303fe2369",
"name": "John",
"offersAndProducts": {
"o1":2,
"o2":2,
"o3":1
},
{
"_id": "zbc95f921d7625303fe2363",
"name": "Doe",
"offersAndProducts": {
"o1":2,
"o2":1,
"o7":1
}
]
I want to apply aggregations per document, After performing $unwind on itemsPurchased, applied $group on items and then on offer to eliminate the duplication:
{
"$group" : {
"_id" : {
"item" : {
"$arrayElemAt" : [
"$itemsPurchased.item",
0.0
]
},
"count" : {
"$sum" : 1.0
},
"offer" : "$itemsPurchased.offer"
}
}
}
then,
{
"$group" : {
"_id" : "$_id.offer",
"count" : {
"$sum" : 1.0
}
}
}
this gives the array of products and offers for all documents:
[
{o1:4,o2:3,o3:1,o7:1}
]
But i need it at document level.
tried $addFeild, but $unwind and $match operators gives invalid error.
Any other way of achieving this?
Generally speaking, it's an anti-pattern to $unwind an array and then to $group on the original _id since most operations can be done on the array directly, in a single stage. Here is what such a stage would look like:
{$addFields:{
offers:{$arrayToObject:{
$map:{
input:{$setUnion:"$itemsPurchased.offer"},
as:"o",
in:[
"$$o",
{$size:{$setUnion:{$let:{
vars:{items:{$filter:{
input:"$itemsPurchased",
cond:{$eq:["$$this.offer","$$o"]}
}}},
in:{$reduce:{
input:"$$items",
initialValue:[],
in:{$concatArrays:["$$value","$$items.items"]}
}}
}}}
}]
}
}}
}}
What this does is create an array where each element is a two element array (which is a syntax that $arrayToObject can convert to an object where first element is key name and second is value) and the input is a unique set of offers and for each we accumulate an array of products, get rid of duplicates (with $setUnion) and then get the size of the result. What this produces on your input is this:
"offers" : {
"o1" : 2,
"o2" : 2,
"o3" : 1
}
You need to run $unwind and $group twice. To count only unique items you can use $addToSet. To build your keys dynamically you need to use $arrayToObject:
db.collection.aggregate([
{
$unwind: "$itemsPurchased"
},
{
$unwind: "$itemsPurchased.items"
},
{
$group: {
_id: {
_id: "$_id",
offer: "$itemsPurchased.offer"
},
name: { $first: "$name" },
items: { $addToSet: "$itemsPurchased.items" }
}
},
{
$group: {
_id: "$_id._id",
name: { $first: "$name" },
offersAndProducts: { $push: { k: "$_id.offer", v: { $size: "$items" } } }
}
},
{
$project: {
_id: 1,
name: 1,
offersAndProducts: { $arrayToObject: "$offersAndProducts" }
}
}
])
Mongo Playground

Get last and minimal values from grouped documents

My document model looks like:
{
"model": "ABC123",
"date": "2018-12-24T23:00:00.000+0000",
"price": "2000" ,
}
I would like to retrive collection to get array of documents:
[
{ "_id" : "ABC123", "newestDate" : ISODate("2018-12-26T23:00:00Z"), "newestPrice" : 2801.33, "lowestPriceAtAll": 1300 },
{ "_id" : "ABC124", "newestDate" : ISODate("2018-12-26T23:00:00Z"), "newestPrice" : 2801.33, "lowestPriceAtAll": 990}
]
where _id is model field, newestPrice is price of newest document (grouped by model) and lowestPriceAtAll is lowest price in all documents with the same model.
I grilled two queries.
First is to find lowest price documents:
offers.aggregate([
{ $sort: { "model": 1, "price": 1 }},
{
$group: {
_id: "$model",
lowestPrice: { "$first": "$price" },
lowestPriceDate: { "$first": "$date"},
}
}
])
the second is to find newest documents:
offers.aggregate([
{ $sort: { "model": 1, "date": -1 }},
{
$group: {
_id: "$model",
newestDate: { "$first": "$date" },
newestPrice: { "$first": "$price"},
}
}
])
Is it possible to merge these two queries into one? (the most important thing is that documents have to be grouped by model field).
you can use $facet
db.offers.aggregate([
{$facet :{
lowest: [
{ $sort: { "model": 1, "price": 1 }},
{
$group: {
_id: "$model",
lowestPrice: { "$first": "$price" },
lowestPriceDate: { "$first": "$date"},
}
}
],
newest: [
{ $sort: { "model": 1, "date": -1 }},
{
$group: {
_id: "$model",
newestDate: { "$first": "$date" },
newestPrice: { "$first": "$price"},
}
}
]
}}
])