How to merge an array object's fields with aggregate in mongodb - mongodb

I am trying to aggregate an object with arrays. Would appreciate help.
here is my sample object.
[
{
"tipo": "A",
"prices": [
{
"min_pax": 1,
"max_pax": 3,
"type": "One Way",
"price": 35
},
{
"min_pax": 1,
"max_pax": 3,
"type": "Round Trip",
"price": 63
},
{
"min_pax": 4,
"max_pax": 6,
"type": "One Way",
"price": 40
},
{
"min_pax": 4,
"max_pax": 6,
"type": "Round Trip",
"price": 65
},
{
"min_pax": 7,
"max_pax": 10,
"type": "One Way",
"price": 50
},
{
"min_pax": 7,
"max_pax": 10,
"type": "Round Trip",
"price": 80
}
],
}
]
I want to merge objects with same number of min & max pax, for example:
Between 1 and 3 passengers for one way trip the cost is 35, for round trip the cost is 63.
I think looks better is I can merge both objects.
I would like something like this:
[
{
"tipo": "A",
"prices": [
{
"min_pax": 1,
"max_pax": 3,
"one_way": 35,
"round_trip": 63
},
{
"min_pax": 4,
"max_pax": 6,
"one_way": 40,
"round_trip":65
},
{
"min_pax": 7,
"max_pax": 10,
"one_way": 50,
"round_trip": 80
},
],
}
]
I would really appreciate your help

One option is to $unwind and $group again according to the pax:
db.collection.aggregate([
{$unwind: "$prices"},
{$group: {
_id: {max_pax: "$prices.max_pax", min_pax: "$prices.min_pax"},
tipo: {$first: "$tipo"},
data: {$push: {k: "$prices.type", v: "$prices.price"}}
}
},
{$project: {
_id: 0,
tipo: 1,
max_pax: "$_id.max_pax",
min_pax: "$_id.min_pax",
data: {$arrayToObject: "$data"}
}
},
{$set: {"data.max_pax": "$max_pax", "data.min_pax": "$min_pax"}},
{$group: {_id: "$tipo", prices: {$push: "$data"}}},
{$project: {_id: 0, tipo: "$_id", prices: 1}}
])
See how it works on the playground example

Related

How to get X students from each grade in 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.

Mongo db aggregation - $push and $slice top results

I have the following documents in my db:
{uid: 1, score: 10}
{uid: 2, score: 11}
{uid: 3, score: 1}
{uid: 4, score: 6}
{uid: 5, score: 2}
{uid: 6, score: 3}
{uid: 7, score: 8}
{uid: 8, score: 10}
I want to split them into buckets by score - i.e.:
score
uids
(bucket name in aggregation)
[0,4)
3,5,6
0
[4,7)
4
4
[7,inf
1,2,7,8
7
For this, I created the following aggregation which works just fine:
db.scores.aggregation(
[
{
$bucket:
{
groupBy: "$score",
boundaries: [0, 4, 7],
default: 7,
output:
{
"total": {$sum: 1},
"top_frustrated":
{
$push: {
"uid": "$uid", "score": "$score"
}
},
},
}
},
]
)
However, I would like to return only the top 3 of every bucket - i.e, buckets 0, 4 should be the same, but bucket 7 should have only uids 1,2,8 returned (as uid 7 has the lowest score) - but to include the total count of documents as well, i.e. output of bucket "7" should look like:
{ "total" : 4, "top_scores" :
[
{"uid" : 2, "score" : 11},
{"uid" : 1, "score" : 10},
{"uid" : 8, "score" : 10},
]
}
I tried using $addFields with $sortArray and $slice, but it either won't work or return errors.
I can of course use $project but I was wondering if there is a more efficient way.
I am using Amazon DocumentDB.
You can use the $topN accumulator, instead of $push, like this:
db.collection.aggregate([
{
"$bucket": {
"groupBy": "$score",
"boundaries": [
0,
4,
7
],
"default": 7,
"output": {
"total": {
"$sum": 1
},
"top_frustrated": {
"$topN": {
"n": 3,
"sortBy": {
"score": -1
},
"output": {
"uid": "$uid",
"score": "$score"
}
}
}
},
}
},
])
Playground link.
The only catch here is this operator is present in MongoDB 5.2 and above.
For older versions, this will work:
db.collection.aggregate([
{
"$sort": {
score: -1
}
},
{
$bucket: {
groupBy: "$score",
boundaries: [
0,
4,
7
],
default: 7,
output: {
"total": {
$sum: 1
},
"top_frustrated": {
$push: {
"uid": "$uid",
"score": "$score"
}
},
},
}
},
{
"$project": {
total: 1,
top_frustrated: {
"$slice": [
"$top_frustrated",
3
]
}
}
}
])
Playground link.

Mongo Filter Similar Documents

Let's say I have this collection:
Cards:
[
{orientation: "portrait", size: "45", columns: 2, type: 1, _id: "c5ea1968-6ab5-4b05-80a9-db9dabe29dde"},
{orientation: "landscape", size: "45", columns: 3, type: 1, _id: "37186b8e-4033-46c6-8b2e-82ee45f96904"},
{orientation: "portrait", size: "45", columns: 2, type: 1, _id: "8f49a3ff-2859-4027-b644-88ac0949808d"},
{orientation: "portrait", size: "45", columns: 2, type: 2, _id: "862717f4-2ef7-4839-947a-02771338c38c"},
{orientation: "portrait", size: "45", columns: 2, type: 3, _id: "e1a93b10-dfcb-42a2-955a-ee14d539624f"}
]
And I need a result like this:
CardList:
[
{ type: 1,
list: [
{orientation: "landscape", size: "45", columns: 3, type: 1, _id: "1_landscape_2"},
{orientation: "portrait", size: "45", columns: 2, type: 1, _id: "1_portrait_2"}
]
},
{ type: 2,
list: [
{orientation: "portrait", size: "45", columns: 2, type: 2, _id: "2_portrait_2"}
],
{ type: 3,
list: [
{orientation: "portrait", size: "45", columns: 2, type: 3, _id: "3_portrait_2"}
]
}
];
So, group by common attribute type, and not repeating documents with same attributes orientation and columns, for example.
I was able to group by type:
{ _id : "$type", list: { $push: "$$ROOT" } }
But, how would be the next stages?
You can use two $group stages like this:
First group to get the elements where orientation, columns and type are the same. So, that is, create the "list" objects.
With that objects, another $group by the type and add result to an array
db.collection.aggregate([
{
"$group": {
"_id": {
"orientation": "$orientation",
"columns": "$columns",
"type": "$type"
},
"list": {
"$first": "$$ROOT"
}
}
},
{
"$group": {
"_id": "$_id.type",
"list": {
"$push": "$list"
}
}
}
])
Example here
Note how the first $group uses $first to avoid repeated values (each type has one single elements with the same orientation and columns. And the second $group uses $push to generate the list.
Also, to get your desired _id output (and assuming is compound by type_orientation_columns) in the list field you can do this:
Add orientation and columns in the second $group to keep as auxiliar values.
Use $project to get desired values
db.collection.aggregate([
{
"$group": {
"_id": {
"orientation": "$orientation",
"columns": "$columns",
"type": "$type"
},
"list": {
"$first": "$$ROOT"
}
}
},
{
"$group": {
"_id": "$_id.type",
"list": {
"$push": "$list"
},
"orientation": {
"$first": "$_id.orientation"
},
"columns": {
"$first": "$_id.columns"
}
}
},
{
"$project": {
"_id": 0,
"type": "$_id",
"list": {
"orientation": 1,
"size": 1,
"columns": 1,
"type": 1,
"_id": {
"$concat": [
{"$toString": "$_id"},
"_",
{"$toString": "$orientation"},
"_",
{"$toString": "$columns"}
]
}
}
}
}
])
Example here

MongoDB aggregation, combining 2 arrays in round-robin fashion

I have data in a MongoDB collection that looks something like this:
[
{
"_id": 1,
"type": "big",
"fields": [11, 12, 13],
"items": [21, 22, 23]
},
{
"_id": 2,
"type": "small",
"fields": [14, 15],
"items": [24, 25]
},
{
"_id": 3,
"type": "small",
"fields": [],
"items": [41, 42]
},
{
"_id": 4,
"type": "small",
"fields": [31, 32, 33],
"items": []
}
]
I have been tasked with returning data according to a procedure like this:
For each document in the collection, obtain 1 value from its fields (if there are any), and 1 value its items (if there are any). Concatenate all of the results in a single array.
One might summarize this as selecting data in "round robin" fashion from two arrays held in each document.
How would I achieve this in a MongoDB aggregation query? This logic is not hard to implement in the client that connects to the Mongo server, but I would like to let Mongo take care of pagination (with $skip and $limit). I am using MongoDB 4.4.
The resulting data would look something like this:
[
{
"value": 11,
"type": "field",
"fromId": 1
},
{
"value": 21,
"type": "item",
"fromId": 1
},
{
"value": 14,
"type": "field",
"fromId": 2
},
{
"value": 24,
"type": "item",
"fromId": 2
},
{
"value": 41,
"type": "item",
"fromId": 3
},
{
"value": 31,
"type": "field",
"fromId": 4
},
]
If I understand your question right; this should be a workable pipe. To implement a random functionality, you would simply adjust the index passed to $arrayElemAt
https://mongoplayground.net/p/PPXV6fTSwHP
db.collection.aggregate([
{$project: {
types: [
{type: "field", values: "$fields"},
{type: "item", values: "$items"}
]
}},
{$unwind: '$types'},
{$project: {
_id: 0,
value: {$arrayElemAt: ['$types.values', 0]},
type: '$types.type',
fromId: '$_id'
}},
{$match: {
value: {$exists: true}
}}
])
Randomizing would look something like this:
https://mongoplayground.net/p/qi1Ud53J6yv
db.collection.aggregate([
{$project: {
types: [
{type: "field", values: "$fields"},
{type: "item", values: "$items"}
]
}},
{$unwind: '$types'},
{$project: {
_id: 0,
value: {$arrayElemAt: [
'$types.values',
{$floor: {$multiply: [{$rand: {}}, {$size: '$types.values'}]}}
]},
type: '$types.type',
fromId: '$_id'
}},
{$match: {
value: {$exists: true}
}}
])

How to group data again in a single object with new keys after a mongodb $group?

I have a mongodb database with a collection of companies that look like this (it's just a sample, the actual collection is much larger):
[
{
"_id": 100,
"name": "Test Name 1",
"level": "1"
},
{
"_id": 101,
"name": "Test Name 2",
"level": "1"
},
{
"_id": 102,
"name": "Test Name 3",
"level": "2"
}
]
Where "level" can only range from 0 to 5
I'm trying to make an aggregate query with $group and $project that counts how many companies there are in each level, but according to the API specification I need follow, it needs to be formatted like this, in a single object:
{
"metrics": {
"companies": {
"total": <integer>,
"level1": <integer>,
"level2": <integer>,
"level3": <integer>,
"level4": <integer>,
"level5": <integer>
}
}
}
The closest I could get to this was using $group and $project like this:
Companies.aggregate([{
$group: {
_id: {
level: "$level"
},
count: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
level: "$_id.level",
total: "$count"
}
}
])
Which gives the following result:
[
{
"level": 3,
"total": 108
},
{
"level": 5,
"total": 172
},
{
"level": 2,
"total": 624
},
{
"level": 4,
"total": 98
},
{
"level": 1,
"total": 137
},
{
"level": 0,
"total": 94
}
]
However, this result is an array and I need to put the data for each level in a single object with new keys "level1", "level2", etc, according to the specification.
I believe I need to make another $group operation but I couldn't find out how to do it.
Any ideas?
I'm not sure If I understand, but I suppose you just need to map it, like here:
> var aux = new Object;
> db.Companies.aggregate([
{
$group: {
_id: {
level: "$level"
},
count: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
level: "$_id.level",
total: "$count"
}
}
]).forEach(function(a){aux["level"+a.level] = a.total;});
> printjson(aux);
{ "level2" : 1, "level1" : 2 }
I believe there could be better solution, but this one is working:
db.companies.aggregate([{
$group:{_id:{level: "$level"}, count: {$sum: 1}}},
{$group:{"_id": 0, levels: {$push: {_id:"$_id.level", count: "$count"}}, total: {$sum: "$count"}}},
{$unwind: "$levels"},
{$sort: {"levels._id": 1}},
{$group:{_id: 0, levels: {$push: {levels:"$levels.count"}}, "total": {$avg:"$total"}}},
{$project: {total: "$total", level1: {$arrayElemAt: ["$levels",0]}, level2: {$arrayElemAt: ["$levels", 1]}, level3: {$arrayElemAt: ["$levels",2]}, level4: {$arrayElemAt: ["$levels",3]},level5: {$arrayElemAt: ["$levels",4]} }},
{$project: {_id: 0, metrics: {companies: {total: "$total", level1: "$level1.levels", level2: "$level2.levels", level3: "$level3.levels",level4: "$level4.levels", level5: "$level5.levels"}}}}
])
Returned result:
{ "metrics" :
{ "companies" :
{ "total" : 7,
"level1" : 1,
"level2" : 2,
"level3" : 2,
"level4" : 1,
"level5" : 1
} } }