Sort query results based desc value of nested subdocument within array Mongoose/Mongodb - mongodb

My documents look like this:
{
{
mlsId: 'RTC749',
firstName: 'Tommy',
lastName: 'Davidson',
officeMlsId: 'RTC2421',
officeName: 'John Jones Real Estate LLC',
slug: 'tommy-davidson',
serviceAreas: [
{
name: 'Nashville',
slug: 'nashville',
type: 'city',
totalClosedSales: 3
},
{
name: 'Franklin',
slug: 'franklin',
type: 'city',
totalClosedSales: 7
}
},
{
id: 'RTC7280',
firstName: 'Jack',
lastName: 'Miller',
slug: 'jack-miller',
serviceAreas: [
{
name: 'Nashville',
slug: 'nashville',
type: 'city',
totalClosedSales: 4
},
{
name: 'Franklin',
slug: 'franklin',
type: 'city',
totalClosedSales: 10
}
]
},
}
The query to find documents based on slugs within the subdocuments looks like this:
const localAgents = await Agent.find(
{
'serviceAreas.slug': locationSlug,
},
'-_id -__v'
)
.sort({ 'serviceAreas.totalClosedSales': -1 })
Note that I'd like to find agents by location slug and sort the result using totalClosedSales however I'm unable to get it to work. So the desired result would look like this:
{
{
id: 'RTC7280',
firstName: 'Jack',
lastName: 'Miller',
slug: 'jack-miller',
serviceAreas: [
{
name: 'Franklin',
slug: 'franklin',
type: 'city',
totalClosedSales: 10
},
{
name: 'Nashville',
slug: 'nashville',
type: 'city',
totalClosedSales: 4
}
]
},
{
mlsId: 'RTC749',
firstName: 'Tommy',
lastName: 'Davidson',
officeMlsId: 'RTC2421',
officeName: 'John Jones Real Estate LLC',
slug: 'tommy-davidson',
serviceAreas: [
{
name: 'Nashville',
slug: 'nashville',
type: 'city',
totalClosedSales: 3
},
{
name: 'Franklin',
slug: 'franklin',
type: 'city',
totalClosedSales: 7
}
]
},
}

We can't sort array directly, But aggregation helps it
$unwind helps to de-structure the array
$sort helps to sort as you wish
$group helps to re-group the de-structured array
Mongo script is given below
db.collection.aggregate([
{
"$match": {
"serviceAreas.slug": "nashville"
}
},
{
$unwind: "$serviceAreas"
},
{
$sort: {
"serviceAreas.totalClosedSales": -1
}
},
{
$addFields: {
total: "$serviceAreas.totalClosedSales"
}
},
{
$sort: {
total: -1
}
},
{
$group: {
_id: "$_id",
mlsId: {
$first: "$mlsId"
},
firstName: {
$first: "$firstName"
},
lastName: {
$first: "$lastName"
},
slug: {
$first: "$slug"
},
serviceAreas: {
$push: "$serviceAreas"
}
}
}
])
Working Mongo playground

Related

Get extra field in model summing all records from lookup document

Having this model:
const matchSchema = mongoose.Schema({
location: {type: mongoose.Types.ObjectId, ref: 'Location'},
datetime: Date,
teamAName: String,
teamBName: String,
teamA: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
teamB: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
teamAScore: {type: Number, default: 0},
teamBScore: {type: Number, default: 0},
pichichi: [{type: mongoose.Types.ObjectId, ref: 'Player'}],
mvp: {type: mongoose.Types.ObjectId, ref: 'Player', default:null},
});
"teamA" and "teamB" are lists containing the "_id" of every player. When retrieving a player, I want to retrieve the number of matches that he/she have played. How can I do that? Below my query just retrieving fields from model "Player"
class PlayerController {
getAll(req, res) {
Player.find()
.sort('firstname')
.exec(function(err, players) {
res.send(players);
});
}
}
So, instead of just having this list:
[
{
_id: new ObjectId("6232395d08663294b412d6a1"),
firstname: 'Sam',
lastname: 'Credington',
__v: 0
},
{
_id: new ObjectId("622479f39be8118a52af70e5"),
firstname: 'Santi',
lastname: 'Futsal',
__v: 0
},
{
_id: new ObjectId("6232399608663294b412d6b9"),
firstname: 'Tom',
lastname: 'Hendry',
__v: 0
}
]
I would like to have the amount of matches that every player played:
[
{
_id: new ObjectId("6232395d08663294b412d6a1"),
firstname: 'Sam',
lastname: 'Credington',
matches:6,
__v: 0
},
{
_id: new ObjectId("622479f39be8118a52af70e5"),
firstname: 'Santi',
lastname: 'Futsal',
matches:8,
__v: 0
},
{
_id: new ObjectId("6232399608663294b412d6b9"),
firstname: 'Tom',
lastname: 'Hendry',
matches: 2,
__v: 0
}
]
Here's one way you could do it.
db.players.aggregate([
{
"$lookup": {
"from": "matches",
"let": { "myId": "$_id" },
"pipeline": [
{
"$match": {
"$expr": {
"$in": [ "$$myId", { "$setUnion": [ "$teamA", "$teamB" ] } ]
}
}
},
{ "$count": "numMatches" }
],
"as": "matchCount"
}
},
{
"$set": {
"matches": {
"$ifNull": [ { "$first": "$matchCount.numMatches" }, 0 ]
}
}
},
{ "$unset": "matchCount" }
])
Try it on mongoplayground.net.

Mongoose/Mongodb Aggregate - group and average multiple fields

I have a Post model with 2 fields : date and rating. How would I go about getting an average aggregate rating for each date? So group by date first and then average the rating across all posts for that date. I need to do this within mongoose but their docs are so difficult to understand.
const PostSchema = new Schema({
date: {
type: String,
default: getToday() //this is just a new Date() formatted
},
rating: {
type: Number,
required: true
}
},
)
This gives me the average across all dates but I can't figure out how to filter it by date:
Post.aggregate([
{ $group: { _id: null, avgRating: { $avg: '$rating' }}}
])
.then(function (res) {
console.log(res[0]["avgRating"]);
})
This worked for me:
Post.aggregate([
{ $group: { _id: "$date", avgRating: { $avg: '$rating' }}}
]).
then(function (res) {
console.log(res);
})
Output:
[
{ _id: 'Aug 18, 2021', avgRating: 3.0212234706616727 },
{ _id: 'Aug 19, 2021', avgRating: 2.9680319680319682 },
{ _id: 'Aug 20, 2021', avgRating: 3.023976023976024 },
{ _id: 'Aug 17, 2021', avgRating: 2.9600665557404326 },
{ _id: 'Aug 21, 2021', avgRating: 3.072661217075386 }
]
BUT it would be great if I could somehow filter this based on other factors. For example, each post has an author (reference to User model). How would I go about filtering based on the author's country.name or gender?
User model:
const userSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
birthday: {
type: Date,
required: true,
},
gender:{
type: String,
required: true
},
country:{
name: {
type: String,
required: true
},
flag: {
type: String,
// default: "/images/flags/US.png"
}
},
avatar: AvatarSchema,
displayName: String,
bio: String,
coverColor: {
type: String,
default: "#343a40"
},
posts: [
{
type: Schema.Types.ObjectId,
ref: "Post"
}
],
comments: [
{
type: Schema.Types.ObjectId,
ref: "Comment"
}
],
postedToday: {
type: Boolean,
default: false
},
todaysPost: {
type: String
}
})
Something like this
Post.aggregate([
{$match: {"date": today}},
{$group: {_id: {"country": "$author.country.name"}, avgRating: {$avg: "$rating"}}}
]).then(function(res) {
console.log(res)
})

Aggregate string values to array of unique string values per field with mongo or mongoose and node.js

I need to aggregate "lastNames" and "occupations" for a "name" to get a result:
{
name: 'John',
occupations: ['software engineer', 'qa']
lastNames: ['Smith', 'Red', 'Doe']
}
input
name: 'John'
documents present in mongo:
{name: 'John', lastName: 'Smith', occupation: 'software engineer'}
{name: 'Steve', lastName: 'Smith', occupation: 'senior software engineer'}
{name: 'John', lastName: 'Doe', occupation: 'qa'}
{name: 'Steve', lastName: 'Doe', occupation: 'manager'}
{name: 'John', lastName: 'Red', occupation: 'software engineer'}
I started with this aggregation query:
Employees.aggregate([
{ $match: { name: name } },
{
$unwind: {
path: '$lastName',
},
},
{
$unwind: {
path: '$occupation',
},
},
{ $group: { _id: '$name' } },
]);
but this returns an empty array, so I kinda stuck as I never did aggregations before.
Is there a way to produce this required result?
Would be this one:
db.collection.aggregate([
{ $match: { name: "John" } },
{
$group: {
_id: "$name",
occupations: { $addToSet: "$occupation" },
lastNames: { $addToSet: "$lastName" },
}
},
{
$project: {
_id: 0,
name: "$_id",
occupations: 1,
lastNames: 2
}
}
])
Mongo playground

How to remove subdocument (by Id) embedded in sub array in MongoDB?

ProductCollection:
{
_id: { ObjectId('x1')},
products: [
{ listPrice: '1.90', product: {id: 'xxx1'} },
{ listPrice: '3.90', product: {id: 'xxx2'} },
{ listPrice: '5.90', product: {id: 'xxx3'} }
]
},
{
_id: { ObjectId('x2')},
products: [
{ listPrice: '2.90', product: {id: 'xxx4'} },
{ listPrice: '4.90', product: {id: 'xxx5'} },
{ listPrice: '5.90', product: {id: 'xxx6'} }
]
},
I want to remove subdocument (xxx3) from collection (x1), and try below:
ProductCollection.update(
{
"_id": mongoose.Types.ObjectId('x1')
},
{
$pull : { 'products.product': {id: 'xxx3' } }
}
It just doesn't seem to work. Can anyone please help me? Thank you
The field for $pull needs to be the array.
This should work:
$pull: { products: { 'product.id': 'xxx3' } }
add this _id: {id: false} while creating mongoose schema
for eg:
partners:[{
name: { type: String, default: '' },
logo: { type: mongoose.Schema.Types.Mixed, default: '' },
_id: { id: false }
}],

mongo db aggregate keep fields after group

I have this aggregate:
[
{
$match: {_id: new ObjectId('xxx')}
},
{
$unwind: "$users"
},
{
$group: {
_id: '$users.status',
count: {$sum: 1}
}
},
{
$project: {
name: 1,
field1: 1,
field2: 1,
count: '$count'
}
}
]
The schema:
{
name: String,
field1: String,
field2: Schema.Types.Mixed,
users: [{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
status: String
}]
}
The expected result:
{
name: 'NY',
field1: 'example',
field2: 'example2',
statuses: [
{
_id: 'ONLINE',
count: 20
},
{
_id: 'OFFLINE',
count: 120
},
{
_id: 'OTHER',
count: 230
}
]
}
This way I get group result just for the users statuses, and not for the other fields of the original object.