How to find all matched elements from array of objects in mongoose - mongodb

Schema:
new Schema({
productId: String,
types: [{
productType: String,
lastModified: Date
}]
});
Query:
{
productId: "1",
email: "test#test.com",
productType: "test",
}
I tried this but its returning only first matched element:
const productType = 'test';
const result = await this.model(email)
.find(
{ productId, 'types.productType': productType },
{ 'types.$': productType }
).lean();
with aggregate, it return empty array result:
const result = await this.model(email).aggregate([
{ $match: { productId, 'types.productType': 'productType' } },
{
$project: {
types: {
$filter: {
input: '$types',
as: 'r',
cond: { $eq: ['$$r.productType', productType] }
}
},
_id: 0
}
}
]);
I need to find all matching elements where projection $ returns the first matched

Related

Deleting an item with condition in MongoDB?

I want to remove a product from the Cart by checking its quantity. Its quantity should be decremented by 1 unless it reaches zero, and after that, it should pull out from the product array of the Cart.
here is my Logic : (I want to perform the pull and decrement operation inside the single query. But I m stuck on how to perform these two operations together by a simple condition in MongoDb)
const cart = await Cart.findOneAndUpdate({"products.productId": req.body.productId}, {$inc: {"products.$.quantity": -1}}, {new: true})
await Cart.update({"products.productId": req.body.productId}, {$pull: {quantity: 0}})
here is the model for clarification:
import mongoose from 'mongoose';
const cartSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
},
products: [
{
productId: {
type: String,
},
quantity: {
type: Number,
default: 1
}
}
]
}, {timestamps: true});
const Cart = new mongoose.model('Cart', cartSchema);
export default Cart;
Thanks :)
There is no straight way to do this in single regular update query.
To improve your approach you can try this,
first query to check productId and quantity should greater than 1
const cart = await Cart.updateOne(
{
products: {
$elemMatch: {
productId: req.body.productId,
quantity: { $gt: 1 }
}
}
},
{ $inc: { "products.$.quantity": -1 } }
);
Playground
second query if the first query's result is nModified is 0 then pull the product, by checking condition productId and quantity equal-to 1
if (cart.nModified === 0) {
await Cart.updateOne(
{
products: {
$elemMatch: {
productId: req.body.productId,
quantity: { $eq: 1 }
}
}
},
{ $pull: { products: { productId: req.body.productId } } }
)
}
Playground
If you really want to do using single query you can try update with aggregation pipeline starting from MongoDB 4.2,
$map to iterate loop of products array and check condition, if the productId matches then increment/decrement quantity by $add operator otherwise return current quantity
$filter to iterate loop of above result and check condition if productId and quantity is not zero
await Cart.updateOne(
{ "products.productId": req.body.productId },
[{
$set: {
products: {
$filter: {
input: {
$map: {
input: "$products",
in: {
productId: "$$this.productId",
quantity: {
$cond: [
{ $eq: ["$$this.productId", req.body.productId] },
{ $add: ["$$this.quantity", -1] },
"$$this.quantity"
]
}
}
}
},
cond: {
$and: [
{ $eq: ["$$this.productId", req.body.productId] },
{ $ne: ["$$this.quantity", 0] }
]
}
}
}
}
}
])
Playground

Mongoose find and update nested document

I am trying to find and update a sub document under another sub document. I am not getting the result as I expect. This is what I currently have setup:
const SiteSchema = new mongoose.Schema({
domain: { type: String, required: true },
keywords: [],
campaigns: [
{
campaign: {
type: mongoose.Schema.Types.ObjectId,
ref: "Campaign",
},
responses: [
{
message: { type: String },
asking_fee: { type: Number },
date: { type: Date },
},
],
}],
})
I would like to find and edit a particular response. Here is the code I have now. I am new to mongoose and MongoDB.
const site = await Site.findOneAndUpdate({
"campaigns.responses._id": responseId, // will it fetch the response ?
}, {
$set: { // I am struggling with the following
"campaigns.$.responses.message": message,
"campaigns.$.responses.asking_price": asking_price,
"campaigns.$.responses.date": date,
},
}
);
If you don't have campaigns.campaign id then you have to use update with aggregation pipeline starting from MongoDB 4.2,
$set to update campaigns field, $map to iterate loop of campaigns array, $map to iterate loop of campaigns.responses array and check condition if responseId match then return updateFields otherwise return old fields and merge with current object using $mergeObjects
let responseId = 1;
let updateFields = {
message: "new message",
asking_fee: 10,
date: new Date()
};
const site = await Site.findOneAndUpdate(
{ "campaigns.responses._id": responseId },
[{
$set: {
campaigns: {
$map: {
input: "$campaigns",
in: {
$mergeObjects: [
"$$this",
{
responses: {
$map: {
input: "$$this.responses",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{ $eq: ["$$this._id", responseId] },
updateFields,
"$$this"
]
}
]
}
}
}
}
]
}
}
}
}
}]
)
Playground
Second option if you have campaigns.campaign id then you can use $[<identifier>] arrayFilters,
let campaign = 1;
let responseId = 1;
let updateFields = {
message: "new message",
asking_fee: 10,
date: new Date()
};
const site = await Site.findOneAndUpdate({
"campaigns.campaign": campaign,
"campaigns.responses._id": responseId
},
{
$set: {
"campaigns.$[parent].responses.$[child]": updateFields
}
},
{
arrayFilters: [
{ "child._id": responseId },
{ "parent.campaign": campaign }
]
})
Playground

Problem using aggregation in mongodb retrieving data from two collections

i am strugling with a query that i don't know how to perform... I have two collections,
Tarifas Collection
tarifaConfig = new Schema({
producto: { type: String },
titulo: { type: String },
bloqueo: { type: Boolean },
margen: { type: Number },
precioVenta: { type: Number },
precioVentaIva: { type: Number },
})
const tarifaSchema = new Schema({
codigo: { type: String },
titulo: { type: String },
margen: { type: Number },
estado: { type: Boolean },
bloqueo: { type: Boolean },
configs: [tarifaConfig]
})
Producto Collection
const productosSchema = new Schema({
ref: { type: String },
nombre: { type: String },
precioCompra: { type: Number },
precioCompraIva: { type: Number },
precioVenta: { type: Number },
precioVentaIva: { type: Number },
iva: { type: Number },
})
Now i am using an Aggregation method to retrieve both collection in a response
productosModel.aggregate([
{
$match: { _id: ObjectId(req.params.id) }
},
{
$lookup: {
from: "tarifas",
as: "tarifas",
pipeline: []
}
}
]).then((producto) => {
res.json(producto);
})
This is working and gives me both collections in the response... but..
In tarifa's collection i have a propertie called 'configs' that is an array with lot of sub collections... this sub collections are a config of each product that i have,
So what i need to do is, retrieve all tarifas that has a configs for the product, and if the configs does not contain retrieve the tarifa with a empty array.
Expected result
{
ref: 'rbe34',
nombre: 'bike',
precioCompra: 10,
precioCompraIva: 12.1,
precioVenta: "",
precioVentaIva: "",
iva: 21,
tarifas:[
{
codigo: 'NOR',
titulo: 'Normal tarifa',
margen: 33,
estado: true,
bloqueo: true,
configs: [], ///HERE I NEED A EMPTY ARRAY IF THERE IS NOT ANY CONFIG THAT MATCH WITH THE PRODUCT ID,
}
]
}
i tried to add $match in my aggregation pipeline.
productosModel.aggregate([
{
$match: { _id: ObjectId(req.params.id) }
},
{
$lookup: {
from: "tarifas",
as: "tarifas",
pipeline: [
{ $match: { 'configs.producto': req.params.id } }
]
}
}
])
But if there is not any config that match the product it doesn't retrieve the rest of Tarifa's collection
It seems you are trying to $filter the array after you retrieve it.
This pipeline will return only the configs for which the producto field from the config matches the ref field from the product.
[
{
$match: { _id: ObjectId(req.params.id) }
},
{
$lookup: {
from: "tarifas",
as: "tarifas",
pipeline: [
{
$addFields: {
"tarifas.configs":{ $filter:{
input: "$tarifas.configs",
cond: {$eq:["$$this.producto","$ref"]}
} }
}
}
]
}
},
]
Change the fields in the $eq array to the ones you need to match.

Mongoose: Filter doc and manipulate nested array

I have an image schema that has a reference to a category schema and a nested array that contains an object with two fields (user, createdAt)
I am trying to query the schema by a category and add two custom fields to each image in my query.
Here is the solution with virtual fields:
totalLikes: Count of all nested attributes
schema.virtual("totalLikes").get(function() {
return this.likes.length;
});
canLike: Check if user with id "5c8f9e676ed4356b1de3eaa1" is included in the nested array. If user is included it should return false otherwise true
schema.virtual("canLike").get(function() {
return !this.likes.find(like => {
return like.user === "5c8f9e676ed4356b1de3eaa1";
});
});
In sql it would be a simple SUBQUERY but I can't get it working in Mongoose.
Schema:
import mongoose from "mongoose";
const model = new mongoose.Schema(
{
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category"
},
likes: [{
user: {
type: String,
required: true
},
createdAt: {
type: Date,
required: true
}
}]
})
here is a sample document:
[{
category:5c90a0777952597cda9e9c8d,
likes: [
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1de3eaa1",
createdAt:"2019-03-19T08:13:59.250+00:00"
}
]
},
{
category:5c90a0777952597cda9e9c8d,
likes: [
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1dw223332",
createdAt:"2019-03-19T08:13:59.250+00:00"
},
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1d8498933",
createdAt:"2019-03-19T08:13:59.250+00:00"
}
]
}]
Here is how it should look like:
[{
category:5c90a0777952597cda9e9c8d,
likes: [
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1de3eaa1",
createdAt:"2019-03-19T08:13:59.250+00:00"
}
],
totalLikes: 1,
canLike: false
},
{
category:5c90a0777952597cda9e9c8d,
likes: [
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1dw223332",
createdAt:"2019-03-19T08:13:59.250+00:00"
},
{
_id: "5c90a4c79906507dac54e764",
user: "5c8f9e676ed4356b1d8498933",
createdAt:"2019-03-19T08:13:59.250+00:00"
}
],
totalLikes: 2,
canLike: true
}]
Here is what I tried:
Resolver:
1) Tried in Mongoose call - Fails
const resources = await model.aggregate([
{ $match: {category: "5c90a0777952597cda9e9c8d"},
$addFields: {
totalLikes: {
$size: {
$filter: {
input: "$likes",
as: "el",
cond: "$$el.user"
}
}
}
},
$addFields: {
canLike: {
$match: {
'likes.user':"5c8f9e676ed4356b1de3eaa1"
}
}
}
}
])
2) Tried to change it after db call - works but not preferred solution
model.where({ competition: "5c90a0777952597cda9e9c8d" }).exec(function (err, records) {
resources = records.map(resource => {
resource.likes = resource.likes ? resource.likes: []
const included = resource.likes.find(like => {
return like.user === "5c8f9e676ed4356b1de3eaa1";
});
resource.set('totalLikes', resource.likes.length, {strict: false});
resource.set('canLike', !included, {strict: false});
return resource
});
})
Does anyone know how I can do it at runtime? THX
you can achieve it using aggregate
Model.aggregate()
.addFields({ // map likes so that it can result to array of ids
likesMap: {
$map: {
input: "$likes",
as: "like",
in: "$$like.user"
}
}
})
.addFields({ // check if the id is present in likesMap
canLike: {
$cond: [
{
$in: ["5c8f9e676ed4356b1de3eaa1", "$likesMap"]
},
true,
false
]
},
totalLikes: {
$size: "$likes"
}
})
.project({ // remove likesMap
likesMap: 0,
})

How to make a comma separated list of array values?

I have the following documents
User Schema:
var UserSchema = new Schema({
name: String,
email: { type: String, lowercase: true },
offers: [],
anyCountry: {type: Boolean, default: false},
city: String,
});
Tags Schema
var TagSchema = new Schema({
text: String,
dateCreated: { type: Date, default: Date.now}
});
I am aggregating it this way:
User.aggregate(
{$match: {
$or: [
{'isBlocked': false},
{'isBlocked': {$exists: false}}
]}},
{ $project: {"offers": 1, _id: 0, city: 1, name: 1}},
{ $unwind: "$offers" },
{
$match: {
$and: [
{'offers': { $not: { $size: 0} }},
{'offers.type': type}
]
}
},
{ $sort: {"offers.dateCreated": -1} },
function (err, result) {
if (!err) {
return res.json({status: 'success', data: result});
} else {
return res.send(err);
}
}
)
The output is ok, but it contains tags as array. What I need is:
to have array values assigned to a computed field "offers.tagsList" as a coma separated sting {offers.tagsList = 'tag1, tag2, tag3, ...'}.
check if filed offers.anyCountry doesn't exists and add it to the output with value false.
Thanks!