Mongodb query use skip and limit function in first N documents - mongodb

Can we make query which enable us to use skip and limit in first N documents?
Ex :
Suppose there are nearly 500 documents in a collection called teachers.
I want a query which restrict me to read first 300 documents only.
If I use skip(300) in that query it should display null.
db.teachers.find().pretty()
{
id : 1,
name : "teach001"
},
{
id : 2,
name : "teach002"
},
{
id : 3,
name : "teach003"
},
{
id : 4,
name : "teach004"
},
{
id : 5,
name : "teach005"
},
{
id : 6,
name : "teach006"
},
{
id : 7,
name : "teach007"
},
{
id : 8,
name : "teach008"
},
{
id : 9,
name : "teach009"
},
{
id : 10,
name : "teach0010"
}
db.teachers.find({some query here to restrict access first 5 documents only }).skip(5).limit(5).pretty()

Aggregation
I don't think there is a way to do exactly what you are requesting. If you are open to using the aggregation framework, it can be accomplished easily.
db.teachers.aggregate([{$limit: 5}, {$skip: 5}])
View
If you are open to creating a view, you could even create a view that enforces the limit
db.createView('limitedTeachers', 'teachers', [{$limit: 5}])
Then you can use find on the view:
db.limitedTeachers.find({}).skip(5)
Two finds
If you cannot use aggregation, you have the option of using 2 finds in your query. First, find the n number of Object IDs. Then, limit your second query to only those Object IDs.
var ids = [];
db.teachers.find({}, {_id: 1}).limit(5).forEach(function(doc){
ids.push(doc._id);
});
db.teachers.find({ _id: {$in: ids} }).skip(5)
Or as the same type of query but closer to the format you had in your question
db.teachers.find({$or: db.teachers.find({}, {_id: 1}).limit(5).toArray()}).skip(5)

Related

Update multiple array elements with $inc

I have the following document
{store : {
id : 'STORE1',
name : 'Electronics Store',
locations : [
{
id : 'LOC1',
quantity : 4
},
{
id : 'LOC2',
quantity : 10
},
{
id : 'LOC3',
quantity : 5
}
]
}
}
I want to update the quantity of multiple elements of the locations array based on their id field using $inc.
For example I want to update id : 'LOC1' with +5 to the quantity field and id : LOC3 with +2 to its quantity field.
Is it possible to do this with one query in mongodb instead of using multiple queries for each location.
You can make use of filtered positional operator $[<identifier>]. Below code will be helpful:
db.collection.update(
{},
{$inc: {'store.locations.$[elem1].quantity': 5, 'store.locations.$[elem2].quantity': 2}},
{arrayFilters: [{'elem1.id': 'LOC1'}, {'elem2.id': 'LOC3'}]}
)

Insert and return ID of sub-document in MongoDB document sub-document array

My node.js application will insert a sub-document into a nested sub-document array field of the following MongoDB document, and I need to determine the ID of the newly inserted sub-document:
{
"_id" : ObjectId("578d5a52cc13117022e09def"),
"name" : "Grade 5 - Section A",
"scores" : [{
"studentId" : ObjectId("5776bd36ffc8227405d364d2"),
"performance" : [{
"_id" : ObjectId("57969b8fc164a21c20698261"),
"subjectId" : ObjectId("577694ecbf6f3a781759c54a"),
"score" : 86,
"maximum" : 100,
"grade" : "B+"
}]
}]
}
The sub-document looks like this:
{
"subjectId" : ObjectId("5776ffe1804540e29c602a62"),
"score" : 74,
"maximum" : 100,
"grade" : "A-"
}
I am adding the sub-document using the following Mongoose code:
Class.update({
_id: '578d5a52cc13117022e09def',
'scores.studentId': '5776bd36ffc8227405d364d2'
}, {
$addToSet: {
'scores.$.performance': {
'subjectId' : '5776ffe1804540e29c602a62',
'score' : 74,
'maximum' : 100,
'grade' : 'A-'
}
}
}, function(err, result) {
if (err) {
throw err;
}
console.log(result);
});
The subject sub-document gets added in the performance sub-document array which is itself nested in the scores sub-document array. Notice that the newly inserted sub-document is assigned with its own ID, as instituted by the defined schema. Even if I get back the entire document, that's not very helpful. I specifically need the ID of that newly inserted sub-document. What is the recommended approach to this problem?
In this case I prefer pre-assign the ID to the sub-document (i.e. sub._id = ObjectId() or use uuid package if you prefer uuid): is clear and predictable.
Also remember that if you frequent query by a subdoc id is good to add (using ensureIndex()) an index for this use case in the collection.
There is a good solution for that, try to use the methods create and push of the MongooseArrays.
In your code you could return the Student and do something like this:
const newPerformance = student.performance.create(newData);
student.performance.push(newPerformance);
const updatedStudent = await student.save();
if (updatedStudent) return newPerformance._id;
This is just a simple example.
Using the create method of MongooseArrays, link for doc, the mongoose will create an _id and do all the validations and casts it needs, so if the save process is fine the created subdocument you could just use the _id of the subdocument you got with the create method.

Multiple update in a document in MongoDB

I am trying to update multiple nested documents in a document in mongoDB.
Say my data is:
{
"_id" : "ObjectId(7df78ad8902c)",
"title" : "Test",
"img_url" : "[{s: 1, v:1}, {s: 2, v: 2}, {s: 3, v: 3}]",
"tags" : "['mongodb', 'database', 'NoSQL']",
"likes" : "100"
}
I want to update v to 200 for s = 1 and s= 2 in img_url list.
It is easy to update v for any single s.
Is there any way to update multiple documents satisfying some criteria.
I tried:
db.test.update({ "_id" : ObjectId("7df78ad8902c"), "img_url.s": {$in : ["1", "2"]}}, {$set: { "img_url.$.v" : 200 } });
and
db.test.update({ "_id" : ObjectId("7df78ad8902c"), "img_url.s": {$in : ["1", "2"]}}, {$set: { "img_url.$.v" : 200 } }, {mulit: true});
Some sources are suggesting it is not possible to do so.
Multiple update of embedded documents' properties
https://jira.mongodb.org/browse/SERVER-1243
Am I missing something ?
For the specific case/example you have here. You are specifying an _id which means you are to update only one with that specific _id.
to update img_url try without the _id; something like this:
db.test.update({}, {"$set":{"img_url.0":{s:1, v:400}}}, {multi:true})
db.test.update({}, {"$set":{"img_url.1":{s:2, v:400}}}, {multi:true})
0 and 1 in img_url are the array indexes for s:1 and s:2
in order to update based on specific criteria you need to set the attribute you need on the first argument. say for example, to update all documents that have likes greater than 100 increment by 1 you do (assuming likes type is int...):
db.people.update( { likes: {$gt:100} }, {$inc :{likes: 1}}, {multi: true} )
hope that helps

Sorting with condition in mongodb

Here is the example collection:
[
{'name': 'element1', ratings: {'user1': 1, 'user2':2}},
{'name': 'element2', ratings: {'user1': 2, 'user2':1}}
]
I want to sort for user1: ['element2', 'element1'] ,
and for user2: ['element1', 'element2'].
In other words, I want to place the results with maximum rating for current user to top.
The collection is very big, thats why search must use indexes. The collection structure can be modified, this is just example.
Those are sorts on different fields. The first is a sort on { "ratings.user1" : -1 } and the second is a sort on { "ratings.user2" : -1 }; you will need an index on each field to support each sort. You can't scale a set up like that beyond a few users. I don't know what the entire use case is for these documents, but if the core requirement is to sort elements for a user based on the user's ratings, I would restructure the collection so that a single document represents a rating of a particular element by a particular user:
{
"_id" : ObjectId(...),
"element" : "element1",
"user" : "user1",
"rating" : 1
},
{
"_id" : ObjectId(...),
"element" : "element1",
"user" : "user2",
"rating" : 2
}
If you create an index on { "user" : 1, "rating" : -1 }, you can perform indexed queries for the ratings of elements by a particular user sorted descending by rating:
db.ratings.find({ "user" : "user1" }).sort({ "rating" : -1 })

matching fields internally in mongodb

I am having following document in mongodb
{
"_id" : ObjectId("517b88decd483543a8bdd95b"),
"studentId" : 23,
"students" : [
{
"id" : 23,
"class" : "a"
},
{
"id" : 55,
"class" : "b"
}
]
}
{
"_id" : ObjectId("517b9d05254e385a07fc4e71"),
"studentId" : 55,
"students" : [
{
"id" : 33,
"class" : "c"
}
]
}
Note: Not an actual data but schema is exactly same.
Requirement: Finding the document which matches the studentId and students.id(id inside the students array using single query.
I have tried the code like below
db.data.aggregate({$match:{"students.id":"$studentId"}},{$group:{_id:"$student"}});
Result: Empty Array, If i replace {"students.id":"$studentId"} to {"students.id":33} it is returning the second document in the above shown json.
Is it possible to get the documents for this scenario using single query?
If possible, I'd suggest that you set the condition while storing the data so that you can do a quick truth check (isInStudentsList). It would be super fast to do that type of query.
Otherwise, there is a relatively complex way of using the Aggregation framework pipeline to do what you want in a single query:
db.students.aggregate(
{$project:
{studentId: 1, studentIdComp: "$students.id"}},
{$unwind: "$studentIdComp"},
{$project : { studentId : 1,
isStudentEqual: { $eq : [ "$studentId", "$studentIdComp" ] }}},
{$match: {isStudentEqual: true}})
Given your input example the output would be:
{
"result" : [
{
"_id" : ObjectId("517b88decd483543a8bdd95b"),
"studentId" : 23,
"isStudentEqual" : true
}
],
"ok" : 1
}
A brief explanation of the steps:
Build a projection of the document with just studentId and a new field with an array containing just the id (so the first document it would contain [23, 55].
Using that structure, $unwind. That creates a new temporary document for each array element in the studentIdComp array.
Now, take those documents, and create a new document projection, which continues to have the studentId and adds a new field called isStudentEqual that compares the equality of two fields, the studentId and studentIdComp. Remember that at this point there is a single temporary document that contains those two fields.
Finally, check that the comparison value isStudentEqual is true and return those documents (which will contain the original document _id and the studentId.
If the student was in the list multiple times, you might need to group the results on studentId or _id to prevent duplicates (but I don't know that you'd need that).
Unfortunately it's impossible ;(
to solve this problem it is necessary to use a $where statement
(example: Finding embeded document in mongodb?),
but $where is restricted from being used with aggregation framework
db.data.find({students: {$elemMatch: {id: 23}} , studentId: 23});