Assure order when using $skip and $limit in MongoDB - mongodb

I have data like this in my collection:
...
{ name: 'ad1cvbc', price: 300 },
{ name: 'tbcvbc3', price: 500 },
{ name: 'yjc5vbc', price: 500 },
{ name: 'at7vvbc', price: 500 },
{ name: 'a86v5bc', price: 500 },
{ name: 'a23dvbc', price: 500 },
{ name: 'adth4bc', price: 500 },
{ name: 'e34bc', price: 700 },
...
And I have an aggregate like this:
[
{ $match: { ... } },
{ $sort: { price: -1 } },
{ $skip: x },
{ $limit: 20 }
]
Where x is increased with 20 every single time I wants more data, but the problem is that, because I have multiple price with same value( and I have more than 20) sometimes I get the same document. How cand I assure that the order is the same and I don't get duplicates when I have many rows with the field for the sort having the same value.
Edit: So if I want to get 3 items each time I may have the following results:
1st call result:
{ name: 'ad1cvbc', price: 300 },
{ name: 'tbcvbc3', price: 500 },
{ name: 'yjc5vbc', price: 500 } // <- this
2st call result:
{ name: 'a23dvbc', price: 500 },
{ name: 'yjc5vbc', price: 500 }, // <- this
{ name: 'at7vvbc', price: 500 }
1 document is repeated because MongoDB doesn't get each time the items in order when the documents has the same value for the sort field.

Something like
[
{ $match: { ... } },
{ $sort: { name:-1, price: -1 } },
{ $skip: x },
{ $limit: x + 20 }
]

Related

MongoDB query on objects in an array based on condition

I have a players inventory that looks like this.
let inventory = [ { name: 'Wood', amount: 6 }, { name: 'Stone', amount: 2 } ]
This is the players resources.
I also have a list of craftable items.
{"name":"CraftingTable","craftingReagents":[{"name":"Stone","amount":"2"}]}
{"name":"CraftingTable2","craftingReagents":[{"name":"Wood","amount":"4"}]}
{"name":"CraftingTable3","craftingReagents":[{"name":"Wood","amount":"5"},{"name":"Stone","amount":"2"}]}
The items schema is as such
let itemSchema = new mongoose.Schema(
{
name:String,
craftingReagents: [
{
name: String,
amount: Number
}
]
}
);
I want a query that will return all craftable objects where the players inventory has sufficient resources to do so.
For example with 6 Wood and 2 Stone the query should return all 3 crafting tables, as the player has sufficient resources to craft all three
This is what I have so far, and I am very lost. Please help!
itemModel.find({
craftingReagents: {
$all: [{
$elemMatch: {
name: {
$in: [
'Wood'
]
},
amount: { $lte: 6 }
}
},
{
$elemMatch: {
name: {
$in: [
'Stone'
]
},
amount: { $lte: 2 }
}
}
]
}
});
Heres the thing. The players inventory can change to an infinite number of different resources. AND the craftable objects can have an infinite number of different requirments. I meerly gave an example of what the inventory, and craftable objects could look like.
How do you list documents that the player has enough resources to craft?
Try this :
itemModel.find({
$or: [{ craftingReagents: { $elemMatch: { name: 'Wood', amount: { $lte: 6 } } } },
{ craftingReagents: { $elemMatch: { name: 'Stone', amount: { $lte: 2 } } } }]
})

how to do summation of a field in the entire document along with the search?

I want to sum up an element's total whose value I can push into an array.
Basically I have a different type of documents whose data I wanted to push into the array along with the summation value.
I have some sample documents:-
{
school:"mnps",
name: 'sweta',
totalimages: 100,
state: 'downloaded',
},
{
school:"mnps",
name: 'sweta',
totalimages: 400,
state: 'allocated',
},
{
school:"mnps",
name: 'sweta',
totalimages: 200,
state: 'allocated'
},
{
school:"mnps",
name: 'sweta',
totalimages: 600,
state: 'allocated',
},
{
school:"mnps",
name: 'peter',
totalimages: 1000,
state: 'allocated',
}
Something that I have tried:-
return db.aggregate([
{
$match:{school:"mnps"}
},
{
$group:
{
"_id":{"name":"$name","state":"$state"},
"totalcount":{$sum:"$totalimages"}
}
},
{
"$group":
{
"_id":"$_id.name",
"states":{
"$push":{
"k":"$_id.state","v":"$totalcount"
}
}
}
},
{"$addFields":{
"states":{
"$arrayToObject":"$states"
}
}
])
Current Result :
[
{ _id: 'sweta', states: { allocated: 400, downloaded: 100 } },
{ _id: 'peter', states: { allocated: 1000 } },
]
Expected result :
[
{ _id: 'sweta', states: { allocated: 1200, downloaded: 100 } },
{ _id: 'peter', states: { allocated: 1000 } },
]
It's only taking the first document of the combination. If there is only a document for the combination then the count is correct but for multiple documents, the count is not correct.
For eg:-
1. peter only 1 state is there i.e allocated so the count is correct.
2. sweta 2 states are there allocated and downloaded. For downloaded the count is correct but for allocated the count is not correct because for allocated there is more than 1 document.

How to pull array of documents from array of documents?

I have a document like this
{
users: [
{
name: 'John',
id: 1
},
{
name: 'Mark',
id: 2
},
{
name: 'Mike',
id: 3
},
{
name: 'Anna',
id: 4
}
]
}
and I want to remove users from the array with ids 2 and 4. To do that I execute the following code:
const documents = [
{
id: 2
},
{
id: 4
},
]
Model.updateOne({ document_id: 1 }, { $pull: { users: { $in: documents } } });
But it doesn't remove any user.
Could you say me what I'm doing wrong and how to achieve the needed result?
This works if you can redefine the structure of your documents array:
const documents = [2, 4]
Model.updateOne({ document_id: 1 }, { $pull: { users: { id: { $in: documents } } } })

How to get documents from collection compared with object? custom filtering function?

I have an object:
{
name: "John",
size: 10,
volume: 20
}
Documents on collection:
[{
id:1,
name: "Sara",
size: 10
},
{
id:2,
volume: 20
},
{
id:3,
name: "John",
size: 10
},
{
id:4,
size: 20
}]
Now I need to filter my collection using object - if every field(except id) from each document exists in object and the values are equal - then this document should be in query results:
[{
id:1, //!EXCLUDE - name not Sara
name: "Sara",
size: 10
},
{
id:2, //OK - volume matches volume in object - return document in results
volume: 20
},
{
id:3, //OK - name and size matches object - return in results
name: "John",
size: 10
},
{
id:4, //!EXCLUDE - size don't match size on object
size: 20
}]
So the final response would be:
[{
id:2, //OK - volume matches volume in object - return document in results
volume: 20
},
{
id:3, //OK - name and size matches object - return in results
name: "John",
size: 10
}]
How can I do it with mongo find or others? Maybe I should write my custom filtering function?
You can try something like this.
The query will select all documents where each field when exists and is equal to the value.
db.collection.find({
$and: [{
$or: [{
name: {
$exists: false
}
}, {
name: "John"
}]
}, {
$or: [{
size: {
$exists: false
}
}, {
size: 10
}]
}, {
$or: [{
volume: {
$exists: false
}
}, {
volume: 20
}]
}]
})

When I need to run mapReduce in MongoDB

I'm newbie with MongoDB.
I have created a mapReduce on my Person collection to group cities.
db.Person.find()
[{
name: 'Bob',
addresses: [
{
street: 'Vegas Street',
neighborhood: {
name: 'Center',
city: {
name: 'Springfield'
}
}
}, {
.....
}
]
}, {
....
}]
And this is my mapReduce:
db.Person.mapReduce(function() {
for (var i = 0; i < this.address.length-1; i++) {
var address = this.address[i];
emit(address.neighborhood.city.name, 1);
}
}, function(k, v) {
return v.length;
}, { out: 'City' });
Then I use this to list my cities:
db.City.find().sort({'_id:', 1})
[{
_id: 'Springfield',
value: 3
}, {
_id: 'City B',
value: 2
}, {
...
}]
My question is about the City data, I need run the mapReduce each time I insert, update or delete on my Personcollection or it runs automatically?