mongodb query findById() and month and year - mongodb

I've searched for an answer on how to solve my problem but found nothing, so I am very sorry if I am repeating a question that has been asked before.
i'm trying to find a results for a specific userId by month and year.
the dates has been stored in db in this format : yyyy-mm-dd.
I'm trying the following query with Mongo v3.6.8:
db.collection.find({
$and: [
{
$expr: {
$eq: [
{
$month: {
$dateFromString: {
dateString: "$Ntry.BookgDt",
format: "%Y-%m-%d"
}
}
},
12
]
}
},
{
$expr: {
$eq: [
{
$year: {
$dateFromString: {
dateString: "$Ntry.BookgDt",
format: "%Y-%m-%d"
}
}
},
2020
]
}
},
{
$expr: {
$eq: [
"id",
"5fab9a66c493dc4a3c49a7a3"
]
}
}
]
})
Sample data:
[
{
"userid": "5fab9a66c493dc4a3c49a7a3",
"name": "user name",
"acc": "admin",
"Blas": "00.00",
"Ntry": [
{
"Amt": "11.72",
"BookgDt": "2020-08-16",
},
{
"Amt": "16.72",
"BookgDt": "2020-06-23",
}
]
},
{
"userid": "5fab9a77c493dc4a3c49a7a3",
"name": "user name",
"acc": "user",
"Blas": "00.00",
"Ntry": [
{
"Amt": "11.72",
"BookgDt": "2020-08-23",
},
{
"Amt": "16.72",
"BookgDt": "2020-07-23",
}
]
}
]
so my query is to find all Ntry for UserId 5fab9a66c493dc4a3c49a7a3 in month 8 and year 2020, but I got this erorr:
query failed: (ConversionFailure) $dateFromString requires that 'dateString' be a string, found: array with value ["2020-08-16", "2020-06-23"]
can you please help me to find the suitable query, and thank you in advance.
here is also a mongo play ground link, It's the best for quick editing:

This pipeline should work:
db.foo.aggregate([
// Top level match
{$match: {userid: "5fab9a66c493dc4a3c49a7a3"}}
// Next, only keep entries in the Ntry array with month 8 and year 2020.
// addFields: {"Ntry": {$filter: {input: "$Ntry"}}} means overwrite the
// original Ntry array.
,{$addFields: {"Ntry": {$filter: {
input: "$Ntry",
as: "zz",
cond: { $and: [
{$eq: [{$month:{$dateFromString:{dateString:"$$zz.BookgDt",format: "%Y-%m-%d"}}}\
, 8] },
{$eq: [{$year: {$dateFromString:{dateString:"$$zz.BookgDt",format: "%Y-%m-%d"}}}\
, 2020] }
]}
}}
}}
// It is possible everything got filtered out of the Ntry array, leaving
// an empty (size 0) array. We likely do not want that, so further
// cut down the output material. You can comment this out to see what
// changes, especially if you change the month and year targets above.
,{$match: {$expr: {$ne: [ {$size: "$Ntry"}, 0] } }}
]);
It's probably simpler to call $dateFromString twice but if you are feeling adventurous, then use $let inside the cond to convert the date just once:
db.foo.aggregate([
{$match: {userid: "5fab9a66c493dc4a3c49a7a3"}}
,{$addFields: {"Ntry": {$filter: {
input: "$Ntry",
as: "zz",
cond: {
$let: {
vars: {dd: {$dateFromString:{dateString:"$$zz.BookgDt",format: "%Y-%m-%d"}}},
in: {
$and: [
{$eq: [{$month: "$$dd"}, 8] },
{$eq: [{$year: "$$dd"}, 2020] }
]
}
}
}
}}
}}
,{$match: {$expr: {$ne: [ {$size: "$Ntry"}, 0] } }}
]);

Related

MongoDB Query on multiple field condition

I have a MongoDB model that is currently like this (this is the stripped version):
{
title: String,
type: {
type: String,
lowercase: true,
enum: ['event', 'regular', 'project'],
},
project_start_time: Date,
project_end_time: Date,
regular_start_date: Date,
regular_end_date: Date,
events: [{
id: Number,
date: Date
}]
}
Now, I want to query something like this:
Find data where the regular_end_date, project_end_time, and events at the last index are lower than the date provided
The catch is, not every data has the three criteria above because it is available according to the types (Sorry for the messy data, it is already there). Below is an example:
If the data type is an event, then there are events
If the data type is regular, then there are regular_start_date and regular_end_date
If the data type is a project, then there are project_start_date and project_end_date
So far, I've tried to use this:
db.data.find({
"$or": [
{
"project_end_time": {
"$lt": ISODate("2022-12-27T10:09:49.753Z")
},
},
{
"regular_end_date": {
"$lt": ISODate("2022-12-27T10:09:49.753Z")
}
},
{
"$expr": {
"$lt": [
{
"$getField": {
"field": "date",
"input": {
"$last": "$events"
}
}
},
ISODate("2022-12-27T10:09:49.753Z")
]
}
}
]
})
Also with aggregation pipeline:
db.data.aggregate([
{
$match: {
"$or": [{
"project_end_time": {
"$lt": ISODate("2022-12-27T10:09:49.753Z")
},
},
{
"regular_end_date": {
"$lt": ISODate("2022-12-27T10:09:49.753Z")
}
},
{
"$expr": {
"$lt": [{
"$getField": {
"field": "date",
"input": {
"$last": "$events"
}
}
},
ISODate("2022-12-27T10:09:49.753Z")
]}
}]
}
}
])
But it shows all data as if it wasn't filtered according to the criteria. Any idea where did I do wrong?
FYI I am using MongoDB 5.0.2
One option is to check if the relevant field exists before checking its value, otherwise its value is null which is less than your requested date:
db.collection.find({
$or: [
{$and: [
{project_end_time: {$exists: true}},
{project_end_time: {$lt: ISODate("2022-12-27T10:09:49.753Z")}}
]},
{$and: [
{regular_end_date: {$exists: true}},
{regular_end_date: {$lt: ISODate("2022-12-27T10:09:49.753Z")}}
]},
{$and: [
{"events.0": {$exists: true}},
{$expr: {
$lt: [
{$last: "$events.date"},
ISODate("2022-12-27T10:09:49.753Z")
]
}}
]}
]
})
See how it works on the playground example

How to get or return only the matching object from an nested array using mongoose

{
"_id": "6339f99ee18b2481a04b4fe8",
"userId": "60a8a51cf2229813a45d2238",
"array1": [
{
"someId1": "6339f99ee18b2481a04b4fe9",
"customIndex": 2,
"array2": [
{
"someId2": "6339f99ee18b2481a04b4fea",
"startDate": 2022-10-10T19:56:26.000+00:00,
"endDate": 2022-10-12T19:56:26.000+00:00,
}
]
},
{
"someId1": "6345ca40112b743fd8172be0",
"customIndex": 4,
"array2": [
{
"someId2": "6345ca40112b743fd8172be1",
"startDate": 2022-10-10T19:56:26.000+00:00,
"endDate": 2022-10-27T19:56:26.000+00:00,
}
]
}
]
}
I have above structure in mongoDB and want to get only that object from array1 which matches the conditions of endDate > 2022-10-17
Here's what I try to do:
result= await Collection.find({
userId: { '$in': userIdList},
'array1.array2.endDate': { "$gte": 2022-10-17}
})
But above return the both objects from array1 even though the endDate for one object is less than 2022-10-17
How can I get the the response like below? Also, Am I using the right Mongoose calls to achieve what I am trying to achieve.
Expected response that I am trying to achieve:
{
"_id": "6339f99ee18b2481a04b4fe8",
"userId": "60a8a51cf2229813a45d2238",
"array1": [
{
"someId1": "6345ca40112b743fd8172be0",
"customIndex": 4,
"array2": [
{
"someId2": "6345ca40112b743fd8172be1",
"startDate": 2022-10-10T19:56:26.000+00:00,
"endDate": 2022-10-27T19:56:26.000+00:00,
}
]
}
]
}
If array1 can contain several such items, and array2 contain several such items, one option is using $reduce with $filter and $mergeObjects for this:
db.collection.aggregate([
{$match: {userId: {'$in': userIdList}}}
{$project: {
userId: 1,
array1: {
$reduce: {
input: "$array1",
initialValue: [],
in: {$concatArrays: [
"$$value",
[{$mergeObjects: [
"$$this",
{array2: {
$filter: {
input: "$$this.array2",
as: "innerItem",
cond: {$gte: [
"$$innerItem.endDate",
{$dateFromParts: {year: 2022, month: 10, day: 17}}
]}
}
}}
]}]
]}
}
}
}},
{$project: {
userId: 1,
array1: {$filter: {
input: "$array1",
cond: {$gt: [{$size: "$$this.array2"}, 0]}
}}
}}
])
See how it works on the playground example

Conditional match on an existence of a field in collection

Please help me. I have a collection as below
[
{
"_id":{
"$oid":"62a3673660e2f16c7a7bc088"
},
"merchant":{
"$oid":"62a3640560e2f16c7a7bc078"
},
"title":"24 Test 1",
"filter_conditions":{
"city":[
"AAA",
"BBB",
"CCC",
"DDD"
],
"state":[
],
"pincode":[
"12345"
]
}
},
{
"_id":{
"$oid":"62a3673660e2f16c7a7bc089"
},
"merchant":{
"$oid":"62a3640560e2f16c7a7bc079"
},
"title":"24 Test 2",
"filter_conditions":{
"city":[
"AAA",
"BBB"
]
}
}
]
I want to filter data based on pincode/city/state
if pincode is present match it and ignore city and state
elseif city is present match it and ignore state
else match on state
You can use an aggregation pipeline with a $filter:
If any of the fields does not exist on the doc, create it with an empty array.
Use $filter to grade the docs, so the grade for matching pincode is 100, for matching city is 10 for matching state is 1. Use $max to keep the best grade only.
Return the doc with highest grade.
db.collection.aggregate([
{$set: {
"filter_conditions.pincode": {$ifNull: ["$filter_conditions.pincode", []]},
"filter_conditions.city": {$ifNull: ["$filter_conditions.city", []]},
"filter_conditions.state": {$ifNull: ["$filter_conditions.state", []]}
}
},
{$set: {
grade: {
$max: [
{$multiply: [
{$size: {
$filter: {
input: "$filter_conditions.pincode",
as: "item",
cond: {$eq: ["$$item", "12345"]}
}
}
}, 100]
},
{$multiply: [
{$size: {
$filter: {
input: "$filter_conditions.city",
as: "item",
cond: {$eq: ["$$item", "BBB"]}
}
}
}, 10]
},
{$multiply: [
{$size: {
$filter: {
input: "$filter_conditions.state",
as: "item",
cond: {$eq: ["$$item", "AL"]}
}
}
}, 1]
}
]
}
}
},
{$sort: {grade: -1}},
{$limit: 1}
])
See how it works on the playground example
You can work with nested $cond to perform the filtering.
Concept:
Check filter_conditions.pincode is existed.
1.1. If true, check the value is existed in filter_conditions.pincode array.
1.2. Else, proceed to 2.
Check filter_conditions.city is existed.
2.1. If true, check the value is existed in filter_conditions.city array.
2.2. Else, proceed to 3.
Check if value is existed in filter_conditions.state array (default as empty array if the array is not existed).
db.collection.aggregate([
{
$match: {
$expr: {
$cond: {
if: {
$ne: [
"$filter_conditions.pincode",
undefined
]
},
then: {
$in: [
"", // pincode value
"$filter_conditions.pincode"
]
},
else: {
$cond: {
if: {
$ne: [
"$filter_conditions.city",
undefined
]
},
then: {
$in: [
"", // city value
"$filter_conditions.city"
]
},
else: {
$in: [
"", // state value
{
$ifNull: [
"$filter_conditions.state",
[]
]
}
]
}
}
}
}
}
}
}
])
Sample Mongo Playground

How do I query mongodb with aggregration by passing data as a parameter to filter unix time stamp

I'm querying through Metabase which is connected to a Mongodb server. The field which I'm querying is nested and is a Unix timestamp. See below
{
room_data: {
"meta": {
"xxx_unrecognized": null,
"xxx_sizecache": 0,
"id": "Hke7owir4oejq3bMf",
"createdat": 1565336450838,
"updatedat": 1565336651548,
}
}
}
The query I have written is as follows
[
{
$match: {
client_id: "{{client_id}}",
"room_data.meta.createdat": {
$gt: "{{start}}",
$lt: "{{end}}",
}
}
},
{
$group: {
id: "$room_data.recipe.id",
count: {
$sum: 1
}
}
}
]
I do not get any result as the field room_data.meta.createdat is not a date (Aug 20, 2020) which I'm passing in. Here start and end are the parameters (Metabase feature) which I'm passing in the Date format. I need some help in converting those dates into unix timestamp which can then be used to filter out the results between the specific dates
If you're using Mongo version 4.0+ you can then use $toDate in you're aggregation like so:
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{
$eq: [
"$client_id",
{{client_id}}
]
},
{
$lt: [
{
$toDate: "$room_data.meta.createdat"
},
{{end}}
]
},
{
$gt: [
{
$toDate: "$room_data.meta.createdat"
},
{{start}}
]
}
]
}
}
}
])
MongoPlayground
If you're you're on an older Mongo version I recommend you either convert you're database fields to be Date type, or you convert your input into a number timestamp somehow (I'm unfamiliar with metabase).
The last option is to use $subtract as you can subtract a number from a date in Mongo, then check to see whether that date is before or after 1970-01-01T00:00:00Z. the problem with this approach is it does not consider timezones, so if your input's timezone is different than your database one or is dynamic this will be a problem you'll have to account for.
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{
$eq: [
"$client_id",
{{client_id}}
]
},
{
$gt: [
{
"$subtract": [
{{end}},
"$room_data.meta.createdat"
]
},
ISODate("1970-01-01T00:00:00.000Z")
]
},
{
$lt: [
{
"$subtract": [
{{start}},
"$room_data.meta.createdat"
]
},
ISODate("1970-01-01T00:00:00.000Z")
]
}
]
}
}
}
])
MongoPlayground

MongoDb Aggregate nested documents with $add

I need to get sum value from nested documents.
DB document:
{
"_id": 123,
"products": [
{
"productId": 1,
"charges": [
{
"type": "che",
"amount": 100
}
]
}
]
}
i wanted to get sum value.
sumValue = products.charges.amount+20; where "products.productId" is 1 and "products.charges.type" is "che"
i tried below query but no hope:
db.getCollection('test').aggregate(
[
{"$match":{$and:[{"products.productId": 14117426}, {"products.charges.type":"che"}]},
{ $project: { "_id":0, total: { $add: [ "$products.charges.price", 20 ] } }}
]
)
please help me to solve this.
You have to take a look at $unwind operator which deconstructs an array to output a document for each element of array. Also take a look at add and project operators.
I assume your db query should look like this:
db.test.aggregate([
{$unwind: '$products'}, // Unwind products array
{$match: {'products.productId' : 3}}, // Matching product id
{$unwind: '$products.charges'}, // Unwind charges
{$match: {'products.charges.type' : 'che'}}, // Matching charge type of che
{$project: {'with20': {$add: ["$products.charges.amount", 20]}}}, // project total field which is value + 20
{$group: {_id : null, amount: { $sum: '$with20' }}} // total sum
])
You can run $reduce twice to convert your arrays into scalar value. The outer condition could be applied as $filter, the inner one can be run as $cond:
db.collection.aggregate([
{
"$project": {
_id: 0,
total: {
$reduce: {
input: { $filter: { input: "$products", cond: [ "$$this.productId", 1 ] } },
initialValue: 20,
in: {
$add: [
"$$value",
{
$reduce: {
input: "$$this.charges",
initialValue: 0,
in: {
$cond: [ { $eq: [ "$$this.type", "che" ] }, "$$this.amount", 0 ]
}
}
}
]
}
}
}
}
}
])
Mongo Playground