I using Express in Mongoose,
Result would be like this,,
Consider initial count is 0
var arr = [0,0,0,1,1,1,2,3,4,5,6,7,8,9]
{
_id: '1234'
inventory: [
{
itemIndex: 0,
count: 3 // increate 3 times, since it included upper array
},
{
itemIndex: 1,
count: 3 // Increase 3 times this, since it included upper array
},
{
itemIndex: 5,
count: 1 // Increase 1 , since it included upper array
},
{
itemIndex: 10,
count: 0
},
{
itemIndex: 11,
count: 0
},
]
}
** Result would be
enter code here
I tried this code, but I don't know what should I do.
var filter = { _id: id }
await User.updateOne(filter, {
$inc: {
clubInventories: {
itemIndex: ...... ??
}
}
},
Should I use updateMany instead of updateOne?
Please Help.
You can use arrayFitlers,
elem is a user defined identifier, you can put condition on array field and use that identifier in update part using $[<identifier>] positional operator.
var filter = { _id: id };
var arr = [0,1,2,3,4,5,6,7,8,9];
await User.updateOne(filter,
{
$inc: { "inventory.$[elem].count": 1 }
},
{
arrayFilters: [
{ "elem.itemIndex": { $in: arr } }
]
}
)
To update different count value in different index then use,
var filter = { _id: id };
var arr = [1,2,3,4,5,6,7,8,9];
var arr1 = [0];
await User.updateOne(filter,
{
$inc: {
"inventory.$[elem].count": 1,
"inventory.$[elem1].count": 5
}
},
{
arrayFilters: [
{ "elem.itemIndex": { $in: arr } },
{ "elem1.itemIndex": { $in: arr1 } }
]
}
)
Related
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
I have a Model for all requests that I get for my business (real estate).
I want to create an aggregation middleware based on the "createdAt" field which returns only models created between monday at midnight and the present time.
I have looked through all previous questions but could not find anything and the aggregation docs in mongo are so huge I cannot wrap my head around!
Do you have any tips?
I have tried this but it's returning an empty array:
getGestionaleStats = async (req, res) => {
try {
const richieste = await Richiesta.aggregate([
{
$match: {
createdAt: { $lt: new Date() }
}
},
{
$group: {
_id: { $week: '$createdAt' }
}
}
]);
res.status(200).json({
status: 'success',
data: { richieste }
});
} catch (err) {
console.error(err.message);
res.status(500).json({
status: 'error',
data: err.message
});
}
Of course the {$lt: new Date()} is probably where the problem lies.
Any suggestions?
This solution uses Aggregation query and a custom JavaScript function. The function takes a date object and returns the first Monday's date before today. This is used to get all the documents with createdAt date after the calculated date.
// Function returns the date of the "last Monday" from
// the given input date.
function getLastMonday(dt) {
let n = null; // last Monday conversion
switch (dt.getDay()) {
case 0: n = -5; break;
case 1: n = -6; break;
case 2: n = 0; break;
case 3: n = -1; break;
case 4: n = -2; break;
case 5: n = -3; break;
case 6: n = -4; break;
default: "This never happens";
}
let today_date = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
let last_monday_date = today_date.setDate(today_date.getDate() + n );
return last_monday_date;
}
var d = ISODate(); // -or- any date like ISODate("2019-11-26T00:00:00Z")
var LAST_MONDAY = getLastMonday(d);
db.test.aggregate( [
{
$addFields: {
last_monday: {
$dateFromParts : {
year: { $year: new Date(LAST_MONDAY) },
month: { $month: new Date(LAST_MONDAY) },
day: { $dayOfMonth: new Date(LAST_MONDAY) }
}
},
created_at: {
$dateFromParts : {
year: { $year: "$createdAt" },
month: { $month: "$createdAt" },
day: { $dayOfMonth: "$createdAt" }
}
}
}
},
{
$match: { $expr: { $gt: [ "$created_at", "$last_monday" ] } }
},
{
$project: { created_at: 0, last_monday: 0 }
}
] )
For a set of input documents like this:
{ _id : 1, createdAt : ISODate("2019-12-03T00:00:00Z") }
{ _id : 2, createdAt : ISODate("2019-11-12T02:00:00Z") }
{ _id : 3, createdAt : ISODate("2019-11-25T05:00:00Z") }
{ _id : 4, createdAt : ISODate("2019-11-26T00:00:00Z") }
{ _id : 9, createdAt : ISODate("2019-12-02T23:45:00Z") }
And, LAST_MONDAY = getLastMonday(ISODate("2019-12-04T05:40:20Z")), the aggregation query returns the document with _id : 1.
I'm using momentJS for this:
const result = Collection.aggregate([
{
$match: {
createdAt: {
$gte: moment().startOf('isoweek').toDate(),
$lt: moment().endOf('isoweek').toDate()
},
}
}
]);
I've found an answer with vanillaJS:
const richieste = await Richiesta.aggregate([
{
$match: {
createdAt: { $gte: getBeginningOfTheWeek(new Date()), $lt: new Date() }
}
},
{
$group: {
_id: null,
count: { $sum: 1 }
}
},
]}
where getBeginningOfTheWeek is as such:
exports.getBeginningOfTheWeek = (now) => {
const days = (now.getDay() + 7 - 1) % 7;
now.setDate(now.getDate() - days);
now.setHours(0, 0, 0, 0);
return now;
};
The latter function is from T.J. Crowder: get current week moday javascript
I have this query that works, but I want for the doc to only display network.stations.$ instead of the entire array. If I write fields: network.stations.$, I get an error. Is there a way for the doc only to return a single element from [stations]?
Network.findOneAndUpdate({
"network.stations.id": req.params.station_Id
}, {
"network.stations.$.free_bikes": req.body.free_bikes
}, {
new: true,
fields: "network.stations"
}, (err, doc) => console.log(doc))
// I want doc to somehow point only to a single station instead of
// several stations like it currently does.
The answer is "yes", but not in the way you are expecting. As you note in the question, putting network.stations.$ in the "fields" option to positionally return the "modified" document throws a specific error:
"cannot use a positional projection and return the new document"
This however should be the "hint", because you don't really "need" the "new document" when you know what the value was you are modifying. The simple case then is to not return the "new" document, but instead return it's "found state" which was "before the atomic modification" and simply make the same modification to the returned data as you asked to apply in the statement.
As a small contained demo:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({},{ strict: false });
const Test = mongoose.model('Test', testSchema, 'collection');
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
await Test.remove();
await Test.insertMany([{ a: [{ b: 1 }, { b: 2 }] }]);
for ( let i of [1,2] ) {
let result = await Test.findOneAndUpdate(
{ "a.b": { "$gte": 2 } },
{ "$inc": { "a.$.b": 1 } },
{ "fields": { "a.$": 1 } }
).lean();
console.log('returned');
log(result);
result.a[0].b = result.a[0].b + 1;
console.log('modified');
log(result);
}
} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}
})();
Which produces:
Mongoose: collection.remove({}, {})
Mongoose: collection.insertMany([ { __v: 0, a: [ { b: 1 }, { b: 2 } ], _id: 59af214b6fb3533d274928c9 } ])
Mongoose: collection.findAndModify({ 'a.b': { '$gte': 2 } }, [], { '$inc': { 'a.$.b': 1 } }, { new: false, upsert: false, fields: { 'a.$': 1 } })
returned
{
"_id": "59af214b6fb3533d274928c9",
"a": [
{
"b": 2
}
]
}
modified
{
"_id": "59af214b6fb3533d274928c9",
"a": [
{
"b": 3
}
]
}
Mongoose: collection.findAndModify({ 'a.b': { '$gte': 2 } }, [], { '$inc': { 'a.$.b': 1 } }, { new: false, upsert: false, fields: { 'a.$': 1 } })
returned
{
"_id": "59af214b6fb3533d274928c9",
"a": [
{
"b": 3
}
]
}
modified
{
"_id": "59af214b6fb3533d274928c9",
"a": [
{
"b": 4
}
]
}
So I'm doing the modifications in a loop so you can see that the update is actually applied on the server as the next iteration increments the already incremented value.
Merely by omitting the "new" option, what you get is the document in the state which it was "matched" and it then is perfectly valid to return that document state before modification. The modification still happens.
All you need to do here is in turn make the same modification in code. Adding .lean() makes this simple, and again it's perfectly valid since you "know what you asked the server to do".
This is better than a separate query because "separately" the document can be modified by a different update in between your modification and the query to return just a projected matched field.
And it's better than returning "all" the elements and filtering later, because the potential could be a "very large array" when all you really want is the "matched element". Which of course this actually does.
Try changing fields to projection and then use the network.stations.$ like you tried before.
If your query is otherwise working then that might be enough. If it's still not working you can try changing the second argument to explicitly $set.
Network.findOneAndUpdate({
"network.stations.id": req.params.station_Id
}, {
"$set": {
"network.stations.$.free_bikes": req.body.free_bikes
}
}, {
new: true,
projection: "network.stations.$"
}, (err, doc) => console.log(doc))
How do I update/set the quiz_score array by using qnNumber as index in mongodb?
query
let modifier = {}
modifier["course_learn_list.$.quiz_score." + qnNumber] = selectedAnsNum
Meteor.users.update({
_id: Meteor.userId(),
"course_learn_list.course_id": courseId
}, {
$set: {
modifier
}
})
mongo data
{
"_id": "WsCS7CpeRymtjEjRi",
"course_learn_list": [
{
"course_id": "JPaW6YGGoe8xiCjpJ",
"class_id": "PbRHjWpGsvuQWogJF",
"quiz_score": [
-1,
-1,
-1,
-1
]
}
]
}
let modifier = {}
modifier["course_learn_list.$.quiz_score." + qnNumber] = selectedAnsNum
Meteor.users.update({
_id: Meteor.userId(),
"course_learn_list.course_id": courseId
}, {
$set: modifier
})
I have to different collections Characters, and Souls, that share a lot of the same attributes and used within the same context. This means that every time I want to read/write to these collections, Ill have to do a "type check", and then duplicate the code twice as seen below. Is there a way to accomplish
Polymorphic.update()..
instead of
(Pseudocode)
if target.is(Character)
same logic..
Character.update(same query/fields)..
else
same logic..
Soul.update(same query/fields)..
Complete code below
#
# Adds a new condition instance to target
#
addCondition: (effect, target) ->
name = effect.name
maxDuration = effect.maxDuration
curDuration = if effect.curDuration then effect.curDuration else maxDuration
maxStack = effect.maxStack
curStack = if effect.curStack then effect.curStack else 1
stackable = if maxStack then true else false
if target.location <--- This is my type check, only the Character collection has a location field
character = Characters.findOne({_id: target._id, 'conditions.name': name}, { fields: {'conditions.$': 1} })
if character then existingCondition = character.conditions[0]
if existingCondition and stackable
# Apply additional stack and refresh duration
if existingCondition.curStack < existingCondition.maxStack
Characters.update({ _id: target._id, 'conditions.name': name }, { $inc: { 'conditions.$.curStack': 1 }, $set: { 'conditions.$.curDuration': maxDuration } })
else
Characters.update({ _id: target._id, 'conditions.name': name }, { $set: { 'conditions.$.curDuration': maxDuration } })
else if existingCondition and !stackable
Characters.update({ _id: target._id, 'conditions.name': name }, { $set: { 'conditions.$.curDuration': maxDuration } })
else
effect = _.extend(effect, {curDuration: curDuration, curStack: curStack})
Characters.update(_id: target._id, {$addToSet: { conditions: effect }})
else
soul = Souls.findOne({_id: target._id, 'conditions.name': name}, { fields: {'conditions.$': 1} })
if soul then existingCondition = soul.conditions[0]
if existingCondition and stackable
# Apply additional stack and refresh duration
if existingCondition.curStack < existingCondition.maxStack
Souls.update({ _id: target._id, 'conditions.name': name }, { $inc: { 'conditions.$.curStack': 1 }, $set: { 'conditions.$.curDuration': maxDuration } })
else
Souls.update({ _id: target._id, 'conditions.name': name }, { $set: { 'conditions.$.curDuration': maxDuration } })
else if existingCondition and !stackable
Souls.update({ _id: target._id, 'conditions.name': name }, { $set: { 'conditions.$.curDuration': maxDuration } })
else
effect = _.extend(effect, {curDuration: curDuration, curStack: curStack})
Souls.update(_id: target._id, {$addToSet: { conditions: effect }})
Just add a type (/class/collection) field to your doc:
Character.prototype.type = ->
Character
Soul.prototype.type = ->
Soul
...
target.type.update ...