how to query mongo a value with $lte and $in? - mongodb

I have a document like:
{
_id:"1",
archive:[{mark:10},{mark:20},{mark:30},{mark:40}]
},
{
_id:"2",
archive:[{mark:12},{mark:25},{mark:30}]
},
{
_id:"3",
archive:[{mark:15},{mark:18}]
}
....
I want to find document where archive have {mark:30} but less then mark:30.
If I use query:
{archive:{$elemMatch:{ mark:{$lte:30}}}
will get document with 2,3
If I user query:
{archive:{$elemMatch:{ mark:{$lte:30,$in:[30]}}}
will get 1,2.
how to only get document 2?
Thank you!

You could try a query which would satisfy the logic
match documents where archive have { "mark": 30 } AND mark is NOT
greater than 30 (same as less than or equal to 30):
db.collection.find({
"archive.mark": {
"$eq": 30,
"$not": { "$gt": 30 }
}
})
or using the $and operator explicitly as
db.collection.find({
"$and": [
{ "archive.mark": 30 },
{ "archive.mark": { "$not": {
"$gt": 30
} } }
]
})

With $elemMatch
db.getCollection('yourCollection').find(
{
archive: {
$elemMatch: {
$and: [
{ mark: { $eq: 30 }},
{ mark: { $lte: 30 }},
{ mark: { $not: { $gt: 30 } } }
]
}
}
})

Related

MongoDB - How to filter results of an lookup

I'm new to MongoDB.
My lookup gives me the following result, how can I filter through the results below in order to get expected_delivery_time, using MongoDB aggregate:
[
{
"from":"Jeddah",
"delivery_rule":[
{
"to":"Makkah",
"expected_delivery_time":3
},
{
"to":"Riyadh",
"expected_delivery_time":2
}
]
},
{
"from":"Riyadh",
"delivery_rule":[
{
"to":"Makkah",
"expected_delivery_time":3
},
{
"to":"Riyadh",
"expected_delivery_time":1
}
]
}
]
Below is my code:
{
$lookup:
{
from: "Setting",
pipeline: [
{
$match: {
$expr: {
{ $eq: ["$name", "delivery_rules"] }
}
}
],
as: "delivery_rules"
}
},
{ "$match": { "$delivery_rules.value.from": "Jeddah" } },
{ "$match": { "$delivery_rules.value.to": "Riyadh" } },
I need help with below MySQL equivalent to:
SELECT 'expected_delivery_time' WHERE from='Jeddah' AND to='Makkah'
Based on the $lookup stage, I expected the result documents should be:
[
{
"from": "Jeddah",
"delivery_rules": [
{
"to": "Makkah",
"expected_delivery_time": 3
},
{
"to": "Riyadh",
"expected_delivery_time": 2
}
]
},
{
"from": "Riyadh",
"delivery_rules": [
{
"to": "Makkah",
"expected_delivery_time": 3
},
{
"to": "Riyadh",
"expected_delivery_time": 1
}
]
}
]
delivery_rules (with "s", but the document you shared is with delivery_rule)
Both delivery_rules.value.from and delivery_rules.value.to don't exist. And you shouldn't use $ for the field in $match stage based on your query.
$lookup
$match - Filter from and delivery_rules.to. Combine 2 $match stages into 1.
$project - Decorate output document. Add expected_delivery_time field:
3.1. $getField - Get expected_delivery_time field from the result 3.1.1.
3.1.1. $first - Get the first value from the result 3.1.1.1 array.
3.1.1.1. $filter - Filter the document with to is "Makkah" in delivery_rules array.
db.collection.aggregate([
/* Lookup stage */
{
"$match": {
"from": "Jeddah",
"delivery_rules.to": "Riyadh"
}
},
{
$project: {
expected_delivery_time: {
"$getField": {
"field": "expected_delivery_time",
"input": {
$first: {
$filter: {
input: "$delivery_rules",
cond: {
$eq: [
"$$this.to",
"Makkah"
]
}
}
}
}
}
}
}
}
])
Sample Mongo Playground

How to query an array and retrieve it from MongoDB

Updated:
I have a document on the database that looks like this:
My question is the following:
How can I retrieve the first 10 elements from the friendsArray from database and sort it descending or ascending based on the lastTimestamp value.
I don't want to download all values to my API and then sort them in Python because that is wasting my resources.
I have tried it using this code (Python):
listOfUsers = db.user_relations.find_one({'userId': '123'}, {'friendsArray' : {'$orderBy': {'lastTimestamp': 1}}}).limit(10)
but it just gives me this error pymongo.errors.OperationFailure: Unknown expression $orderBy
Any answer at this point would be really helpful! Thank You!
use aggregate
first unwind
then sort according timestap
group by _id to create sorted array
use addfields and filter for getting first 10 item of array
db.collection.aggregate([
{ $match:{userId:"123"}},
{
"$unwind": "$friendsArray"
},
{
$sort: {
"friendsArray.lastTimeStamp": 1
}
},
{
$group: {
_id: "$_id",
friendsArray: {
$push: "$friendsArray"
}
},
},
{
$addFields: {
friendsArray: {
$filter: {
input: "$friendsArray",
as: "z",
cond: {
$lt: [
{
$indexOfArray: [
"$friendsArray",
"$$z"
]
},
10
]
}// 10 is n first item
}
}
},
}
])
https://mongoplayground.net/p/2Usk5sRY2L2
and for pagination use this
db.collection.aggregate([
{ $match:{userId:"123"}},
{
"$unwind": "$friendsArray"
},
{
$sort: {
"friendsArray.lastTimeStamp": 1
}
},
{
$group: {
_id: "$_id",
friendsArray: {
$push: "$friendsArray"
}
},
},
{
$addFields: {
friendsArray: {
$filter: {
input: "$friendsArray",
as: "z",
cond: {
$and: [
{
$gt: [
{
$indexOfArray: [
"$friendsArray",
"$$z"
]
},
10
]
},
{
$lt: [
{
$indexOfArray: [
"$friendsArray",
"$$z"
]
},
20
]
},
]
}// 10 is n first item
}
}
},
}
])
The translation of your find to aggregation(we need unwind that why aggregation is used) would be like the bellow query.
Test code here
Query (for descending replace 1 with -1)
db.collection.aggregate([
{
"$match": {
"userId": "123"
}
},
{
"$unwind": {
"path": "$friendsArray"
}
},
{
"$sort": {
"friendsArray.lastTimeStamp": 1
}
},
{
"$limit": 10
},
{
"$replaceRoot": {
"newRoot": "$friendsArray"
}
}
])
If you want to skip some before limit add one stage also
{
"$skip" : 10
}
To take the 10-20 messages for example.

Query using $dateFromString and $lt and $lge in mongodb

I am trying to return some records for date range from mongodb and I am trying to use the following query to query the collection test for the field StartDate:
db.test.aggregate([
{
$match: {
"StartDate": {
"$gte": [
{
"$dateFromString": {
"dateString": "2021-03-01T00:00:00.0000000Z"
}
}
],
"$lt": [
{
"$dateFromString": {
"dateString": "2021-04-01T00:00:00.0000000Z"
}
}
]
}
}
}
])
The above query didn't return anything but I am sure there are some data between the dates. Any ideas what have I missed? Thanks!
The $dateFromString is a aggregation pipeline operator, so it requires to match in $expr expression condition, and expression have different syntax of $gte and $lt,
db.test.aggregate([
{
$match: {
$expr: {
$and: [
{
$gte: [
"$StartDate",
{
"$dateFromString": {
"dateString": "2021-03-01T00:00:00.0000000Z"
}
}
]
},
{
$lt: [
"$StartDate",
{
"$dateFromString": {
"dateString": "2021-04-01T00:00:00.0000000Z"
}
}
]
}
]
}
}
}
])
Playground
Second option: You can use $toDate operator alternate of $dateFromString,
Playground
Update as per new requirement:
Field activities.dateOfActivity is an array, it requires to iterate through loop and check each date's conditions,
$map to iterate loop of activities.dateOfActivity array and put both condition inside, it will return true if both condition satisfy otherwise return false
$anyElementTrue will check return array of boolean have any true condition then return document
db.test.aggregate([
{
$match: {
$and: [
{
"$expr": {
$anyElementTrue: {
$map: {
input: "$activities.dateOfActivity",
in: {
$and: [
{
"$gte": [
"$$this",
{
"$dateFromString": {
"dateString": "2021-03-01T00:00:00.0000000Z"
}
}
]
},
{
"$lt": [
"$$this",
{
"$dateFromString": {
"dateString": "2021-04-20T00:00:00.0000000Z"
}
}
]
}
]
}
}
}
}
}
]
}
}
])
Playground

MongoDB $limit inside an $and

I'm looking to implement a query that will return me a single JSON with 2 sets of data together.
What I look for is to retrieve 40 orders which offerDate is still in the future ( $gte: today ) and 8 orders which offerDate is in the past.
Currently I only have the 40 in the future like so:
const orders = await db.Order.aggregate([
{
$match: {
offerDate: {
$gte: moment().startOf('day').toDate(),
},
},
},
{
$limit: 40,
},
How could I achieve to add the limit inside an $and to get both together?
I tried with:
$match: {
$and: [
{
offerDate: {
$gte: moment().startOf('day').toDate(),
},
$limit: 40
},
{
offerDate: {
$lte: moment().startOf('day').toDate(),
},
$limit: 8
},
]
}
},
But that didn't work at all and I cannot find the right way to do it.
Thanks a lot!
$limit is a pipeline stage which cannot be used within any other stage so what you're trying is not the way to go. You simply need two separate $matches followed by $limit and the $facet operator allows you to do that:
{
$facet: {
greater: [
{ $match: { offerDate: { $gte: moment().startOf('day').toDate() } } },
{ $limit: 40 }
],
lower: [
{ $match: { offerDate: { $lte: moment().startOf('day').toDate() } } },
{ $limit: 8 }
]
}
},
{
$project: {
all: { $concatArrays: [ "$greater", "$lower" ] }
}
}

MongoDB, is there a statement similar to if then elif in sql?

Query is to find people whose age is shown and if their grade is 5 or higher then they will get honours, othewise fail. Can I use $cond with find?
db.test.find({age:{$exists:true}},{$cond: {if: "grade":5, then:"Honours" : true, else: "Honours" : "fail"}})
I also tried
db.test.find({$and: [{age:{$exists:true}}, {grade: {$gte:"5"}}]}, {$set:{"Honours":"true"}}, {multi:true})
But neither come close. Any help/direction is very much appreciated.
Check this.
db.test.aggregate([
{
$match: {
"age": {
"$exists": true
}
}
},
{
$project: {
"Honours": {
$switch: {
branches: [
{
case: {
"$gte": [
"$grade",
5
]
},
then: "true"
}
],
default: "fail"
}
}
}
}
])
.find projection only allows include / exclude fields, neither transform them nor add new field. Use MongoDB aggregation
db.test.aggregate([
{
$match: {
age: {
$exists: true
}
}
},
{
$project: {
Honours: {
$cond: [
{
$gte: [
"$grade",
5
]
},
true,
"fail"
]
}
}
}
])
MongoPlayground