Add count inside MongoDB aggregation [duplicate] - javascript

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"},
]
}},
]);

Related

Mongoose aggregate Get count and append new value to the query result

Given that I have this two COLLECTIONS:.
1st.col
users:
{
_id :34,
name :"mama mia"
}
2nd.col
posts:
{
_id :67
body :" hello mongoose"
likes:[ 0: ObjectId("34") ]
}
I wanna get every posts with likes count.
And let suppose if I have a auth user id ready and a want to map through the likes and if the user._id(auth id) is found in the post likes i wanna append a new attribute to the collection result not in the db , i just want to modifies the result i'm gonna get.
To make more since of my question , this is the result expected :
[
{
_id : 67
body : " hello mongoose"
likesCount : 1
liked :true
likes :{
"34":{
name :"mama mia"
}
}
}
]
$lookup to join users collection
$map to iterate loop of likes array and return key-value format result
$arrayToObject to convert key-value array of object to object
$size to get total elements in likes array
$in to check current auth user id in likes array or not
let auth_user_id = ObjectId("34");
db.posts.aggregate([
{
$lookup: {
from: "users",
localField: "likes",
foreignField: "_id",
as: "likes"
}
},
{
$project: {
likes: {
$arrayToObject: {
$map: {
input: "$likes",
in: {
k: { $toString: "$$this._id" },
v: "$$this.name"
}
}
}
},
likesCount: { $size: "$likes" },
liked: { $in: [auth_user_id, "$likes"] },
body: 1
}
}
])
Playground

$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);

MongoDB: Project only the items that was queried for in the array?

I have a user document, each user has an array of objects
Given an array of item tags, I need to find the user whose item array has the item-tag, and return the entire user object except the items array, in which I only want to return the first item tags that existed in the tagArray that was used for the intial query.
//user document
{
user: 'John',
items: [ObjectId('ABC'), ObjectId('123') ...]
}
//item document
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
},
{
_id: ObjectId('DEF'),
tag: 'some-unique-tag'
}
Users have a 1-to-N relationship with items, the items may repeat within the User's items array.
This is what I current have, which returns the entire user object, but also all the items within the array.
const tagArray = [ 'some-unique-id', 'some-unique-tag']
items.aggregate([
{ $match: { 'tag': { $in: tagArray } }},
{ $lookup: {
from: "users",
localField: "tag",
foreignField: '_id',
as: 'userInfo'
}
},
{
$project: {??} //<--- I'm pretty sure I'm missing something in the project
])
Outcome that I have now:
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
userInfo : [ {user: 'John', items: [ObjectId('ABC'), ObjectId('123') ...] }]
}
What I want to achieve:
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
userInfo : [ {user: 'John', items: [ObjectId('ABC')]} ]
}
Edit:
There is a similar question here : Retrieve only the queried element in an object array in MongoDB collection
However in my case, I need the filter condition to be "one of the the tags that is in the tagArray.
Any suggestion or pointers would be appreciated, thank you!
I don't know if I understood well what you need, but I think this is a good start (maybe you can modify it by yourself):
Test data:
// users collection
[
{
user: "John",
items: [
ObjectId("5a934e000102030405000002"),
ObjectId("5a934e000102030405000003")
]
}
]
// items collection
[
{
_id: ObjectId("5a934e000102030405000002"),
tag: "some-unique-id"
},
{
_id: ObjectId("5a934e000102030405000009"),
tag: "some-unique-tag"
}
]
}
Query:
db.users.aggregate([
{
$lookup: {
from: "items",
localField: "items",
foreignField: "_id",
as: "userInfo"
}
},
// create new fields inside the userInfo array
{
$project: {
"userInfo.user": "$user",
"userInfo.items": "$items",
"tag": {
$arrayElemAt: ["$userInfo.tag", 0]
}
}
},
// filter the userInfo.items field, based on _id field
// it's important to use $arrayElemAt here
{
$addFields: {
"userInfo.items": {
$filter: {
input: {
$arrayElemAt: [
"$userInfo.items",
0
]
},
as: "i",
cond: {
$in: [
"$$i",
[
"$_id"
]
]
}
}
}
}
}
])
Result:
[
{
"_id": ObjectId("5a934e000102030405000002"),
"tag": "some-unique-id",
"userInfo": [
{
"items": [
ObjectId("5a934e000102030405000002")
],
"user": "John"
}
]
}
]

MongoDB aggregation - how to add field to every document in array?

Let's say that I have two collections in my database: actor and movie.
Each movie has a title and I want to add this title field to every document in the actors array. I get the actors array by using aggregation pipeline together with $lookup operation.
My attempt was to $map on the actors array and add title field. But it doesn't work, unfortunately:
db.movie
.aggregate([
{
$lookup: {
from: 'actor',
localField: 'id',
foreignField: 'movie',
as: 'actors',
},
},
{
$project: {
actors: {
$map: {
input: '$actors',
as: 'actor',
in: { $set: { title: '$title' } }, // this line doesn't work
},
},
},
},
])

using max query with mongoose

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
}

Categories

Resources