I'm facing a strang problem while sorting documents in mongoose.
I have a huge scheme named as Property that contain a price object something like this:
{
info: {
price: {
price: 361000 // number
}
}
}
Property Model:
#Field(() => Number, { nullable: true })
#Prop({ type: SchemaTypes.Number })
price: number;
Now, I have to sort the properties via property price i.e info.price.price.
What I have done:
const properties = await this.propertiesModel
.find({})
.skip(skipBy)
.limit(size)
.sort({ "info.price.price": -1 }); // sorting
The results are not as expected:
{
"info": {
"price": {
"price": 999000
}
}
},
{
"info": {
"price": {
"price": 99900 // wrong
}
}
},
{
"info": {
"price": {
"price": 997000
}
}
}
The schema is already using the Number type for price but seems like it's sorting as a string somehow.
Related
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.
I'm struggling with this one for a long time and I don't know how to figure it out.
I have this item that is sent to mutation editItem
{
"data": {
"editItem": {
"id": "62379097ad992c518497f0f0",
"itemDate": "2022-03-24T00:00:00",
"itemName": "laptop",
"itemCategory": {
"categoryName": "laptops",
"categoryType": "electronic"
},
"itemPrice": {
"price": "1000",
"currency": "GBP"
},
}
}
}
And let's say that I want to change itemCategory.categoryType and itemPrice.price, so the action should only update respective two fields.
Code I'm using:
editItem: async (_, args, context) => {
const currentUser = await checkAuthorization(context)
let { itemDate, itemName, itemCategory, itemPrice } =
args.itemInput
if (itemDate) {
itemDate = dayjs(itemDate).format('YYYY-MM-DDTHH:mm:ss')
}
if (itemPrice) {
itemPrice = { ...itemPrice, price: itemPrice.price }
}
if (itemCategory) {
itemCategory = {
...itemCategory,
categoryType: itemCategory.categoryType,
}
}
if (itemDate || itemName || itemCategory || itemPrice) {
const currentDate = dayjs(new Date()).format('YYYY-MM-DDTHH:mm:ss')
const itemBody = await Item.findOneAndUpdate(
{ _id: args.itemID },
{
$set: {
itemDate: itemDate,
itemName: itemName,
itemCategory: {
categoryName: itemCategory.categoryName,
categoryType: itemCategory.categoryType,
},
itemPrice: {
price: itemPrice.price,
currency: itemPrice.currency,
},
},
},
{ upsert: true, new: true },
)
return itemBody
}
},
typeDefs:
type Item {
id: ID!
itemDate: String
itemName: String!
itemCategory: Category
itemPrice: Price!
}
input ItemInput {
itemDate: String
itemName: String
itemCategory: CategoryInput
itemPrice: PriceInput
}
input CategoryInput {
categoryName: String
categoryType: String
}
input PriceInput {
price: String!
currency: String
}
type Mutation {
editItem(itemID: ID!, itemInput: ItemInput): Item!
}
I send this request, however it puts both categoryName and currency to null
{
"itemId": "62379097ad992c518497f0f0",
"itemInput": {
"itemCategory": {
"categoryType": "newType"
},
"itemPrice": {
"price": "500"
}
}
}
{
"data": {
"editItem": {
"id": "62379097ad992c518497f0f0",
"itemDate": "2022-03-24T00:00:00",
"itemName": "laptop",
"itemCategory": {
"categoryName": null,
"categoryType": "newType"
},
"itemPrice": {
"price": "500",
"currency": null
},
}
}
}
Any idea how to update the code in order to keep the fields as they were before? Meaning that categoryName and currency won't be overwritten?
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
I need to count the number of parking spaces _id: 5d752c544f4f1c0f1c93eb23, which has the false value in the excluded property, how can I mount the query?
So far, I've been able to select the number of parking spaces with the excluded: false property. But are selecting from all parking lots
Note that there are two documents symbolizing a parking lot, where each has an array, called parkingSpaces, to record parking space documents.
The first document has 2 vacancies not excluded, so with the property excluded: false and the second has only one vacancy, which is not excluded either.
{
"_id": "5d752c544f4f1c0f1c93eb23",
"name": "estacionamento um",
"parkingSpace": [
{
"_id": "5d752cf54f4f1c0f1c93eb26",
"name": "vg001",
"excluded": true
},
{
"_id": "5d752cf54f4f1c0f1c93eb27",
"name": "vg001",
"excluded": false
},
{
"_id": "5d75339bc411423a9c14ac52",
"name": "vg002",
"excluded": false
}
]
},
{
"_id": "5d7706b60d354b72388a38f4",
"name": "estacionamento dois",
"parkingSpace": [
{
"_id": "5d77078a5173bb63bc87b7ca",
"name": "vg004",
"excluded": false
}
]
}
I need to add the number of parking spaces _id: 5d752c544f4f1c0f1c93eb23, which has the value false in the excluded property.
In the end, I need to return the value 2, referring to _id: 5d752c544f4f1c0f1c93eb23 parking spaces, which have the value false in the excluded property.
So far, with the following query, I was able to select the vacancies with the excluded property with the false value, but it is selecting from all parking lots.
const registeredParkingSpaces = await Parking.aggregate([
{ $unwind: '$parkingSpace' },
{ $match: { 'parkingSpace.excluded': false } },
{
$group: {
_id: parking_id,
total: { $sum: 1 }
}
}
]);
returns:
{
"message": [
{
"_id": "5d752c544f4f1c0f1c93eb23",
"total": 3
}
]
}
But it needs to return:
{
"message": [
{
"_id": "5d752c544f4f1c0f1c93eb23",
"total": 2
}
]
}
In aggregation 1st you match with id then go for the next step you will get your desire count because at this moment you are considering entire document "parkingSpace". Below is the sample code I guess it will work for you
const ObjectId = require('mongoose').Types.ObjectId;
const registeredParkingSpaces = await Parking.aggregate([
{ $match: { _id: objectId(body.id) } }
{ $unwind: '$parkingSpace' },
{ $match: { 'parkingSpace.excluded': false } },
{
$group: {
_id: parking_id,
total: { $sum: 1 }
}
}
]);
This is the query through which you can get the count of total parking .
** UPDATED **
db.collection.aggregate([
{
$match: {
"parkingSpace.excluded": false,
}
},
{
$project: {
parkingSpace: 1
}
},
{
"$group": {
"_id": "$_id",
"count": {
"$sum": {
"$size": {
"$filter": {
"input": "$parkingSpace",
"as": "el",
"cond": {
"$eq": [
"$$el.excluded",
false
]
}
}
}
}
}
}
}
])
You can check the solution from this LINK
For more information about $sum : Visit the official official document
Through this you get your solution .
I'm attempting to perform an aggregate function to calculate the sum of the cost and margin values based on the name value. So if multiple results have the name "Fake Provider' I want the sum of the cost results and the sum of the margin results.
Query statement:
Pharmacy.aggregate([
{
$match: {
$and: [{ 'prescription.status': 'Ready for Pickup' }]
}
},
{
$project: {
'insurance.primary.name': 1,
'prescription.financial.cost': 1,
'prescription.financial.margin': 1
}
}
])
Results are similar to:
[
{
"_id": "5cab98cd293bd54e94c40461",
"insurance": {
"primary": {
"name": "Fake Provider 1"
}
},
"prescription": [
{
"financial": {
"cost": "2.89",
"margin": "5.60"
}
},
{
"financial": {
"cost": "0.88",
"margin": "1.24"
}
}
]
},
{
"_id": "5cab98d0293bd54e94c40470",
"insurance": {
"primary": {
"name": "Fake Provider 1"
}
},
"prescription": [
{
"financial": {
"cost": "3.22",
"margin": "9.94"
}
},
{
"financial": {
"cost": "2.57",
"margin": "9.29"
}
},
{
"financial": {
"cost": "2.03",
"margin": "10.17"
}
}
]
}
]
I have attempted to create a group statement without any luck. Also, the cost and margin values are currently stored as strings.
$group: {
_id: '$insurance.primary.name',
Financial: {
$push: {
id: '$insurance.primary.name',
name: '$insurance.primary.name',
cost: '$prescription.financial.cost',
margin: '$prescription.financial.margin'
}
}
}
I would like to get results similar to:
[
{
"primaryInsurance": "Fake Provider 1",
"totalFinancialCost": "11.59",
"totalFinancialMargin": "36.24"
},
{
"primaryInsurance": "Fake Provider 2",
"totalFinancialCost": "12.82",
"totalFinancialMargin": "22.16"
}
]
I think I have a solution that returns the results using a find and projection then using javascript to map thru the results and perform the addition. However, I would prefer to do this at the Database level.
You must first unwind the 'prescription' field then perform a group. Try this pipeline:
let pipeline = [
{
$unwind: {
path: '$prescription'
}
},
{
$group: {
_id: '$insurance.primary.name',
totalFinancialCost: {
$sum: { $convert: { input: '$prescription.financial.cost', to: "decimal" } }
},
totalFinancialMargin: {
$sum: { $convert: { input: '$prescription.financial.margin', to: "decimal" } }
}
}
}]
Notice how the values are converted to decimal in order to perform the sum.