i am trying to carry a simple update query operation with MongoDB from my node js application that will run every night using node-cron but i haven't been able to get the update operation to work
.documents in my db look like
[
{
Balance: 4000,
name: "Steph curry",
password: "*****",
created_At: ISODate("2022-04-19T07:17:29.243Z"),
deposits: [
{
amount: 1000,
paid: false,
expiry: 28903708478, // this should be a timestamp
credit: 150
},
{
amount: 1000,
paid: false,
credit: 100,
expiry: 28903708478 // this should be a timestamp
}
]
}
]
i want to query for all users where their deposit has expired (that is Date.now() > expiry )
and their paid value is false and then add the credit to their balance value, then turn the paid value to true.
/ basically what i want is something like this
db.collection.update({
"deposits.paid": false,
"deposits.expiry": { $lt: "$$NOW" }
},
{
ballance: {
$add: [ "deposits.$.credit", "$balance" ]
},
"deposits.$.paid": true
})
I don't think your expiry is a valid timestamp (28903708478=2885/12/2 Sunday 15:54:38), so convert it yourself.
$map
$add
$cond
$mergeObjects
db.collection.update({
"deposits.paid": false,
"deposits.expiry": { $lt: 28903708479 }
},
[
{
$set: {
Balance: {
$add: [
"$Balance",
{
$sum: {
$map: {
input: "$deposits",
as: "d",
in: {
$cond: {
if: {
$and: [
{ $not: "$$d.paid" },
{ $lt: [ "$d.expiry", 28903708479 ] }
]
},
then: "$$d.credit",
else: 0
}
}
}
}
}
]
}
}
},
{
$set: {
deposits: {
$map: {
input: "$deposits",
as: "d",
in: {
$mergeObjects: [
"$$d",
{
paid: {
$cond: {
if: {
$and: [
{ $not: "$$d.paid" },
{ $lt: [ "$d.expiry", 28903708479 ] }
]
},
then: true,
else: false
}
}
}
]
}
}
}
}
}
],
{
multi: true
})
mongoplayground
Related
await products.updateOne(
{
$and: [
{ name: { $eq: name } },
{ $expr: { $lt: ["$remaining", "$capacity"] } },
],
},
{ $inc: { remaining: 1 } },
{ returnOriginal: false }
);
Instead of having the condition in the query like so { $expr: { $lt: ["$remaining", "$capacity"] } }, is there a way to include this condition in the update argument?
The reason for this is so that I want the returned matchCount to return 1 if the name is matched.
Yes, you can do that if you use mongo 4.2+ using aggregate update.
db.collection.update({
$and: [ //condition goes here
{
name: {
$eq: "name"
}
},
],
},
[
{
"$set": { //conditional update
"remaining": {
"$switch": {
"branches": [
{
case: {
$lt: [ //condition to update
"$remaining",
"$capacity"
]
},
then: {
$add: [ //true case
"$remaining",
1
]
}
}
],
default: {
$add: [ //if no match
"$remaining",
0
]
}
}
}
}
}
])
playground
On my backend I use mongoDB with nodejs and mongoose
I have many records in mongodb with this structure:
{
..fields
type: 'out',
user: 'id1', <--mongodb objectID,
orderPayment: [
{
_id: 'id1',
paid: true,
paymentSum: 40
},
{
_id: 'id2',
paid: true,
paymentSum: 60,
},
{
_id: 'id3',
paid: false,
paymentSum: 50,
}
]
},
{
..fields
type: 'in',
user: 'id1', <--mongodb objectID
orderPayment: [
{
_id: 'id1',
paid: true,
paymentSum: 10
},
{
_id: 'id2',
paid: true,
paymentSum: 10,
},
{
_id: 'id3',
paid: false,
paymentSum: 77,
}
]
}
I need to group this records by 'type' and get sum with conditions.
need to get sum of 'paid' records and sum of noPaid records.
for a better understanding, here is the result Ι need to get
Output is:
{
out { <-- type field
paid: 100, <-- sum of paid
noPaid: 50 <-- sum of noPaid
},
in: { <-- type field
paid: 20, <-- sum of paid
noPaid: 77 <-- sum of noPaid
}
}
Different solution would be this one. It may give better performance than solution of #YuTing:
db.collection.aggregate([
{
$project: {
type: 1,
paid: {
$filter: {
input: "$orderPayment",
cond: "$$this.paid"
}
},
noPaid: {
$filter: {
input: "$orderPayment",
cond: { $not: "$$this.paid" }
}
}
}
},
{
$set: {
paid: { $sum: "$paid.paymentSum" },
noPaid: { $sum: "$noPaid.paymentSum" }
}
},
{
$group: {
_id: "$type",
paid: { $sum: "$paid" },
noPaid: { $sum: "$noPaid" }
}
}
])
Mongo Playground
use $cond in $group
db.collection.aggregate([
{
"$unwind": "$orderPayment"
},
{
"$group": {
"_id": "$type",
"paid": {
"$sum": {
$cond: {
if: { $eq: [ "$orderPayment.paid", true ] },
then: "$orderPayment.paymentSum",
else: 0
}
}
},
"noPaid": {
"$sum": {
$cond: {
if: { $eq: [ "$orderPayment.paid", false ] },
then: "$orderPayment.paymentSum",
else: 0
}
}
}
}
}
])
mongoplayground
i want to find profit of each account using mongodb and node.js i wrote this code but the main problem is i $match stage of aggregation pipeline i want to filter document according to accountNum and also rate but in my query filtering according to account number not work it return an empty array when i comment the account number and only filter by rate and grouping according to account number it work but i want to only find the profit of one person not all and filter data in $match state according to rate and accountNum both.
my code that return null value(filtering according to rate and accountNum in $match. i have some data with that account number):
const profit = await roznamcha.aggregate([
{
$match: {
rate: { $ne: 0 },
accountNum:{$eq:'$req.query.accountNum'}
}
},
{
$addFields: {
resultMultiply: {
$divide: [
{ $multiply: ["$credit", { $subtract: [100, "$rate"] }] }, 100
]
},
resultMultiplyde: {
$divide: [
{ $multiply: ["$debit", { $subtract: [100, "$rate"] }] }, 100
]
},
}
},
{
$group: {
_id: 0,
sumcredit: {
$sum: '$resultMultiply'
},
sumdebit: {
$sum: '$resultMultiplyde'
}
}
},
{
$addFields: {
finalProfit: { $subtract: ["$sumcredit", "$sumdebit"] }
}
}
])
res.status(201).json({
status: 'success',
data: {
profit
}
})
})
the code that work and return a value (filter according to rate only in $match stage)
const profit = await roznamcha.aggregate([
{
$match: {
rate: { $ne: 0 },
}
},
{
$addFields: {
resultMultiply: {
$divide: [
{ $multiply: ["$credit", { $subtract: [100, "$rate"] }] }, 100
]
},
resultMultiplyde: {
$divide: [
{ $multiply: ["$debit", { $subtract: [100, "$rate"] }] }, 100
]
},
}
},
{
$group: {
_id: '$accountNum',
sumcredit: {
$sum: '$resultMultiply'
},
sumdebit: {
$sum: '$resultMultiplyde'
}
}
},
{
$addFields: {
finalProfit: { $subtract: ["$sumcredit", "$sumdebit"] }
}
}
])
res.status(201).json({
status: 'success',
data: {
profit
}
})
})
I have the following MySQL query in which I have done sum profit which I have fields like: rate, credit(money)
SELECT SUM((credit*(100-rate))/100) FROM roznamcha WHERE (accountNum=$id AND rate!=0.0)'
I have written the following query in mongodb using node.js but it returns null whoever I have some data in my database
const profit=await roznamcha.aggregate([
{
$match:{
rate:{$ne:0}
}
},
{
$group:{
_id :'$accountNum',
}
},
{
$addFields:{
resultMultiply:{
$divide:[
{$multiply:['$credit','$rate-$100']},100
]
},
sumcredit:{
$sum:'$resultMultiply'
}
} }
])
res.status(201).json({
status:'success',
data:{
profit
}
})
My output:
{
"status": "success",
"data": {
"profit": [
{
"_id": "612deac8fbc8ef21a0fa4ea7",
"resultMultiply": null,
"sumcredit": 0
},
{
"_id": "612223327e2af83a4cec1272",
"resultMultiply": null,
"sumcredit": 0
}
]
}
my schema:
const mongoose = require('mongoose');
const roznamchaSchem=mongoose.Schema({
accountNum: {
type:mongoose.Schema.ObjectId,
ref:'account',
required: ['please specify this record is from who', true],
},
credit: {
type: Number,
default: 0
},
debit: {
type: Number,
default: 0
},
rate: {
type: Number,
default: 0
},
description:{
type:String,
required:['description can not be empty',true],
minlength: 8
},
issueDate:{
type: Date,
required:['add an valide date',true]
}
});
roznamchaSchem.index({accountNum:-1});
const Roznamcha=mongoose.model('roznamcha',roznamchaSchem);
module.exports=Roznamcha;
and my example of document:
id:612f533e8eb5533f303966e4
credit:50
debit:0
rate:2
accountNum:612deac8fbc8ef21a0fa4ea7
description:"this it for you"
issueDate:6543-01-01T00:00:00.000+00:00
can anyone guide me in solving this query?
Besides #Joe and #Nenad answer the error for the subtraction: '$rate-$100';
You need to re-position your logic structure.
Perform calculation: SUM((credit*(100-rate))/100).
Group by $accountNum and aggregate SUM for resultMultiply.
db.collection.aggregate([
{
$match: {
rate: {
$ne: 0
}
}
},
{
$addFields: {
resultMultiply: {
$divide: [
{
$multiply: [
"$credit",
{
"$subtract": [
100,
"$rate"
]
}
]
},
100
]
}
}
},
{
$group: {
_id: "$accountNum",
total: {
$sum: "$resultMultiply"
}
}
}
])
Output
[
{
"_id": 2,
"total": 1.5
},
{
"_id": 1,
"total": 5.5
}
]
Sample MongoDB playground
'$rate-$100' is referring to a field named "rate-$100". You probably meant to subtract using
{$subtract: [ 100, "$rate"]}
You can not use mathematical operator minus for substraction, you have to use $subtract aggregation operator.
resultMultiply: {
$divide: [{
$multiply: [
"$credit",
{ $subtract: [ 100, "$rate" ] }
]
},
100
]
}
In my app, I have a polls collection and the a document looks like this;
/* POLLS */
{
...
"type": "COMMON",
"__v": {
"$numberInt": "0"
},
"targets": [
{
"code": "city",
"logic": "eq",
"value": "4"
},
{
"code": "city",
"logic": "eq",
"value": "15"
}
]
}
And the targeting key is an array contains of objects of targets.
Each target's code can be city or gender.
So it means, "gender" may not be found in a Poll document.
Depending on that when fetching Polls, I want the ones matching with my User's fields, if the code exists.
For example; When fetching the Polls from a User's feed, If a Poll contains gender targeting, I want to find polls matching with my User's gender.
My code is like this:
Poll.find({
state: "PUBLISHED",
approval: "APPROVED",
$or: [
{
targets: {
$exists: true,
$eq: []
}
},
{
targets: {
$exists: true,
$ne: [],
$all: [
{
$elemMatch: {
code: {
$exists: true,
$eq: "city"
},
value: {
$eq: foundUser.cityCode // equals to 4 in this example
}
}
},
{
$elemMatch: {
code: {
$exists: true,
$eq: "gender"
},
value: {
$eq: foundUser.gender // equals to m in this example
}
}
}
]
}
}
]
})
However, the list returns empty for the Poll document I wrote above.
When the part below commented out, the code returns the correct polls.
Poll.find({
state: "PUBLISHED",
approval: "APPROVED",
$or: [
{
targets: {
$exists: true,
$eq: []
}
},
{
targets: {
$exists: true,
$ne: [],
$all: [
{
$elemMatch: {
code: {
$exists: true,
$eq: "city"
},
value: {
$eq: foundUser.cityCode // equals to 4 in this example
}
}
},
// {
// $elemMatch: {
// code: {
// $exists: true,
// $eq: "gender"
// },
// value: {
// $eq: foundUser.gender // equals to m in this example
// }
// }
// }
]
}
}
]
})
May anyone help me with the situation that return the right polls when gender or country targeted?
Thanks, Onur.
Return targets field is an array whose elements match the $elemMatch criteria in array
Try that query
Poll.find({
state: "PUBLISHED",
approval: "APPROVED",
$or: [
{
targets: {
$exists: true,
$eq: []
}
},
{
targets: {
$exists: true,
$ne: [],
$all: [
{
$elemMatch: {
code: {
$exists: true,
$eq: "city"
},
value: {
$eq: "4" // equals to 4 in this example
}
}
}
]
}
},
{
targets: {
$exists: true,
$ne: [],
$all: [
{
$elemMatch: {
code: {
$exists: true,
$eq: "gender"
},
value: {
$eq: 'm' // equals to m in this example
}
}
}
]
}
}
]
})