I am trying to conditionally execute two different mongoose operators but it just return no error and doesn't work to update the document.
My Schema:
const CartSchema: Schema<Document<ICart>> = new Schema({
clientID: { type: String, required: true },
sourceID: { type: String, required: true },
items: { type: Array },
source: { type: String, required: true },
}, { collection: "carts", timestamps: true });
The way I am trying to implement that:
await CartModel.findOneAndUpdate(
{ sourceID: clientID, 'items.id': Types.ObjectId(itemID) },
{
$cond: {
if: {
$eq: 1,
},
then: { $pull: { 'items.$.id': Types.ObjectId(itemID) }},
else: { $inc: { 'items.$.amount': -1 }},
}
},
{ new: true }
).lean({ virtuals: true })
And I also tried to have this kind of query: { sourceID: clientID } but it didn't help. I thought maybe I could not find the element and it just silently pass through.
The main idea here of what I am gonna do is - have a conditional mongoose request where I'll either remove the object from the array if the current value in the field amount is equal to 1, or decrement the value to -1.
Apparently, the $cond operator works only in the aggregation framework but in my Atlas tier I cannot check if the query works properly, but I suppose it should look something like this:
await CartModel.aggregate([
{ $match: {
sourceID: clientID,
'items.id': Types.ObjectId(itemID)
}
},
{
$cond: {
if: {
$eq: 1,
},
then: { $pull: { 'items.$.id': Types.ObjectId(itemID) }},
else: { $inc: { 'items.$.amount': -1 }},
}
}
])
Related
I have an aggregate like this :
const files = await File.aggregate([
{
$match: { facilityId: { $in: facilities } }
},
{
$sort: { createdAt: 1 }
},
{
$project: {
file: 0,
}
}
])
And i would like to have each "facility" return only 4 files, i used to do something like facilities.map(id => query(id)) but id like to speed things up in production env.
Using $limit will limit the whole query, that's not what i want, i tried using $slice in the projection stage but got en error :
MongoError: Bad projection specification, cannot include fields or add computed fields during an exclusion projection
how can i achieve that in a single query ?
Schema of the collections is :
const FileStorageSchema = new Schema({
name: { type: String, required: true },
userId: { type: String },
facilityId: { type: String },
patientId: { type: String },
type: { type: String },
accessed: { type: Boolean, default: false, required: true },
file: {
type: String, //
required: true,
set: AES.encrypt,
get: AES.decrypt
},
sent: { type: Boolean, default: false, required: true },
},
{
timestamps: true,
toObject: { getters: true },
toJSON: { getters: true }
})
And i would like to returns all fields except for the file fields that contains the encrypted blob encoded as base64.
Also: i have the feeling that my approach is not correct, what i really would like to get is being able to query all facilityId at once but limited to the 4 latest file created for each facility, i though using an aggregate would help me achieve this but im starting to think it's not how its done.
From the question the schema is not clear. So I have two answers based on two Schemas. Please use what works for you
#1
db.collection.aggregate([
{
$match: {
facilityId: {
$in: [
1,
2
]
}
}
},
{
$group: {
_id: "$facilityId",
files: {
$push: "$file"
}
}
},
{
$project: {
files: {
$slice: [
"$files",
0,
4
],
}
}
}
])
Test Here
#2
db.collection.aggregate([
{
$match: {
facilityId: {
$in: [
1,
2
]
}
}
},
{
$project: {
facilityId: 1,
file: {
$slice: [
"$file",
4
]
}
}
}
])
Test Here
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.
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,
})
var PostSchema = new mongoose.Schema({
item: {
type: mongoose.Schema.ObjectId,
ref: 'item',
required: true
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
vote: {
type: Number,
default: 0
},
total: {
type: Number,
default: 0
},
awsPostKey: {type: String},
picture: {type: String, required: true}
});
var data = function(){
return Post
.find({})
.then(function(post){
return post;
})
};
var userId = //mongo objectId for current user
//postVote schema:
var PostVoteSchema = new mongoose.Schema({
post: {
type: mongoose.Schema.ObjectId,
ref: 'Post',
required: true
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
vote: {
type: Number,
default: 0
}
});
//pass data from Post query to PostVote sum function:
PostVoteSchema.statics.sum = function (data, userId) {
var postIds = data.map(function (a) {
return a._id;
});
return PostVote
.aggregate(
[
{ $match: { 'post': {$in: postIds}}},
{ $group: { _id:'$post' ,vote:{$sum:'$vote'}}}
])
.execAsync()
.then(function(votes){
return votes;
//desired output to client, _id is for specific post
{_id: 5802ea4bc00cb0beca1972cc, vote: 3, currentUserVote: -1}
});
};
I'm successfully able to get the total sum of all votes with the same postId.
Now, I"m wanting to see if the current user (userId) has placed a vote for the given post as well, then to return how they voted (+1 or -1) along with the sum of all votes for the specific post.
Is it possible to do this, or will I have to do this outside of my aggregate pipeline -- within a second query? It just seems potentially taxing to have to query the collection again.
Yes, that's possible. Within the $group pipeline, you can use the $cond operator as the logic for feeding the $sum accumulator operator. For example:
return PostVote.aggregate([
{ "$match": { "post": { "$in": postIds } } },
{
"$group": {
"_id": "$post",
"votes": { "$sum": "$vote" },
"userVotes": {
"$sum": {
"$cond": [
{ "$eq": ["$user", userId] },
"$vote",
0
]
}
}
}
}
]).execAsync().then(function(votes){
return votes;
});
Schema({
name: String,
items: [{
body: String,
active: Boolean,
_id: mongoose.Schema.Types.ObjectId,
added: { type: Date, default: Date.now }
}],
date: { type: Date, default: Date.now }
});
I want to get the last item in 'items' with a 'name' where 'items.active' is true.
Tried many different configurations.. this is basically what i want to do:
List.find({ name: LIST_NAME, 'items.active': true }, { items: { $slice: -1 } }, function(err, result){...do stuff w/ result.body...});
Appreciate any help, thx.
Using Aggregation was ultimately the solution:
List.aggregate(
{ $match: { name: req.params.list }},
{ $unwind: '$items' },
{ $match: { 'items.active': true }},
{ $sort: { 'items.added': -1 }},
{ $limit: 1 }, function (err, result) {
if(err) {
res.status(500);
res.send(err);
} else {
console.log(result);
res.send(result[0].items.body);
}
});