using max query with mongoose - javascript

I am new with mongoose and still trying to understand how make correct queries
I have 2 simple Models
User :
const UserSchema = new Schema({
name: String,
age: Number,
movies:[{
type: Schema.Types.ObjectId,
ref: 'movie'
}]
}, { collection: 'USER_COLLEC' });
Movie :
const MovieSchema = new Schema({
title:String ,
duration: Number
}, { collection: 'MOVIE_COLLEC' });
What I want is the user with le longest movie ( highest duration )
For now I got that :
db.getCollection('USER_COLLEC') .
aggregate([
{ "$unwind": "$movies" } ,
{ $lookup:
{from: "MOVIE_COLLEC",
localField: "movies",
foreignField: "_id",
as: "movieContent"},
} ,
{ $unwind: "$movieContent" },
{ $group:
{ maxDuration: { $max: "$movieContent.duration" },
}
}
])
But it will only find the max duration with no user attached to it...
And indeed I only ask for the max duration on my query, but after the lookup I lose my user :(
How can I can keep it, or retrieve my user data ?
If you have any idea, I am completely stuck...
Thanks guys !

you can use $push to get the movie object as well.
db.getCollection('USER_COLLEC') .
aggregate([
{ "$unwind": "$movies" } ,
{ $lookup:
{from: "MOVIE_COLLEC",
localField: "movies",
foreignField: "_id",
as: "movieContent"},
} ,
{ $unwind: "$movieContent" },
{ $group:
{ _id: { $max: "$movieContent.duration" },
"movie": {
"$push": "movieContent"
}
}
}
])
After this, just get search for the Movie's _id in the user's movies array
UserSchema.find({movies:{$in:movieContent[0]._id}});
OR, instead of $push you can also use $first
{ $first: "$movieContent" }
Then you won't get it in an array.
Update:
Instead of {$push: $movieContent} or{$first: $movieContent}, you could just push $$ROOT:
{$push: $$ROOT} or {$first: $$ROOT}
and then you'll get the entire object. You don't need to fire another query to get the user.

I finally managed to find the solution, the $group was not the solution
db.getCollection('USER_COLLEC') .
aggregate([
{ "$unwind": "$movies" } ,
{ $lookup:
{from: "MOVIE_COLLEC",
localField: "movies",
foreignField: "_id",
as: "movieContent"},
} ,
{ $unwind: "$movieContent" },
{$sort: {"movieContent.duration":-1}},
{ $project: { "user":"$name","duration" : "$movieContent.duration"} } ,
{ $limit : 1 }
Which gives me something like :
{
"_id" : ObjectId("59d2f64dded1c008192f7e73"),
"user" : "Michael",
"duration" : 96
}

Related

How to get expected output from MongoDB?

I'm new to MongoDB aggregation. I am not getting desired output
The output I'm getting from aggregation:-
[
{tweet:{key:value}},
{tweet:{key:value}},
{tweet:{key:value}},
{tweet:{key:value}},
]
but I want the following output from the pipeline:-
[
{key:value},
{key:value},
{key:value},
]
and lastly, pipeline I'm running:-
const pipeline = [[
{
$match: {
$expr: {
$in: [
Mongoose.Types.ObjectId(userid), '$likedBy.user'
]
}
}
}, {
$lookup: {
from: 'tweets',
localField: 'tweet',
foreignField: '_id',
as: 'tweet'
}
}, {
$unwind: {
path: '$tweet'
}
}, {
$lookup: {
from: 'users',
localField: 'tweet.user',
foreignField: '_id',
as: 'user'
}
}, {
$unwind: {
path: '$user'
}
}, {
$addFields: {
'tweet.user': '$user'
}
},
{
$addFields: {
'tweet.isLiked': true,
}
},{
$project:{
tweet:1,
}
},
]
];
const likedTweets = await TweetLike.aggregate(pipeline)
I know I can do this with javascript but I want to do it with the pipeline
You can replace your last project stage with the following to achieve what you need:
{$project:{key:"$tweet.key"}}
Answering my own question
i wanted to return sub-document so i found this https://stackoverflow.com/a/43411988/12332711
all i had to do is use
{
$replaceRoot: {newRoot: "$tweet"}
}
it worked for me

Add count inside MongoDB aggregation [duplicate]

I'm using such aggregation to sort all products by deep nested field ObjectId.
At first I populate catalogProduct field.
Then populate category inside catalogProduct.
Sort all data by category Id (return product if ids arr includes category._id)
Sort in reverse order, returns page and limit by 8 for pagination.
Then getting total count of all sorted products without paginatin and limit.
const sortedProducts = await StorageModel.aggregate([
// Unite products arr and totalCount of sorted products
{$facet: {
"sortedProducts": [
// populate catalogProduct ref by Id
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct" },
// populate category ref by Id inside catalogProduct object
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct.category" },
// returns product, if ids arr includes a catalogProduct.category._id
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// sort in reverse order
{ $sort: { _id: -1 } },
// returns only *page
{ $skip: (page - 1) * 8 },
/// limit result by 8
{ $limit: 8 },
],
// total count for pagination, the same operations
"totalCount": [
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
{ $unwind: "$catalogProduct" },
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
{ $unwind: "$catalogProduct.category" },
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// get total count of sorted data, without limit and pagination
{$count : "totalCount"},
]
}},
]);
products = sortedProducts[0].sortedProducts
totalProducts = sortedProducts[0].totalCount.totalCount
I'm getting such data:
[
{ sortedProducts: [ [Object], [Object] ], totalCount: [ [Object] ] }
]
And It's fine. But I think, that aggregation can be simplified, and i don't need to repeat operations to get total count, but I don't know how.
You can observe the starting stages until $match by catalogProduct.category._id is repeated in the 2 $facet. Therefore, you can simply factor them out, then put the afterwards stages into $facet respectively.
Below is my suggested version of your code:
StorageModel.aggregate([
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct" },
// populate category ref by Id inside catalogProduct object
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct.category" },
// returns product, if ids arr includes a catalogProduct.category._id
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// Unite products arr and totalCount of sorted products
{$facet: {
"sortedProducts": [
// populate catalogProduct ref by Id
// sort in reverse order
{ $sort: { _id: -1 } },
// returns only *page
{ $skip: (page - 1) * 8 },
/// limit result by 8
{ $limit: 8 },
],
// total count for pagination, the same operations
"totalCount": [
// get total count of sorted data, without limit and pagination
{$count : "totalCount"},
]
}},
]);

$addFields $size property always returns zero - mongoose

Hi I have added $addFields property in aggregate query and $size of my documents always return 0. here are my tables and query
table post:
{
_id: 1,
text: 'some text',
}
table comments:
{
_id: 1,
text: 'comment text',
postId: 1
}
In aggregate i have the following
let aggregateDataQuery = [
{
$lookup: {
from: 'comments',
localField: '_id',
foreignField: 'postId',
as: 'numberOfComments',
},
},
{
$addFields: {numberOfComments: { $size: { $ifNull: ['$numberOfComments', []] } }},
},
];
This query always result in numberOfComments: 0. I am sure that there are comments against postId 1 but result is always zero. Any Idea what i'm missing here. thanks
Hi the clause from in $lookup stay of the collection than you want to join.
The starter collection must be posts collection.
So you should put comments instead posts.
The $size attribute not work because the join not join.
The code must be shomething like:
let aggregateDataQuery = [
{
$lookup: {
from: 'comments',
localField: '_id',
foreignField: 'postId',
as: 'numberOfComments',
},
},
{
$addFields: {numberOfComments: { $size: { $ifNull: ['$numberOfComments', []] } }},
},
];
Posts.aggregate(aggregateDataQuery);

Mongoose Model find using an attribute from anohter Schema

Basically I have 2 Schemas.
User and Post.
User have an array which contains _ids from posts.
And post have an attribute that tells if he's an active post. -> is_active.
So, i want to filter User that have at least, one active post.
UserSchema
const UserSchema = new Schema(
{
name: {
type: String,
trim: true,
required: true
},
posts: [
{
type: Schema.Types.ObjectId,
ref: 'Post'
}
],
created_at: {
type: Date,
required: true,
default: Date.now()
}
}
)
export default mongoose.model<User>('User', UserSchema)
Post Schema
const postSchema = new Schema(
{
name: String,
is_active: boolean
}
)
As an alternative to #Tunmee's answer
Since the pipeline $lookup is available from v3.6 and as of v4.2 still has some performance issues. You could also use the "regular" $lookup available from v3.2
db.Users.aggregate([
{
$lookup: {
from: "Posts",
localField: "posts",
foreignField: "_id",
as: "posts"
}
},
{
$match: {
"posts.is_active": true
}
}
])
You can try this:
Users.aggregate([
{
$lookup: {
from: "Posts",
let: { postIds: "$posts", },
pipeline: [
{
$match: {
$expr: {
$and: [
{
$in: [ "$_id", "$$postIds" ]
},
{
$eq: [ "$is_active", true ]
},
]
}
},
},
// You can remove the projection below
// if you need the actual posts data in the final result
{
$project: { _id: 1 }
}
],
as: "posts"
}
},
{
$match: {
$expr: {
$gt: [ { $size: "$posts" }, 0 ]
}
}
}
])
You can test it out in a playground here
I'm not sure about your application's query requirement but you can add a compound index on _id and is_active properties in Posts collection to make the query faster.
You can read more about MongoDB data aggregation here.

How to filter mongo document based on nested object?

How can I find room by ID and make sure that the room has the current player in it?
My mongodb has a document of rooms which has players and a player is a user.
const RoomSchema = new Schema({
players: [{ type: Schema.Types.ObjectId, ref: "Player" }]
})
const PlayerSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: "User" }
})
const UserSchema = new Schema({
username: { type: String}
})
I want to find room where id === roomId and room has a player with user._id === userId
My query so far just finds one room by ID but I want to make sure that the room returned has the current user in as a player
RoomModel
.findOne({_id: roomId})
.populate({
path: 'players',
populate: {
path: 'user',
model: 'User',
select: ["_id", "username"]
}
})
You can use mongodb aggregation framework for this task.
Playground
const result = await RoomModel.aggregate([
{
$match: {
_id: "1", // match by room id
},
},
{
$lookup: {
from: "players", // must be physical collection name, check if different
localField: "players",
foreignField: "_id",
as: "players",
},
},
{
$unwind: "$players",
},
{
$match: {
"players.user": "100", //match by user id
},
},
{
$lookup: {
from: "users",
localField: "players.user",
foreignField: "_id",
as: "user"
}
}
]);
if (result.length > 0) {
console.log("found"); //todo: add your logic when found
} else {
console.log("not found"); //todo: add your logic when not found
}
This will give a result like this when the user found, you may need some transformation.
[
{
"_id": "1",
"players": {
"_id": "10",
"user": "100"
},
"user": [
{
"_id": "100",
"username": "user1"
}
]
}
]

Categories

Resources