mongodb find with calculated field - node.js

I'm trying to create a mongodb query using the filtered value in the filter. For example:
var myIdVariable = '1jig23h34r34r30h';
var myVisibleVariable = false;
var myDistanceVariable = 100;
db.getCollection.find({
'_id': myIdVariable,
'isVisible': myVisibleVariable,
'distanceRange': {$lte: {myDistanceVariable - distanceRange}}
})
So, I want filter the distanceRange from database based on the calculation of (myDistanceVariable - distanceRange), with the distanceRange given in the same query.
I don't know if I give you a clear explanation of my problem. It's possible?
Thanks you.

Use the $expr operator to build a query expression that allows you to compare fields from the same document as well as compare the distanceRange field with the calculation of the field itself and your variables.
You would need to use the logical $and query operator to include the other query expressions thus your final query would look like the following:
db.getCollection('collectionName').find({
'$expr': {
'$and': [
{ 'isVisible': myVisibleVariable },
{ '$lte': [
'$distanceRange', {
'$subtract': [
myDistanceVariable, '$distanceRange'
]
}
] }
]
}
})
If your MongoDB server doesn't support the $expr operator then go for the aggregation framework route with $redact
db.getCollection('collectionName').aggregate([
{ "$redact": {
"$cond": [
{
'$and': [
{ 'isVisible': myVisibleVariable },
{ '$lte': [
'$distanceRange', {
'$subtract': [
x, '$distanceRange'
]
}
] }
]
},
"$$KEEP",
"$$PRUNE"
]
} }
])
Note
Including the _id in the query expressions means you are narrowing down your selection to just a single document and the query may not return any results since it's looking for a specific document with that _id AND the same document should satisfy the other query expressions.

Related

updateOne nested Array in mongodb

I have a group collection that has the array order that contains ids.
I would like to use updateOne to set multiple items in that order array.
I tried this which updates one value in the array:
db.groups.updateOne({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
{$set: {'order.0': 'b6386841-2ff7-4d90-af5d-7499dd49ca4b'}}
)
That correctly updates (or sets) the array value with index 0.
However, I want to set more array values and updateOne also supports a pipeline so I tried this:
db.slides.updateOne({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
[
{$set: {'order.0': 'b6386841-2ff7-4d90-af5d-7499dd49ca4b1'}}
]
)
This does NOTHING if the order array is empty. But if it's not, it replaces every element in the order array with an object { 0: 'b6386841-2ff7-4d90-af5d-7499dd49ca4b1' }.
I don't understand that behavior.
In the optimal case I would just do
db.slides.updateOne({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
[
{$set: {'order.0': 'b6386841-2ff7-4d90-af5d-7499dd49ca4b1'}},
{$set: {'order.1': 'otherid'}},
{$set: {'order.2': 'anotherone'}},
]
)
And that would just update the order array with the values.
What is happening here and how can I achieve my desired behavior?
The update by index position in the array is only supported in regular update queries, but not in aggregation queries,
They have explained this feature in regular update query $set operator documentation, but not it aggregation $set.
The correct implementation in regular update query:
db.slides.updateOne({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
{
$set: {
'order.0': 'b6386841-2ff7-4d90-af5d-7499dd49ca4b1',
'order.1': 'otherid',
'order.2': 'anotherone'
}
}
)
If you are looking for only an aggregation query, it is totally long process than the above regular update query, i don't recommend that way instead, you can format your input in your client-side language and use regular query.
If you have to use aggregation framework, try this (you will have to pass array of indexes and array of updated values separately):
$map and $range to iterate over the order array by indexes
$cond and $arrayElemAt to check if the current index is in the array of indexes that has to be updates. If it is, update it with the same index from the array of new values. If it is not, keep the current value.
NOTE: This will work only if the array of indexes that you want to update starts from 0 and goes up (as in your example).
db.collection.update({
_id: '831e0572-0f04-4d84-b1cf-64ffa9a12199'
},
[
{
"$set": {
"order": {
"$map": {
input: {
$range: [
0,
{
$size: "$order"
}
]
},
in: {
$cond: [
{
$in: [
"$$this",
[
0,
1,
2
]
]
},
{
$arrayElemAt: [
[
"b6386841-2ff7-4d90-af5d-7499dd49ca4b1",
"otherid",
"anotherone"
],
"$$this"
]
},
{
$arrayElemAt: [
"$order",
"$$this"
]
}
]
}
}
}
}
}
])
Here is the working example: https://mongoplayground.net/p/P4irM9Ouyza

need help writing aggregated query with grouping multiple fields

I am new to using mongodb and mongoose for my backend stack and Im having a hard time getting from SQL to NoSQL when it comes to query building.
I have an array of object that looks like this:
{
timestamp: "12313113",
symbol: "XY",
amount: 121212
value: 24324234
}
I want to query the collection to get the following output grouped by symbol:
{
symbol: xy,
occurences: 1231
summedAmount: 2131231
summedValue: 23131313
}
Could anyone tell me how to do it using aggregate on the Model? My timestamp filtering works already, but the grouping throws errors
let result = await TransactionEvent.aggregate([
{
$match : {
timestamp : { $gte: new Date(Date.now() - INTERVALS[timeframe]) }
}
},
{
$group : {
what to do in here
}
]);
Lets say I have another field in my object with a key of "direction" that can either be "IN" our "OUT". How could I also group the occurences of these values?
Expected output
{
symbol: xy,
occurences: 1231
summedAmount: 2131231
summedValue: 23131313
in: occurrences where direction property is "IN"
out: occurences where direction property is "OUT"
}
In MongoDB's $group stage, the _id key is mandatory and
it should be the keys which you want to be merged (It's symbol in your case).
Make sure that you pre-fix it with a `$ sign since you are referencing a key in your document.
Following the _id key, you can add all the additional operations to be performed for the required keys. In your specific use case, use $sum to add values to the user-defined key.
Note: Use "$sum": 1 to add 1 for each occurences ans "$sum": "$<Key-Name>" to add existing key's value.
Below code should be your $group stage
{
"$group": {
"_id": "$symbol", // Group by key (Use Sub-Object to group by multiple keys
"occurences": {"$sum": 1}, // Add `1` for each occurences
"summedAmount": {"$sum": "$amount"}, // Add `amount` values of grouped data
"summedValue": {"$sum": "$value"}, // Add `value` values of grouped data
}
}
Comment if you have any additional doubts.
You use $group and $sum
db.collection.aggregate([
{
"$group": {
"_id": "$symbol",
"summbedAmount": {
"$sum": "$amount"
},
"summbedValue": {
"$sum": "$value"
},
"occurences": {
$sum: 1
}
}
}
])
Working Mongo playground
Update 1
you can use $cond to check condition.
First parameter what is the condition
Second parameter - what we need to do if the condition is true (We need to increase by 1 if condition true)
Third parameter - what we need to do if the condition is false (No need to increase anything)
Here is the code
db.collection.aggregate([
{
"$group": {
"_id": "$symbol",
"summbedAmount": { "$sum": "$amount" },
"summbedValue": { "$sum": "$value" },
"occurences": { $sum: 1 },
in: {
$sum: {
$cond: [ { $eq: [ "$direction", "in" ] }, 1, 0 ]
}
},
out: {
$sum: {
$cond: [ { $eq: [ "$direction", "out" ] }, 1, 0 ] }
}
}
}
])
Working Mongo playground

Why can't I use $nin, $exists, etc. inside a Mongo pipeline match?

I've looked high and low for this answer and nothing has worked. I have a pipeline query with a match term like this:
$match: {
$expr: {
$and: [
....
]
}
}
Inside the $and I have all sorts of conditions using $eq, $in, $ne, $gt, $lt, etc.
However try as I may I can't get it to recognize $nin or $exists. I'm trying to add a term where I search for a key not existing, eg:
{ $exists: [ '$key', 0 ] }
I keep getting
MongoError: Unrecognized expression '$exists'
and
MongoError: Unrecognized expression '$nin'
Can anyone help??
You can only use aggregation operators inside the $expr and the $nin and $exists are query operators not aggregation ones. Use the above conditions outside the $expr expression.
Something like
{ "$match": {
"key": { "$exists": true },
"$expr": {
"$and": [
{...}, {...}
]
}
}}

mongodb lookup with collection and dynamic query output

I have some dynamic query for a collection
var condition = dynmic_query
db.collection.find(condition)
is giving me an output
now what I need whatever I am getting from previous query need to lookup with an another collection.
I am scared if this is not possible yet in mongodb
[https://jira.mongodb.org/browse/SERVER-22497]
I did some google but not getting idea how to achieve this one
I have to execute this query in node.js
Please help
Thanks
It sounds like you need the Aggregation Framework with the $lookup pipeline stage with a $match pipeline. It's a bit like doing a JOIN from the SQL world. Here's an example:
db.orders.aggregate([
{
$lookup:
{
from: "warehouses",
let: { order_item: "$item", order_qty: "$ordered" },
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$stock_item", "$$order_item" ] },
{ $gte: [ "$instock", "$$order_qty" ] }
]
}
}
},
{ $project: { stock_item: 0, _id: 0 } }
],
as: "stockdata"
}
}
])
You can not use one query output in second query. But you need to handle this at your code level.
hit one query and kept result in one variable then use that result to search in another query.
Mongodb is not meant for complex fetch. That's the whole reason it gives you high performance.

Most efficient way to check if element exists in a set

so in my MongoDB database I have a collection holding user posts.
Within that collection I have a set called "likes", which holds an array of the ids of the users that have liked that post. When querying I would like to pass a user id to my query and have a boolean in the result telling me whether the id exists in the array to see whether the user has already liked the post. I understand this would be easy to do with two queries, one to get the post and one to check if the user has liked it, but I would like to find the most efficient way to do this.
For example, one of my documents looks like this
{
_id: 24jef247jos991,
post: "Test Post",
likes: ["userid1", "userid2"]
}
When I query from "userid1" I would like the return
{
_id: 24jef247jos991,
post: "Test Post",
likes: ["userid1", "userid2"],
userLiked: true
}
But when I query from let's say "userid3" I would like
{
_id: 24jef247jos991,
post: "Test Post",
likes: ["userid1", "userid2"],
userLiked: false
}
You can add the $addFields stage checking each of the document likes arrays against the input user.
db.collection.aggregate( [
{
$addFields: {
"userLiked":{ $in: [ "userid1", "$likes" ] }
}
}
] )
Starting from MongoDB 3.4 you can use the $in aggregation operator to check if an array contains a given element. You can use the $addFields operator aggregation operator to add the newly computed value to your document without explicitly including other fields.
db.collection.aggregate( [
{ "$addFields": { "userLiked": { "$in": [ "userid1", "$likes" ] } } }
])
In MongoDB 3.2, you can use the $setIsSubset operator and the square bracket [] operator to do this. The downside of this approach is that you need to manually $project all the field in your document. Also the $setIsSubset operator with de-duplicate your array which may not be what you want.
db.collection.aggregate([
{ "$project": {
"post": 1, "likes": 1,
"userLiked": { "$setIsSubset": [ [ "userid3" ], "$likes" ] }
}}
])
Finally if your mongod version is 3.0 or older you need to use the $literal operator instead of the [] operator.

Resources