Query to show json responses which are public true inside mongoose aggregate - javascript

I have query like this, in which I try to find average of all ratings linked to specific entity. And then return avg rating as an additional field to entity model. Now I want to filter out only those responses in which public field is set to be true.
This is how my query looks like:-
try {
const reviews = await Entity.aggregate([
{
$lookup: {
from: 'reviews',
localField: '_id',
foreignField: 'entityId',
as: 'avgRating',
},
},
{
$addFields: {
avgRating: {
$avg: {
$map: {
input: '$avgRating',
in: '$$this.rating',
},
},
},
},
},
{
$project: {
admin: 0,
createdAt: 0,
updatedAt: 0,
},
},
]);
res.send(reviews);
} catch (e) {
res.status(500).send();
}
the query works fine and gives the following response
{
{...},
{
"_id": "182ehc02031nd013810wd",
"public": false,
"organizations": [
"icnq03d0-2qidc-cq2c"
],
"cities": [
"1234"
],
"name": "test 3",
"__v": 0,
"avgRating": 5
},
{...},
}
I want to add another condition that it should return only those responses in which public is set to true.
I tried to use $filterbut did not work.
How to do this?

public is a document-level field so you need $match instead of $filter:
{ $match: { public: true } }
Mongo Playground
You can also simplify the way you calculate the average:
{
$addFields: {
avgRating: { $avg: 'avgRating.rating' }
}
}
should work

Related

How can I count all category under productId?

So I'm still new using MongoDB, so what I'm trying to do here is count all category under productId who have same category. So the expected output should be 7. I used populate first but got stuck on how can I use the $count. Instead I use aggregate and then use $lookup, but i only empty array of product
CartSchema.js
const CartSchema = new mongoose.Schema({
productId: {type: mongoose.Schema.Types.ObjectId, ref: 'Product'}
})
export default mongoose.model('Cart', CartSchema)
ProductSchema.js
const ProductSchema = new mongoose.Schema({
category: {type: String, required: true},
})
export default mongoose.model('Product', ProductSchema)
I used this code to show the information under productId.
router.get('/categories', async (req, res) => {
try {
const cart = await Cart.find()
.populate([
{path: 'productId', select: 'category' },
]).exec()
res.status(200).json(cart);
} catch (error) {
res.status(500).json({error: error.message})
}
})
The result of populate method.
[
{
"_id": "63b410fdde61a124ffd95a51",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
},
{
"_id": "63b41a679950cb7c5293bf12",
"productId": {
"_id": "63b41637e3957a541eb59e81",
"category": "CASE"
},
},
{
"_id": "63b433ef226742ae6b30b991",
"productId": {
"_id": "63b41637e3957a541eb59e81",
"category": "CASE"
},
},
{
"_id": "63b670dc62b0f91ee4f8fbd9",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
},
{
"_id": "63b6710b62b0f91ee4f8fc13",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
},
{
"_id": "63b671bc62b0f91ee4f8fc49",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
},
{
"_id": "63b6721c62b0f91ee4f8fcc5",
"productId": {
"_id": "63b410d6de61a124ffd9585b",
"category": "CASE"
},
]
So I used this method, but instead, I just get an empty array
router.get('/categories', async (req, res) => {
try {
const cart = await Cart.aggregate([
{
$lookup: {
from: 'product',
localField: 'productId',
foreignField: '_id',
as: 'product'
}
},
{
$unwind: "$product"
},
{
$group: {
_id: "$product.category",
total: {
$sum: 1
}
}
},
{
$sort: {total: -1}
},
{
$project: {
_id: 0,
category: "$_id",
total: 1
}
},
])
res.status(200).json(cart);
} catch (error) {
res.status(500).json({error: error.message})
}
})
In the aggregation, the collection to perform the $lookup on should be products (with an s) rather than product.
The name of the collection that Mongoose creates in your database is the same as the name of your model, except lowercase and pluralized, as documented in the documentation.
Mongoose automatically looks for the plural, lowercased version of your model name. Thus, for the example above, the model Tank is for the tanks collection in the database.
(emphasis theirs)
When using the aggregation framework, your aggregation pipeline is sent to the database as-is. Mongoose doesn't do any sort of coercion or casting on it. So when writing aggregation pipelines you should more or less forget you're using Mongoose. What's important is the name of the underlying collection in Mongo, which is generated from your model name based on the mentioned rule.
You can also override the collection name yourself if desired, for example:
export default mongoose.model('Product', ProductSchema, 'xyz');
This will override Mongoose's default naming behavior and will name the collection xyz.

$divide is mongoose is giving "Cast to Number failed for value"

Here i am trying to update the 'rating' field of my document by taking average of previously existing value of the rating field and newly sent rating value.
this is my rating field specification in the model
rating: {
type: Number,
min: 0,
max: 5,
required: true,
},
this is my request body and controller function
const { newRating, bookID, userName, comment } = req.body;
const updateRating = await Book.findByIdAndUpdate(
{ _id: bookID },
{
rating: { $divide: [{ $inc: { rating: Number(newRating) } }, 2] },
$inc: { numOfRatings: 1 },
},
{ new: true }
);
and i am using postman to send client side data
here for example the rating field has previously set value of 4.1 and i am sending 5 as new rating in req.body then i want the rating field to have an updated value of 4.55 ((4.1+5)/2)
and this is the output i am getting in postman
{
"message": "Cast to Number failed for value "{ '$divide': [ { '$inc': [Object] }, 2 ] }" (type Object) at path "rating"",
"stack": "CastError: Cast to Number failed for value "{ '$divide': [ { '$inc': [Object] }, 2 ] }" (type Object) at path "rating"\n at model.Query.exec (D:\Programs\VS Code\Web Development\lmsbackend\node_modules\mongoose\lib\query.js:4891:21)\n at model.Query.Query.then (D:\Programs\VS Code\Web Development\lmsbackend\node_modules\mongoose\lib\query.js:4990:15)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)"
}
i tried few things seeing mongodb solutions but it is not working out for me. Thank you in advance.
I hope this will work:
const updateRating = await Book.findByIdAndUpdate(
{ _id: bookID },
[{
"$set": {
"rating": { "$divide": [{ "$sum": ["$rating", 5] }, 2] },
"numOfRatings": { "$sum": ["$numOfRatings", 1 ] }
}
}],
{ new: true }
);
$divide is only available for Aggregation framework, so you need to change your update (second) input like this:
await Book.findByIdAndUpdate({
_id: bookID
},
[
{
"$set": {
"rating": { "$divide": [{ "$sum": ["$rating", Number(newRating)] }, 2] },
"numOfRatings": { "$sum": ["$numOfRatings", 1 ] }
}
}
])
Working example

How to get object value with dynamic key in $project in mongoDB

I want to retrieve a value from an object with a dynamic key
[
{
"_id": 1,
"item": "sweatshirt",
"price": {
"INR": 45.99
},
"currency": 'INR'
}
]
db.collection.aggregate([
{
"$project": {
"pricenew": "$price.currency"
}
}
])
If I do price.INR it will work fine but here I have currency dynamic, so I want something like price.currency but here currency is coming like "INR" and it gives no data.
I really appreciate any help you can provide.
You need to convert the price object to an array using $objectToArray, filter it and then convert it back, like so:
db.collection.aggregate([
{
$replaceRoot: {
newRoot: {
"$mergeObjects": [
{
"$arrayToObject": {
$map: {
input: {
$filter: {
input: {
"$objectToArray": "$price"
},
cond: {
$eq: [
"$$this.k",
"$currency"
]
}
}
},
in: {
k: "pricenew",
v: "$$this.v"
}
}
}
},
{
_id: "$_id"
}
]
}
}
}
])
Mongo Playground

Retrieving a relationship field in mongodb aggregation

I am using mongodb aggregation with a collection named files
that has a relationship with another collection named file_upload.
files = {
type: String,
media: { type: Schema.Types.ObjectId, ref: 'file_upload', required: true },
}
file_upoad = {
name: String,
}
This is the query
const data = await strapi.query('files').model.aggregate([
{
$lookup: {
from: "analytics",
localField: "_id",
foreignField: "file_id",
as: "hits",
}
},
{ $unwind: '$hits' },
{ $group: { _id: "$_id", hitsCount: { $sum: 1 } } },
{ $sort: { hitsCount: -1 } },
{ $limit: 1 },
])
my goal is to retrieve the media as part of the result since it is a relationship field, at the moment I get this
[
{
"_id": "61fd74367b6ee77b89bae34d",
"hitsCount": 12
},
{
"_id": "61fd74367b6ee77b89sddfee",
"hitsCount": 8
}
]
expected result
[
{
"_id": "61fd74367b6ee77b89bae34d",
"hitsCount": 12,
"media": {
name:"name1"
}
},
{
"_id": "61fd74367b6ee77b89sddfee",
"hitsCount": 8,
"media": {
name:"name2"
}
}
]

Destructure arrays within the MongoDB aggregation pipeline

I was wondering if it was possible to destructure arrays while I am still in the MongoDB aggregation pipeline which would make my code alot neater.
For example, I have the following aggregation pipeline.
await User.aggregate([
{ $match: { _id: userID } },
{
$project: { chatLogs: 1, username: 1, profilePicURL: 1 },
},
{ $unwind: "$chatLogs" },
{
$lookup: {
from: "users",
let: { recipientID: "$chatLogs.recipientID" },
pipeline: [
{
$match: { $expr: { $eq: ["$_id", "$$recipientID"] } },
},
{ $project: { profilePicURL: 1 } },
],
as: "chatLogs.recipientID",
},
},
]);
This gives the following results when queried:
{
"_id": "5f2ffb54eea9c2180a732afa",
"username": "joe",
"profilePicURL": "/images/profile/default_profile.png",
"chatLogs": {
"recipientID": [
{
"_id": "5f2faf5ad18a76073729f475",
"profilePicURL": "/images/profile/default_profile.png"
}
],
"chat": "5f30b6c3d117441c2abda1ba"
}
}
In my case, because "recipientID" represents a default MongoDB id, it will always be unique. Hence I would prefer the following, where the resulting recipientID field is no longer a meaningless array
Desired results:
{
"_id": "5f2ffb54eea9c2180a732afa",
"username": "joe",
"profilePicURL": "/images/profile/default_profile.png",
"chatLogs": {
"recipientID": {
"_id": "5f2faf5ad18a76073729f475",
"profilePicURL": "/images/profile/default_profile.png"
}
"chat": "5f30b6c3d117441c2abda1ba"
}
}
You can deconstruct recipientID array using $unwind in last pipeline,
await User.aggregate([
... // your all pipelines
// add this line
{ $unwind: "$chatLogs.recipientID" }
]);

Categories

Resources