Second Match is working not the 1st one MongoDB Aggregation - node.js

let id = new moongoes.Types.ObjectID("some_id_here")
aggregation = PropertyDetails.aggregate([
{
$match: {
team: id,
isDeleted: { $in:[ null, "", false] }
},
$match: {
units: {
$gte: 20,
$lte: 25
}
}
}])
Using the first match works fine getting the result against ID but when trying to get between Units using the second match it's returning all the units between 20 and 25 regardless of the ID I selected in First Match

It should work like you did that. I made a test
[
{
'$match': {
'team': 12345,
'isDeleted': {
'$in': [
null, '', false
]
}
}
}, {
'$match': {
'units': {
'$gte': 20,
'$lte': 25
}
}
}
]
You can see - team 12346 is not included in the results.
But why arent you using a single stage for this?
[
{
'$match': {
'team': 12345,
'isDeleted': {
'$in': [
null, '', false
]
},
'units': {
'$gte': 20,
'$lte': 25
}
}
}
]
This would lead to the same results.

Related

$group in Mogoose Random Results But No Duplicate in Pagination

Actually i want to produce random results each time page loads, it's working but on pagination sometimes I got
duplicate results one or two products, am i doing something wrong. i am randomising by spliting product_code. your
help is really appreciated.
var obj = {
$or: [
{
'main_data.product_name': {
$regex: req.body.searchTerm,
$options: 'i'
}
},
{
'main_data.product_code': {
$regex: req.body.searchTerm,
$options: 'i'
}
}
],
'main_data.status': 'active',
}
var query = [
{ $unwind: '$main_material' },
{
$project: {
product_name: 1,
product_code: 1,
main_material: 1,
status:1,
qty_per_box:1,
city_state: { $split: ["$product_code", "-"] },
qty: 1
}
},
{
$project: {
product_name: 1,
product_code: 1,
main_material: 1,
status:1,
qty_per_box:1,
city_state: "$city_state"
}
},
{ $group: { _id: { "pc": "$city_state" }, main_data: { "$first": "$$ROOT" }, cdate: { "$first": "$createdOn" } } },
{ $match: obj},
{ $sort: { "price_usd": 1 } },
{ $skip: parseInt(req.params.skip) },
{ $limit: parseInt(req.body.items) },
]
Products_model.aggregate(query);
My Exact requirement is to show random products every time. But due to random list in
pagination it creates duplicity in the results.

update mongodb field by an array object property

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

Fetching last 30 days of Mongo records, summing by date, and filling in missing dates in range

I have a Mongo database filled with "Events" records, that look like this:
{
timestamp: 2022-03-15T22:11:34.711Z,
_id: new ObjectId("62310f16b0d71321e887a905")
}
Using a NodeJs server, I need to fetch the last 30 days of Events, grouped/summed by date, and any dates within that 30 days with no records need to be filled with 0.
Using this code I can get the correct events, grouped/summed by date:
Event.aggregate( [
{
$match: {
timestamp: {
$gte: start,
$lte: end,
}
}
},
{
$project: {
date: {
$dateToParts: { date: "$timestamp" }
},
}
},
{
$group: {
_id: {
date: {
year: "$date.year",
month: "$date.month",
day: "$date.day"
}
},
"count": { "$sum": 1 }
}
}
] )
This will return something like this:
[
{
"_id": {
"date": {
"year": 2022,
"month": 3,
"day": 14
}
},
"count": 3
},
{
"_id": {
"date": {
"year": 2022,
"month": 3,
"day": 15
}
},
"count": 8
},
]
I also have this Javascript code to generate the last 30 days of dates:
const getDateRange = (start, end) => {
const arr = [];
for(let dt = new Date(start); dt <= end; dt.setDate(dt.getDate() + 1)){
arr.push(new Date(dt));
}
return arr;
};
const subtractDays = (date, days) => {
return new Date(date.getTime() - (days * 24 * 60 * 60 * 1000));
}
const end = new Date();
const start = subtractDays(end, 30);
const range = getDateRange(start, end);
Which returns something like this:
[
2022-03-09T01:13:10.769Z,
2022-03-10T01:13:10.769Z,
2022-03-11T01:13:10.769Z,
2022-03-12T01:13:10.769Z,
2022-03-13T01:13:10.769Z,
...
]
It seems like I have all the pieces, but I'm having trouble putting all this together to do what I need in an efficient way. Any push in the right direction would be appreciated.
Whenever one has to work with date/time arithmetic then I recommend a library like moment.js
const end = moment().startOf('day').toDate();
const start = moment().startOf('day').subtract(30, 'day').toDate();
In MongoDB version 5.0 you can use $dateTrunc(), which is shorter than $dateToParts and { year: "$date.year", month: "$date.month", day: "$date.day" }
You need to put all data in an array ({$group: {_id: null, data: { $push: "$$ROOT" }}) and then at missing elements with $ifNull:
event.aggregate([
{
$match: {
timestamp: { $gte: start, $lte: end }
}
},
{
$group: {
_id: { $dateTrunc: { date: "$timestamp", unit: "day" } },
count: { $sum: 1 }
}
},
{ $project: {timestamp: "$_id", count: 1, _id: 0} },
{
$group: {
_id: null,
data: { $push: "$$ROOT" }
}
},
{
$set: {
data: {
$map: {
input: { $range: [0, 30] },
as: "i",
in: {
$let: {
vars: {
day: { $dateAdd: { startDate: start, amount: "day", unit: "$$i" } }
},
in: {
$ifNull: [
{
$first: {
$filter: {
input: "$data",
cond: { $eq: ["$$this.timestamp", "$$day"] }
}
}
},
{ timestamp: "$$day", count: 0 }
]
}
}
}
}
}
}
},
{ $unwind: "$data" }
])
$range operator supports only integer values, that's the reason for using $let. Otherwise, if you prefer to use the external generated range, it would be
{
$set: {
data: {
$map: {
input: range,
as: "day",
in: {
$ifNull: [
{
$first: {
$filter: {
input: "$data",
cond: { $eq: ["$$this.timestamp", "$$day"] }
}
}
},
{ timestamp: "$$day", count: 0 }
]
}
}
}
}
}
And for MongoDB version 5.1 you may have a look at $densify
Use aggregation stage densify if you're using MongoDB version 5.1 or later. But for lower version, below query can be used.
db.collection.aggregate([
{
$match: {
timestamp: {
$gte: {
"$date": "2022-03-01T00:00:00.000Z"
},
$lte: {
"$date": "2022-03-31T23:59:59.999Z"
},
}
}
},
{
$project: {
date: {
$dateToParts: {
date: "$timestamp"
}
},
}
},
{
$group: {
_id: {
date: {
year: "$date.year",
month: "$date.month",
day: "$date.day"
}
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": null,
"originData": {
"$push": "$$ROOT"
}
}
},
{
"$project": {
"_id": 0,
"data": {
"$concatArrays": [
{
"$map": {
"input": {
"$range": [
0,
30,
1
]
},
"in": {
"$let": {
"vars": {
"date": {
"$add": [
{
"$date": "2022-03-01T00:00:00.000Z"
},
{
"$multiply": [
"$$this",
86400000
]
}
]
}
},
"in": {
"_id": {
"date": {
"day": {
"$dayOfMonth": "$$date"
},
"month": {
"$month": "$$date"
},
"year": {
"$year": "$$date"
}
}
},
"count": 0
}
}
}
}
},
"$originData"
]
}
}
},
{
"$unwind": "$data"
},
{
$group: {
_id: {
date: {
year: "$data._id.date.year",
month: "$data._id.date.month",
day: "$data._id.date.day"
}
},
"count": {
"$sum": "$data.count"
}
}
},
{
"$sort": {
"_id.date.year": 1,
"_id.date.month": 1,
"_id.date.day": 1
}
}
])
Link to online playground. https://mongoplayground.net/p/5I0I04HoHXm

How to get the Sum from independent two subdocument Arrays in mongoose

I have this schema
var salesExpenseSchema = new Schema({
date : {
month: Number
},
sales: [{amount : Schema.Types.Decimal128}],
expenses: [{amount : Schema.Types.Decimal128}]
});
Example of a database record is like this
{
_id:'5dbac5dfa1488240cbc4f838',
date:{month:11},
sales:[{amount:3000},{amount:5000}],
expenses: [{amount:5000},{amount:500}]
},
{
_id:'5dbac5dfa1488240cbc4f838',
date:{month:10},
sales:[{amount:2000},{amount:5000}],
expenses: [{amount:500},{amount:800}]
},
{
_id:'5dbac5dfa1488240cbc4f838',
date:{month:09},
sales:[{amount:2000},{amount:4000}],
expenses: [{amount:200},{amount:300}]
}
Now I want to get the Summation of sales and expenses.
I have used Aggregate with $unwind for both sales and expenses like this below:
SalesExpense.aggregate([
{$unwind: "$sales"},
{$unwind: "$expenses"},
{$group:{
_id:'$_id',
sales:{$sum: "$sales.sellPrices"},
expenses:{$sum: "$expenses.amount"},
}
},
But the problem is... If one array document has data and the other has no data, then it gives 0 ie, the real summation isn't obtained. This is to say, if there are sales but no expenses then their sum becomes 0, and vice-versa.
I want to get the summation for both sales and expenses regardless of one of them not having data. How do I achieve this?
EDIT:
I have edited the question and added the date object in my schema and in the database records: I want to make this summation based on each month, that is to say... each month to have its own sales and expenses... Sort of a timeline with each month having its own sales and expenses.
I have tried using $group before $project
{$group:{
_id:'$date.month'}}
But it seems not to give the expected results.
I want an output like this one:
[
{
"month": "11",
"sales": {
"$numberDecimal": "8000"
},
"expenses": {
"$numberDecimal": "5500"
}
},
{
"month": "10",
"sales": {
"$numberDecimal": "7000"
},
"expenses": {
"$numberDecimal": "1100"
}
},
{
"month": "09",
"sales": {
"$numberDecimal": "6000"
},
"expenses": {
"$numberDecimal": "500"
}
},
]
How can I achieve this?
You can group by month and get the totals like this:
db.collection.aggregate([
{
$group: {
_id: "$date.month",
"sales": {
"$sum": {
"$sum": "$sales.amount"
}
},
"expenses": {
"$sum": {
"$sum": "$expenses.amount"
}
}
}
}
])
Sample Data:
[
{
_id: "5dbac5dfa1488240cbc4f838",
date: {
month: 11
},
sales: [
{
amount: 1
},
{
amount: 2
}
],
expenses: []
},
{
_id: "5dbac5dfa1488240cbc4f839",
date: {
month: 11
},
sales: [
{
amount: 5
},
{
amount: 6
}
],
expenses: [
{
amount: 7
},
{
amount: 8
}
]
},
{
_id: "5dbac5dfa1488240cbc4f840",
date: {
month: 12
},
sales: [],
expenses: [
{
amount: 7
},
{
amount: 8
}
]
}
]
Result:
[
{
"_id": 12,
"expenses": 15,
"sales": 0
},
{
"_id": 11,
"expenses": 15,
"sales": 14
}
]
Playground:
https://mongoplayground.net/p/K9ofoZx5ORI

Query on date or does not exist?

Trying to figure out if I can make this query work using mongoose and nodejs.
Product.find({
price: { $gt: 2, $lt: 3},
date: { $gt: new Date() || $exists: false}
}). exec(callback);
Does anyone know if it is possible to check if a date does not exist send it back or if the date is greater than today?
Thanks
Use $or:
Product.find({
"price": { "$gt": 2, "$lt": 3 },
"$or": [
{ "date": { "$gt": new Date() } },
{ "date": { "$exists": false } }
]
}). exec(callback);
All arguments are generally an implicit AND, so just like it is "price greater than 2 AND less than 3" you are saying in addition "AND the date is greater than this date OR date does not exist".
Just to spell out the logic in phrase form
With "multiple fields like this, THEN you actually use an $and
Product.find({
"$and": [
{ "price": { "$gt": 2, "$lt": 3 } },
{ "$or": [
{ "date1": { "$gt": new Date() } },
{ "date1": { "$exists": false } }
]},
{ "$or": [
{ "date2": { "$gt": new Date() } },
{ "date2": { "$exists": false } }
]}
]
}). exec(callback);

Resources