Mongo field A greater than field B plus a value [duplicate] - mongodb

This question already has answers here:
Mongo field A greater than field B
(4 answers)
Closed 5 years ago.
I want to get records of a MongoDB collection that sticks to this condition: fieldA > fieldB + someNaturalValue. This is what I tried so far:
db.getCollection('collection').find({
$where: function() {
return this.fieldA > this.fieldB + 10000}
});
// or
db.getCollection('collection').aggregate([
{ "$project" : {
"sum" : {"$add" : ["$fieldB", 10000]}
}
},
{ "$match" : {
"sum" : {"$lte" : "$fieldA"}
}
}
]);
The issue I face here is the extra value that I need to add in the condition to one of the fields. Those are not working, that value is not taken into account. What I am missing? I appreciate any kind of help.
Sample Data
db.collection.insert({fieldA : 21000, fieldB : 10000}); //1 ok
db.collection.insert({fieldA : 15000, fieldB : 8000}); //2 nok
db.collection.insert({fieldA : 24000, fieldB : 22000}); //3 nok
db.collection.insert({fieldA : 22000, fieldB : 1000}); //4 ok

Adjusted code from the duplicated question:
db.collection.aggregate([
{$project: {
cmp_value: {$cmp: ['$fieldA', {$add: ['$fieldB', 10000]}]},
obj: '$$ROOT'
}},
{$match: {cmp_value: {$gt: 0}}},
{ $replaceRoot: { newRoot: '$obj' } }
])
$where should be avoided where possible. Documentation is quite clear about it:
The $where provides greater flexibility, but requires that the database processes the JavaScript expression or function for each document in the collection

Related

How to improve aggregate pipeline

I have pipeline
[
{'$match':{templateId:ObjectId('blabla')}},
{
"$sort" : {
"_id" : 1
}
},
{
"$facet" : {
"paginatedResult" : [
{
"$skip" : 0
},
{
"$limit" : 100
}
],
"totalCount" : [
{
"$count" : "count"
}
]
}
}
])
Index:
"key" : {
"templateId" : 1,
"_id" : 1
}
Collection has 10.6M documents 500k of it is with needed templateId.
Aggregate use index
"planSummary" : "IXSCAN { templateId: 1, _id: 1 }",
But the request takes 16 seconds. What i did wrong? How to speed up it?
For start, you should get rid of the $sort operator. The documents are already sorted by _id since the documents are already guaranteed to sorted by the { templateId: 1, _id: 1 } index. The outcome is sorting 500k which are already sorted anyway.
Next, you shouldn't use the $skip approach. For high page numbers you will skip large numbers of documents up to almost 500k (rather index entries, but still).
I suggest an alternative approach:
For the first page, calculate an id you know for sure falls out of the left side of the index. Say, if you know that you don't have entries back dated to 2019 and before, you can use a match operator similar to this:
var pageStart = ObjectId.fromDate(new Date("2020/01/01"))
Then, your match operator should look like this:
{'$match' : {templateId:ObjectId('blabla'), _id: {$gt: pageStart}}}
For the next pages, keep track of the last document of the previous page: if the rightmost document _id is x in a certain page, then pageStart should be x for the next page.
So your pipeline may look like this:
[
{'$match' : {templateId:ObjectId('blabla'), _id: {$gt: pageStart}}},
{
"$facet" : {
"paginatedResult" : [
{
"$limit" : 100
}
]
}
}
]
Note, that now the $skip is missing from the $facet operator as well.

MongoDb Need help in finding highest value in database using aggregate [duplicate]

This question already has answers here:
how to get the max value of a field in MongoDB
(2 answers)
mongodb how to get max value from collections
(9 answers)
MongoDB find and return all with max value
(1 answer)
Closed 2 years ago.
I am working on a query to find the subject title, subject type, and credit value of subject with highest credit value. This is the query I have come up with:
db.Subject.aggregate([{$match:{"subject.credit":{$max:true}},
{$project:{"subject.subTitle":1, "subject.type":1, "subject.credit":1, _id: 0}}
]).pretty()
This doesn't seem to net anything.
and
db.Subject.aggregate([{$match:{"subject.credit":$max}},
$project:{"subject.subTitle":1, "subject.type":1, "subject.credit":1, _id: 0}}
]).pretty()
This nets and error as shown below:
E QUERY [js] ReferenceError: $max is not defined :
#(shell):1:32
Here is a part of the database that contains the highest credit value:
db.Subject.insert(
{
"_id":ObjectId(),
"subject":{
"subCode":"CSCI321",
"subTitle":"Final Year Project",
"credit":6,
"type":"Core",
"assessments": [
{ "assessNum": 1,
"weight":30,
"assessType":"Presentation",
"description":"Prototype demonstration" },
{ "assignNum": 2,
"weight":70,
"assessType":"Implementation and Presentation",
"description":"Final product Presentation and assessment of product implementation by panel of project supervisors" }
]
}
}
)
A good strategy, if you don't have an index, is to collapse all documents and get one value:
const pipeline = [
{
"$project" : {
"subTitle" : "$subject.subTitle",
"type" : "$subject.type",
"credit" : "$subject.credit",
"_id" : 0
}
},
{
"$sort" : {
"credit" : -1
}
},
{
"$limit" : 1
}
]
db.Subject.aggregate(pipeline)
Query
$project We want only a subset of fields
$sort to get the highest credit first.
$limit:1 to get only the highest.
db.Subject.find({},{"subject.subTitle":1, "subject.type":1, "subject.credit":1,"_id":0}).sort({"subject.credit":-1})
just use this

Simple $match greater than $gt not working [duplicate]

This question already has answers here:
compare two fields of same document [duplicate]
(1 answer)
MongoDb query condition on comparing 2 fields
(4 answers)
Closed 3 years ago.
This is the result of an aggregate query,
{
"_id" : ObjectId("5dab3240dfbe9a15cd69771d"),
"isManual" : false,
"frequency" : 60,
"lastExecuted" : ISODate("2019-10-21T03:38:15.114Z"),
"lastExecutedTimeFromNow" : 129.58105
}
{
"_id" : ObjectId("5dad47c65310a16581cc6294"),
"isManual" : false,
"frequency" : 50,
"lastExecuted" : ISODate("2019-10-25T00:00:00.000Z"),
"lastExecutedTimeFromNow" : 100
}
{
"_id" : ObjectId("5dad48a55310a16581cc6332"),
"isManual" : true,
"frequency" : 100,
"lastExecuted" : ISODate("2019-10-23T00:00:00.000Z"),
"lastExecutedTimeFromNow" : 50
}
I wanted to filter the documents where the field lastExecutedTimeFromNow greater than frequency. But it returns 0 results.
Here's the aggregate query I'm using,
db.getCollection('test').aggregate([
{
$match: {
"lastExecutedTimeFromNow": { $gte: "$frequency" }
}
}
])
Any clue on where I'm going wrong or any help on this would really be great.
You can use $expr but keep in mind it's slower than normal $match
db.getCollection('test').aggregate([
{
$match: {
$expr: {
$gte: [
"$lastExecutedTimeFromNow",
"$frequency"
]
}
}
}
])

MongoDB $divide on aggregate output

Is there a possibility to calculate mathematical operation on already aggregated computed fields?
I have something like this:
([
{
"$unwind" : {
"path" : "$users"
}
},
{
"$match" : {
"users.r" : {
"$exists" : true
}
}
},
{
"$group" : {
"_id" : "$users.r",
"count" : {
"$sum" : 1
}
}
},
])
Which gives an output as:
{ "_id" : "A", "count" : 7 }
{ "_id" : "B", "count" : 49 }
Now I want to divide 7 by 49 or vice versa.
Is there a possibility to do that? I tried $project and $divide but had no luck.
Any help would be really appreciated.
Thank you,
From your question, it looks like you are assuming result count to be 2 only. In that case I can assume users.r can have only 2 values(apart from null).
The simplest thing I suggest is to do this arithmetic via javascript(if you're using it in mongo console) or in case of using it in progam, use the language you're using to access mongo) e.g.
var results = db.collection.aggregate([theAggregatePipelineQuery]).toArray();
print(results[0].count/results[1].count);
EDIT: I am sharing an alternative to above approach because OP commented about the constraint of not using javascript code and the need to be done only via query. Here it is
([
{ /**your existing aggregation stages that results in two rows as described in the question with a count field **/ },
{ $group: {"_id": 1, firstCount: {$first: "$count"}, lastCount: {$last: "$count"}
},
{ $project: { finalResult: { $divide: ['$firstCount','$lastCount']} } }
])
//The returned document has your answer under `finalResult` field

MongoDB fetch documents with sort by count

I have a document with sub-document which looks something like:
{
"name" : "some name1"
"like" : [
{ "date" : ISODate("2012-11-30T19:00:00Z") },
{ "date" : ISODate("2012-12-02T19:00:00Z") },
{ "date" : ISODate("2012-12-01T19:00:00Z") },
{ "date" : ISODate("2012-12-03T19:00:00Z") }
]
}
Is it possible to fetch documents "most liked" (average value for the last 7 days) and sort by the count?
There are a few different ways to solve this problem. The solution I will focus on uses mongodb's aggregation framework. First, here is an aggregation pipeline that will solve your problem, following it will be an explanation/breakdown of what is happening in the command.
db.testagg.aggregate(
{ $unwind : '$likes' },
{ $group : { _id : '$_id', numlikes : { $sum : 1 }}},
{ $sort : { 'numlikes' : 1}})
This pipeline has 3 main commands:
1) Unwind: this splits up the 'likes' field so that there is 1 'like' element per document
2) Group: this regroups the document using the _id field, incrementing the numLikes field for every document it finds. This will cause numLikes to be filled with a number equal to the number of elements that were in "likes" before
3) Sort: Finally, we sort the return values in ascending order based on numLikes. In a test I ran the output of this command is:
{"result" : [
{
"_id" : 1,
"numlikes" : 1
},
{
"_id" : 2,
"numlikes" : 2
},
{
"_id" : 3,
"numlikes" : 3
},
{
"_id" : 4,
"numlikes" : 4
}....
This is for data inserted via:
for (var i=0; i < 100; i++) {
db.testagg.insert({_id : i})
for (var j=0; j < i; j++) {
db.testagg.update({_id : i}, {'$push' : {'likes' : j}})
}
}
Note that this does not completely answer your question as it avoids the issue of picking the date range, but it should hopefully get you started and moving in the right direction.
Of course, there are other ways to solve this problem. One solution might be to just do all of the sorting and manipulations client-side. This is just one method for getting the information you desire.
EDIT: If you find this somewhat tedious, there is a ticket to add a $size operator to the aggregation framework, I invite you to watch and potentially upvote it to try and speed to addition of this new operator if you are interested.
https://jira.mongodb.org/browse/SERVER-4899
A better solution would be to keep a count field that will record how many likes for this document. While you can use aggregation to do this, the performance will likely be not very good. Having a index on the count field will make read operation fast, and you can use atomic operation to increment the counter when inserting new likes.
You can use this simplify the above aggregation query by the following from mongodb v3.4 onwards:
> db.test.aggregate([
{ $unwind: "$like" },
{ $sortByCount: "$_id" }
]).pretty()
{ "_id" : ObjectId("5864edbfa4d3847e80147698"), "count" : 4 }
Also as #ACE said you can now use $size within a projection instead:
db.test.aggregate([
{ $project: { count: { $size : "$like" } } }
]);
{ "_id" : ObjectId("5864edbfa4d3847e80147698"), "count" : 4 }