Limiting number of different kind of results in mongoDB aggregation - node.js

I would like to write an aggregation that take the following documents:
{ type: "dog", name: "Charlie" }
{ type: "dog", name: "Felix" }
{ type: "dog", name: "John" }
{ type: "cat", name: "Tum" }
And returns up to 2 of each kind, not grouped in any separate way:
{ type: "dog", name: "Charlie" }
{ type: "dog", name: "Felix" }
{ type: "cat", name: "Tum" }
Meaning just up to two cats plus up to two dogs. Does grouping and limiting the way to go here? If so - how?

You can group the documents by type, create a list of the names per group, $project the list to only have two elements with $slice and then flatten the list using $unwind, something like the following:
Model.aggregate([
{ "$group": {
"_id": "$type",
"data": { "$push": "$name" }
} },
{ "$project": {
"type": "$_id",
"name": { "$slice": ["$data", 2] }
} },
{ "$unwind": "$name" }
]).exec(callback);

Related

Find just one subdocument of query (two nested arrays) with mongoose

I'm trying to extract just the right element of an array nested in an array inside a mongoDB document.
This is my example document:
[
{
"name": "name1",
"list1": [
{
"name2": "elem1",
"list2": [
{
"name3": "elem 11"
},
{
"name3": "elem22"
}
]
}
],
{...}
}
]
I want to take just the element of the array with name=name1, name2=eleme1 and name3=elem22.
I want to avoid to use "aggregate", but the idea is the use of findOne().
I tried the following query
this.findOne({ name: "name1", list1: { $elemMatch: { name2: "elem1"}}, 'list1.list2': { $elemMatch: { name3: "elem22"} }})
.select({ list1: { $elemMatch: { name1: "elem1" }})
.select({ "list1.list2": { $elemMatch: { name2: "elem22" }}).
There is an error in the syntax, but If I remove the second select, it selects just an element of the array "list1".
Thanks for your help

MongoDB group by aggregation query

The data I have is:
[
{ type: 'software' },
{ type: 'hardware' },
{ type: 'software' },
{ type: 'network' },
{ type: 'test' },
...
]
I want to create a MongoDB group by aggregation pipeline to return the data like this:
I only want 3 objects in result
the third object in result {_id: 'other', count: 2}, This should be the sum of counts of type other that software and hardware
[
{_id: 'software', count: 2},
{_id: 'hardware', count: 1},
{_id: 'other', count: 2},
]
This is the exact query (MongoPlayground) that you need if those data are separate documents. Just add $project stage before group and then $switch operator. (If those field data are number, you might wanna check $bucket
db.collection.aggregate([
{
"$project": {
type: {
"$switch": {
"branches": [
{
"case": {
"$eq": [
"$type",
"software"
]
},
"then": "software"
},
{
"case": {
"$eq": [
"$type",
"hardware"
]
},
"then": "hardware"
}
],
default: "other"
}
}
}
},
{
"$group": {
"_id": "$type",
"count": {
"$sum": 1
}
}
}
])
Also, I'd like to recommend avoiding field name type. Actually it doesn't reserve in MongoDB, but however it could bring conflicts with some drivers since, in schema/model files, type fields are referred to the exact BSON type of the field.

Mongoose - Custom sorting by a string field in specific order

I have a model with some type field:
const petOptions = Object.freeze({
Dog: "Dog",
Cat: "Cat",
Parrot: "Parrot",
Other: "Other",
});
const petSchema = new mongoose.Schema(
{
...,
type: {type: String, enum: petOptions, required: true},
}
)
const Pet = mongoose.model("Pet", petSchema)
when I'm running find on that model, I need to be able to sort by specific order, meaning:
first all documents with type = "Parrot"
then all documents with type = "Cat"
then all documents with type = "Dog"
then all others (all documents with type = "Other")
I can do that on the JS code itself but wonder if this is something that is possible to do using the mongoose find().sort()
Pet.find({}).sort(?)
Any help would be appreciated!
Thanks in advance
You can use $addFields and $switch to prepare a field you can $sort by in the next aggregation stage:
await Pet.aggregate([
{
$addFields: {
sortField: {
$switch: {
branches: [
{ case: { $eq: [ "$type", "Parrot" ] }, then: 0 },
{ case: { $eq: [ "$type", "Cat" ] }, then: 1 },
{ case: { $eq: [ "$type", "Dog" ] }, then: 2 },
],
default: 3
}
}
}
},
{
$sort: { sortField: 1 }
}
])
Mongo Playground

How to get an object field as an array in mongoose?

Hello I have a mongoose schema like the following,
const followingFollowersSchema = mongoose.Schema({
follower: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
following: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
});
I have created a route when called it filters all the users that the current logged in user is following and shows the following output,
{
"status": "success",
"results": 1,
"data": {
"following": [
{
"_id": "5ef4cd0205070d87156e19da",
"following": {
"_id": "5eeb92d69fceed6d91ad5743",
"name": "X Y"
}
}
]
}
}
But I want to show the output like this,
{
"status": "success",
"results": 1,
"data": {
"following": [
{
"_id": "5eeb92d69fceed6d91ad5743",
"name": "X Y"
}
]
}
}
How can I accomplish this? Any help would be appreciated.
You can use $addFields to replace existing fields and $map to transform an inner array:
let result = Model.aggregate([
{
$addFields: {
"data.following": {
$map: {
input: "$data.following",
in: "$$this.following"
}
}
}
}
])
Mongo Playground

$filter then $project on an aggregation request mongodb

I'm trying to query my database to get a specific element from an array, then only project a part of that array, here is the code I tried:
{ $project : {
name: 1,
language : 1 ,
season: [{
$filter: {
input: "$seasons",
as: "s",
cond: { $eq: [ "$$s.number", saison ] }
}
}, {
$project: {
'episodes.number': 1
}
}]
} }
I want to only get the season that matches the number, then project the number field only.
Here is my schema:
{
name: {type: String},
seasons: [{
number: Number,
episodes: [{number: Number, videos: [
{
provider: String,
quality: String,
language: String,
added: { type: Date, default: new Date(1510272000000) }
}
]}]
}]
}
My current query is generating an error: MongoError: Unrecognized expression '$project', if I do the filter without the $project after it works, but then it returns a whole array instead of what I just need. Thank you.
You have a mistake in your aggregation pipeline. What you mean to say is this?
db.collectionName.aggregate([
{
$project: {
name: 1,
language: 1,
season: {
$filter: {
input: "$seasons",
as: "s",
cond: {
$eq: ["$$s.number", saison]
}
}
}
}
},
{
$project: {
'season.episodes.number': 1
}
}
])
and if you want to return only a single number without the complete array structure:
db.collectionName.aggregate([
{
$project: {
name: 1,
language: 1,
season: {
$filter: {
input: "$seasons",
as: "s",
cond: {
$eq: ["$$s.number", 1]
}
}
}
}
},
{ $unwind: "$season"},
{ $unwind: "$season.episodes"},
{
$project: {
seasonEpisodeNumber: '$season.episodes.number'
}
}
])
Unrecognized expression '$project'
Your second $project was inside your first $project that's why the error message. Each pipeline is one execution, so you can't have nested pipelines. If you need to have two projects back to back then you do it like that example:
{ $project: {...}}, { $project: {...}}
and the field number is inside the array episodes which is in the object field seasons, so you were missing that.
Also in your $filter there is no need to create a double array. You do this:
season: [{
$filter: {
input: "$seasons",
as: "s",
cond: { $eq: [ "$$s.number", saison ] }
} etc...
that creates an array inside an array. Unless you expect the result in that form, there is no reason to do that. In my answer I removed the nested array creation.

Resources