How to get X students from each grade in MongoDB? - mongodb

I have a mongoDB collection named students with the fields grade and name.
I would like to create a query that will get up to 2 students from each grade inside my gradesList.
So this is my input:
const gradesList = [10, 11, 12]
And this is my desired output:
[
{
name: 'Albert',
grade: 10
},
{
name: 'Tim',
grade: 10
},
{
name: 'Monika',
grade: 11
},
{
name: 'Mike',
grade: 11
},
{
name: 'Rich',
grade: 12
},
{
name: 'Beth',
grade: 12
},
]
I am assuming I need to use the aggregate framework but I have no idea how to accomplish what I am trying to do.
One solution is to iterate over the gradesList array and execute a query like this: db.students.find({ grade: 10 }).limit(2). However this is a really bad idea.
Is there an efficient way of accomplishing this in 1 query?

Here's one way to do it.
db.collection.aggregate([
{
"$match": {
"grade": {"$in": [10, 11, 12]}
}
},
{
"$group": {
"_id": "$grade",
"name": {
"$firstN": {
"n": 2,
"input": "$name"
}
}
}
},
{"$unwind": "$name"},
{
"$project": {
"_id": 0,
"name": 1,
"grade": "$_id"
}
},
{
"$sort": {
"grade": 1,
"name": 1
}
}
])
Sample output:
[
{
"grade": 10,
"name": "Jayne Cormier"
},
{
"grade": 10,
"name": "Rebekah Jacobi"
},
{
"grade": 11,
"name": "Mariano Reinger"
},
{
"grade": 11,
"name": "Shea Hartmann"
},
{
"grade": 12,
"name": "Colt Spinka"
},
{
"grade": 12,
"name": "Stephanie Schiller"
}
]
Try it on mongoplayground.net.

Related

How to get X students from each grade in MongoDB version 5.0?

I have a mongoDB collection named students with the fields grade and name.
I would like to create a query that will get up to 2 students from each grade inside my gradesList.
In version MongoDB version 5.2. and above, you can use the $firstN aggregation operator together with $group like this:
const gradesList = [10, 11, 12]
db.students.aggregate([ { $match: { grade: { $in: gradesList } } },
{
$group: {
_id: '$grade',
name: {
$firstN: {
n: 2,
input: "$name"
}
}
}
},
{ $unwind: "$name" },
{ $project: { _id: 0, name: 1, grade: "$_id" } },
])
And this is the output:
[
{
name: 'Albert',
grade: 10
},
{
name: 'Tim',
grade: 10
},
{
name: 'Monika',
grade: 11
},
{
name: 'Mike',
grade: 11
},
{
name: 'Rich',
grade: 12
},
{
name: 'Beth',
grade: 12
},
]
However, in order to use $firstN alongside MongoDB Atlas, you need to use a dedicated cluster which starts at around $60 a month, because the shared cluster tiers only support mongodb version 5.0, which don't support $firstN.
Is there a way to accomplish the same objective with aggregation operators available in version 5.0?
Only a small change is needed to be compatible with MongoDB version 5.0.
db.collection.aggregate([
{
"$match": {
"grade": {"$in": [10, 11, 12]}
}
},
{
"$group": {
"_id": "$grade",
"name": {"$push": "$name"}
}
},
{
"$set": {
"name": {"$slice": ["$name", 2]}
}
},
{"$unwind": "$name"},
{
"$project": {
"_id": 0,
"name": 1,
"grade": "$_id"
}
},
{
"$sort": {
"grade": 1,
"name": 1
}
}
])
Try it on mongoplayground.net.

How can I get a single item from the array and display it as an object? and not as an array Mongodb

I have a collection from which I need specific obj e.g. notes.blok2 and notes.curse5 as an object, not as an array
{
"year":2020,
"grade":4,
"seccion":"A",
"id": 100,
"name": "pedro",
"notes":[{"curse":5,
"block":1,
"score":{ "a1": 5,"a2": 10, "a3": 15}
},{"curse":5,
"block":2,
"score":{ "b1": 10,"b2": 20, "b3": 30}
}
]
}
My query
notas.find({
"$and":[{"grade":1},{"seccion":"A"},{"year":2020}]},
{"projection":{ "grade":1, "seccion":1,"name":1,"id":1,
"notes":{"$elemMatch":{"block":2,"curse":5}},"notes.score":1} })
It works but returns notes like array
{
"_id": "55",
"id": 100,
"grade": 5,
"name": "pedro",
"seccion": "A",
"notes": [
{"score": { "b1": 10,"b2": 20, "b3": 30} }
]
}
But I NEED LIKE THIS: score at the same level as others and if doesn't exist show empty "score":{}
{
"year":2020,
"grade":5,
"seccion":"A",
"id": 100,
"name": "pedro",
"score":{ "b1": 10,"b2": 20, "b3": 30}
}
Demo - https://mongoplayground.net/p/XlJqR2DYW1X
You can use aggregation query
db.collection.aggregate([
{
$match: { // filter
"grade": 1,
"seccion": "A",
"year": 2020,
"notes": {
"$elemMatch": {
"block": 2,
"curse": 5
}
}
}
},
{ $unwind: "$notes" }, //break into individual documents
{
$match: { // match query on individual note
"notes.block": 2,
"notes.curse": 5
}
},
{
$project: { // projection
"grade": 1,
"seccion": 1,
"name": 1,
"id": 1,
"score": "$notes.score"
}
}
])
Update
Demo - https://mongoplayground.net/p/mq5Kue3UG42
Use $filter
db.collection.aggregate([
{
$match: {
"grade": 1,
"seccion": "A",
"year": 2020
}
},
{
$set: {
"score": {
"$filter": {
"input": "$notes",
"as": "note",
"cond": {
$and: [
{
$eq: [ "$$note.block",3]
},
{
$eq: [ "$$note.curse", 5 ]
}
]
}
}
}
}
},
{
$project: {
// projection
"grade": 1,
"seccion": 1,
"name": 1,
"id": 1,
"score": {
"$first": "$score.score"
}
}
}
])
If you want empty object for score when match not found you can do -
Demo - https://mongoplayground.net/p/dumax58kgrc
{
$set: {
score: {
$cond: [
{ $size: "$score" }, // check array length
{ $first: "$score" }, // true - take 1st
{ score: {} } // false - set empty object
]
}
}
},

How to group data and get and get all the field back in mongodb [duplicate]

This question already has an answer here:
How to group documents with specific field in aggregation of mongodb
(1 answer)
Closed 1 year ago.
I have collection in a mongodb and want to group invoice by modified and get all the fields in those objects.
{
modified: "11/02/2020",
stocks: [
{
product:{
name:"Milk",
price: 20
}
quantity: 2,
paid: true
}
]
},
{
modified: "10/02/2020",
stocks: [
{
product:{
name:"Sugar",
price: 50
}
quantity: 1,
paid: false
}
]
},
{
modified: "10/02/2020",
stocks: [
{
product:{
name:"Butter",
price: 10
}
quantity: 5,
paid: false
}
]
}
So I tried:
db.collection.aggregate([{
$group: {
_id: "$modified",
records: { $push: "$$ROOT" }
}
}
])
But It reaggregate on the modifier field being pushed into the records generating duplicates
Demo - https://mongoplayground.net/p/4AGuo5zfF4V
Use $group
db.collection.aggregate([
{
$group: { _id: "$grade", records: { $push: "$$ROOT" } }
}
])
Output
[
{
"_id": "A",
"records": [
{
"_id": ObjectId("5a934e000102030405000000"),
"grade": "A",
"name": "John",
"subject": "English"
},
{
"_id": ObjectId("5a934e000102030405000001"),
"grade": "A",
"name": "John1",
"subject": "English"
}
]
},
{
"_id": "B",
"records": [
{
"_id": ObjectId("5a934e000102030405000002"),
"grade": "B",
"name": "JohnB",
"subject": "English"
},
{
"_id": ObjectId("5a934e000102030405000003"),
"grade": "B",
"name": "JohnB1",
"subject": "English"
}
]
}
]

MongoDB aggregation and sums by common field inside an array

I'm trying to get a list that counts all the victories and battles grouped by player name out of this json that I obtain from an API:
[
{
"createdDate": 1541411260,
"players": [
{
"tag": "tag1234",
"name": "name1",
"battles": 2,
"wins": 1
},
{
"tag": "tag124567",
"name": "name2",
"battles": 1,
"wins": 0
},
{
"tag": "tag1234",
"name": "name3",
"battles": 3,
"wins": 3
}
]
},
{
"createdDate": 1541411460,
"players": [
{
"tag": "tag1234",
"name": "name1",
"battles": 1,
"wins": 1
},
{
"tag": "tag124567",
"name": "name2",
"battles": 1,
"wins": 1
},
{
"tag": "tag1234",
"name": "name3",
"battles": 0,
"wins": 0
},
{
"tag": "tag124567",
"name": "name4",
"battles": 1,
"wins": 0
}
]
},
{
"createdDate": 1541455260,
"players": [
{
"tag": "tag1234",
"name": "name1",
"battles": 0,
"wins": 0
},
{
"tag": "tag124567",
"name": "name2",
"battles": 4,
"wins": 4
},
{
"tag": "tag1234",
"name": "name3",
"battles": 6,
"wins": 6
}
]
}
]
The mongo query I'm using is the following but I can't get the names and battles/wins:
db.getCollection("logs").aggregate([
{ $unwind : '$players' },
{
$group: {
_id: { name: '$players.name' },
numBattles: { $sum: '$players.battles' },
numWins: { $sum: '$players.wins' }
}
},
{
$project: {
name: "$_id.name",
numBattles: '$_id.numBattles',
numWins: '$_id.numWins',
_id: 0
}
]
).pretty();
This gave me 0 results.
Also tried the following but it's returning a full group of players and their stats:
db.getCollection("logs").aggregate([
{
$group: {
_id: { name: '$players.name' },
numBattles: { $sum: '$players.battles' },
numWins: { $sum: '$players.wins' }
}
},
{
$project: {
name: "$_id.name",
numBattles: '$_id.numBattles',
numWins: '$_id.numWins',
_id: 0
}
}
]
).pretty();
The idea is to get something like this:
name1 - 3 battles and 2 wins,
name2 - x battles and y wins,
...
Any ideas?
Thank you.
In your $project stage you're referring to _id which contains only name and other fields should be referenced directly so you just need to change your $project to:
{
$project: {
name: "$_id.name",
numBattles: "$numBattles",
numWins: "$numWins",
_id: 0
}
}

mongo query: add a new field with ranking number based on another field

I am new to mongo queries. Currently I have a collection like this, which is used to create a d3 force-directed graph.
{
"_id": "allesgute3",
"nodes": [{
"id": "bmw#gmail.com",
"count": 15,
"nodeUpdatetime": 1525341732
}, {
"id": "abc#gmail.com",
"count": 10,
"nodeUpdatetime": null
}, {
"id": "xyz#gmail.com",
"count": 8,
"nodeUpdatetime": 1525408742
}, {
"id": "wilson#gmail.com",
"count": 4,
"nodeUpdatetime": 1525423847
}, {
"id": "niv#gmail.com",
"count": 6,
"nodeUpdatetime": 1525447758
}, {
"id": "car#gmail.com",
"count": 9,
"nodeUpdatetime": 1525447763
},
{
"id": "jason#gmail.com",
"count": 1,
"nodeUpdatetime": 1525447783
}
],
"links": [{
"source": "bmw#gmail.com",
"target": "jason#gmail.com",
"timestamp": 1525312111
}, {
"source": "car#gmail.com",
"target": "jason#gmail.com",
"timestamp": 1525334013
}, {
"source": "bmw#gmail.com",
"target": "car#gmail.com",
"timestamp": 1525334118
}]
}
Using a mongo query, I would like to generate the output to something like this. Basically for the nested data under "nodes", add a new field called "topn" and rank them by count from 1 to 5. The remainder values are null. Can anyone help? Thank you!
{
"_id": "allesgute3",
"nodes": [{
"id": "bmw#gmail.com",
"count": 15,
"nodeUpdatetime": 1525341732,
"topn": 1
}, {
"id": "abc#gmail.com",
"count": 10,
"nodeUpdatetime": null,
"topn": 2
}, {
"id": "xyz#gmail.com",
"count": 8,
"nodeUpdatetime": 1525408742,
"topn": 4
}, {
"id": "wilson#gmail.com",
"count": 4,
"nodeUpdatetime": 1525423847,
"topn": null
}, {
"id": "niv#gmail.com",
"count": 6,
"nodeUpdatetime": 1525447758,
"topn": 5
}, {
"id": "car#gmail.com",
"count": 9,
"nodeUpdatetime": 1525447763,
"topn": 3
},
..............
The following should get you what you want:
db.collection.aggregate({
$unwind: "$nodes" // flatten the "nodes" array
}, {
$sort: { "nodes.count": -1 } // sort descending by "count"
}, {
$group: { // create the original structure again - just with sorted array elements
_id: "$_id",
nodes: { "$push": "$nodes" }
}
}, {
$addFields: {
"nodes": {
$zip: { // zip two arrays together
inputs: [
"$nodes", // the first one being the existing and now sorted "nodes" array
{ $range: [ 1, 6 ] } // and the second one being [ 1, 2, 3, 4, 5 ]
],
useLongestLength: true // do not stop after five elements but instead continue using a "null" value
}
}
}
}, {
$addFields: {
"nodes": {
$map: { // transform the "nodes" array
input: "$nodes",
as: "this",
in: {
$mergeObjects: [ // by merging two objects
{ $arrayElemAt: [ "$$this", 0] }, // the first sits at array position 0
{
topn: { $arrayElemAt: [ "$$this", 1] } // the second will be a new entity witha a "topn" field holding the second element in the array
}
]
}
}
}
}
})