Updating array with push and slice - mongodb

I have just started to play with MongoDB and have some questions about how I update my documents in the database. I insert two documents in my db with
db.userscores.insert({name: 'John Doe', email: 'john.doe#mail.com', levels : [{level: 1, hiscores: [90, 40, 25], achivements: ['capture the flag', 'it can only be one', 'apple collector', 'level complete']}, {level: 2, hiscores: [30, 25], achivements: ['level complete']}, {level: 3, hiscores: [], achivements: []}]});
db.userscores.insert({name: 'Jane Doe', email: 'jane.doe#mail.com', levels : [{level: 1, hiscores: [150, 90], achivements: ['Master of the universe', 'capture the flag', 'it can only be one', 'apple collector', 'level complete']}]});
I check if my inserting worked with the find() command and it looks ok.
db.userscores.find().pretty();
{
"_id" : ObjectId("5358b47ab826096525d0ec98"),
"name" : "John Doe",
"email" : "john.doe#mail.com",
"levels" : [
{
"level" : 1,
"hiscores" : [
90,
40,
25
],
"achivements" : [
"capture the flag",
"it can only be one",
"apple collector",
"level complete"
]
},
{
"level" : 2,
"hiscores" : [
30,
25
],
"achivements" : [
"level complete"
]
},
{
"level" : 3,
"hiscores" : [ ],
"achivements" : [ ]
}
]
}
{
"_id" : ObjectId("5358b47ab826096525d0ec99"),
"name" : "Jane Doe",
"email" : "jane.doe#mail.com",
"levels" : [
{
"level" : 1,
"hiscores" : [
150,
90
],
"achivements" : [
"Master of the universe",
"capture the flag",
"it can only be one",
"apple collector",
"level complete"
]
}
]
}
How can I add/update data to my userscores? Lets say I want to add a hiscore to user John Doe on level 1. How do I insert the hiscore 75 and still have the hiscore array sorted? Can I limit the number of hiscores so the array only contains 3 elements? I have tried with
db.userscores.aggregate(
// Initial document match (uses name, if a suitable one is available)
{ $match: {
name : 'John Doe'
}},
// Expand the levels array into a stream of documents
{ $unwind: '$levels' },
// Filter to 'level 1' scores
{ $match: {
'levels.level': 1
}},
// Add score 75 with cap/limit of 3 elements
{ $push: {
'levels.hiscore':{$each [75], $slice:-3}
}}
);
but it wont work, the error I get is "SyntaxError: Unexpected token [".
And also, how do I get the 10 highest score from all users on level 1 for example? Is my document scheme ok or can I use a better scheme for storing users hiscores and achivements on diffrent levels for my game? Is there any downsides on quering or performance using they scheme above?

You can add the score with this statement:
db.userscores.update(
{ "name": "John Doe", "levels.level": 1 },
{ "$push": { "levels.$.hiscores": 75 } } )
This will not sort the array as this is only supported if your array elements are documents.
In MongoDB 2.6 you can use sorting also for non-document arrays:
db.userscores.update(
{ "name": "John Doe", "levels.level": 1 },
{ "$push": { "levels.$.hiscores": { $each: [ 75 ], $sort: -1, $slice: 3 } } } )

Related

MongoDB $elemMatch updates the wrong element when an additional query is present

I am facing a weird issue with MongoDB. I am using the official mongo client to use it from NodeJS.
My data sort of looks like this
// collection "products"
{
shop: 'ShopID1',
customer: ['CustomerID1', 'CustomerID2'],
products: [
{product: 'ProductID1', productCount: 10, customer: ['CustomerID1']},
{product: 'ProductID2', productCount: 5, customer: ['CustomerID2']},
]
}
Now, I want to remove 'CustomerID2' from the 2nd product object if it exists on both the outer customer array and in the inner customer array of the 2nd product object.
I can do it with the following query,
const productID = 'ProductID2';
const customerID = 'CustomerID2';
db
.collection('products')
.findOneAndUpdate(
{
shop: 'ShopID1',
// customer: {
// $in: [customerID]
// },
products: {
$elemMatch: {
product: productID,
customer: {
$in: [customerID]
}
}
},
},
{
$inc: {
'products.$.productCount': 1
},
$pull: {
'products.$.customer': customerID,
customer: customerID
}
},
);
The problem is, if I uncomment the commented part above, it updates the first product object, which is wrong. I can't find the reason why this happens as my knowledge of Mongo is limited. It seems to me the uncommented part is only adding an additional constraint that should not affect the update operation.
I would also appreciate any feedback on whether this is the correct way to achieve my stated goal. Thanks
//data preparation check
> db.custProducts.find().pretty();
{
"_id" : ObjectId("5f5e6d68598d922a1e6eff5c"),
"shop" : "ShopID1",
"customer" : [
"CustomerID1",
"CustomerID2"
],
"products" : [
{
"product" : "ProductID1",
"productCount" : 10,
"customer" : [
"CustomerID1"
]
},
{
"product" : "ProductID2",
"productCount" : 5,
"customer" : [
"CustomerID2"
]
}
]
}
//code to remove customerID2 field in both outer array and inner object
//.$. to be used to traverse the document object
> db.custProducts.aggregate([
... {$unwind:"$customer"},
... {$unwind:"$products"},
... {$match:{
... "shop": "ShopID1",
... "customer": "CustomerID2",
... "products.product": "ProductID2"
... }
... }
... ]).forEach(function(doc){
... print("prod: ",doc.products.product);
... print("cust: ",doc.customer);
... db.custProducts.update(
... {"shop": doc.shop, "products.product": "ProductID2" },
... {$pull:
... {
... customer:{$in:[doc.customer]},
... "products.$.customer":{$in:[doc.customer]}
... }
... }
... );
... }
... );
prod: ProductID2
cust: CustomerID2
//check the output of the update execution.
> db.custProducts.find().pretty();
{
"_id" : ObjectId("5f5e6d68598d922a1e6eff5c"),
"shop" : "ShopID1",
"customer" : [
"CustomerID1"
],
"products" : [
{
"product" : "ProductID1",
"productCount" : 10,
"customer" : [
"CustomerID1"
]
},
{
"product" : "ProductID2",
"productCount" : 5,
"customer" : [ ]
}
]
}
>
//further,you can add other logic like $inc in the above code

How to boost Mongodb search result based on Criteria given

I am working on the requirement where have write query in which if users enters any acronym of university(Ex: MIT) have to get the result from database. JSON looks like this:
{
"_id" : ObjectId("5d68cdcac8acd826e6a386b2"),
"name" : "Massachusetts Institute of Technology",
"acronyms" : [
"MIT"
]
}
,
{
"_id" : ObjectId("5d68ce0bc8acd826e6a45b29"),
"name" : "Manukau Institute of Technology",
"acronyms" : [
"MIT"
]
}
User might input "Name" as well. I have written "OR" query for that.
db.getCollection('universityCollection').find(
{$or: [{"name":"MIT"},{"acronyms":"MIT"}]}
)
Now my requirement is if users enters "input" and if it matches with acronym it should return it first after that it will return items which matches with name.
Current or query is not returning expected order.
Any pointers will help.
Please try below query.
db.getCollection('test').aggregate(
{ $match : { $or : [{ "name":"MIT" }, {"acronyms":"MIT" } ] } }
,{ "$project": {
"name": 1,
"acronyms": 1,
"sortOrder": {
"$setIsSubset": [ ["MIT" ] , "$acronyms" ] }
}
}
,{ "$sort": { "sortOrder": -1 } }
)
If you are not familiar with MongoDB aggregates, check the below links.
https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/
https://docs.mongodb.com/manual/reference/operator/aggregation/setIsSubset/

Mongodb- using find() method on an Array of Objects only return first match instead of all

Unlike the other question someone asked where they wanted only one item returned. I HAVE one item returned and I need ALL of the matching objects in the array return. However the second object that matches my query is being completely ignored.
This is what one of the items in the item collection looks like:
{
name: "soda",
cost: .50,
inventory: [
{ flavor: "Grape",
amount: 8 },
{ flavor: "Orange",
amount: 4 },
{ flavor: "Root Beer",
amount: 15 }
]
}
Here is the query I typed in to mongo shell:
Items.find({"inventory.amount" : { $lte : 10} } , { name : 1, "inventory.$.flavor" : 1})
And here is the result:
"_id" : ObjectId("59dbe33094b70e0b5851724c"),
"name": "soda"
"inventory" : [
{ "flavor" : "Grape",
"amount" : 8,
}
]
And here is what I want it to return to me:
"_id" : ObjectId("59dbe33094b70e0b5851724c"),
"name": "soda"
"inventory" : [
{ "flavor" : "Grape",
"amount" : 8
},
{ "flavor" : "Orange",
"amount" : 4
}
]
I'm new to mongo and am dabbling to get familiar with it. I've read through the docs but couldn't find a solution to this though it's quite possible I overlooked it. I'd really love some help. Thanks in advance.
first u can get your result by this query
db.Items.find({"inventory.amount" : { $lte : 10} } , { name : 1, "inventory.flavor" : 1 , "inventory.amount" : 1})

Increment nested value

I create players the following way.
Players.insert({
name: name,
score: 0,
items: [{'name': 0}, {'name2': 0}...]
});
How do I increment the score in a specific player and specific item name (upserting if necessary)?
Sorry for the terrible wording :p
Well, the answer is - as in life - to simplify the problem by breaking it up.
And to avoid arrays in mongoDB - after all, objects can have as many keys as you like. So, my structure became:
{
"_id": <id>,
"name": <name>,
"score": <score>,
"items": {}
}
And to increment the a dynamic key in items:
// create your update skeleton first
var ud = { $inc: {} };
// fill it in
ud.$inc['item.' + key] = value;
// call it
db.Players.update(player, ud, true);
Works a charm :)
Lets say you have:
{
"_id" : ObjectId("5465332e6c3e2eeb66ef3683"),
"name" : "Alex",
"score" : 0,
"items" : [
{
"food" : 0
}
]
}
To update you can do:
db.Players.update({name: "Alex", "items.food": {$exists : true}},
{$inc: {score: 1, "items.$.food": 5}})
Result:
{
"_id" : ObjectId("5465332e6c3e2eeb66ef3683"),
"name" : "Alex",
"score" : 1,
"items" : [
{
"food" : 5
}
]
}
I am not sure you can upsert if the document doesn't exist because of the positional operator needed to update the array.

Mongodb upsert embedded document

I have a document per day per meter. How can I add another subdocument in the data array and create the whole document if he doesn't exists ?
{
"key": "20120418_123456789",
"data":[
{
"Meter": 123456789,
"Dt": ISODate("2011-12-29T16:00:00.0Z"),
"Energy": 25,
"PMin": 11,
"PMax": 16
}
],
"config": {"someparam": 4.5}
}
Can I use upsert for that purpose ?
The result will be if document exists :
{
"key": "20120418_123456789",
"data":[
{
"Meter": 123456789,
"Dt": ISODate("2011-12-29T16:00:00.0Z"),
"Energy": 25,
"PMin": 11,
"PMax": 16
},
{
"Meter": 123456789,
"Dt": ISODate("2011-12-29T16:15:00.0Z"),
"Energy": 22,
"PMin": 13,
"PMax": 17
}
],
"config": {"someparam": 4.5}
}
Thanks in advance
I think what you want is the $addToSet command - that will push an element to an array only if it does not already exist. I've simplified your example a bit for brevity:
db.meters.findOne()
{
"_id" : ObjectId("4f8e95a718bc9c7da1e6511a"),
"config" : {
"someparam" : 4.5
},
"data" : [
{
"Meter" : 123456789,
}
],
"key" : "20120418_123456789"
}
Now run:
db.meters.update({"key" : "20120418_123456789"}, {"$addToSet": {"data" : {"Meter" : 1234}}})
And we get the updated version:
db.meters.findOne()
{
"_id" : ObjectId("4f8e95a718bc9c7da1e6511a"),
"config" : {
"someparam" : 4.5
},
"data" : [
{
"Meter" : 123456789,
},
{
"Meter" : 1234
}
],
"key" : "20120418_123456789"
}
Run the same command again and the result is unchanged.
Note: you are likely going to be growing these documents, especially if this field is unbounded and causing frequent (relatively expensive) moves by updating in this way - you should have a look here for ideas on how to mitigate this:
http://www.mongodb.org/display/DOCS/Padding+Factor#PaddingFactor-ManualPadding