addToSet for an array in an array - mongodb

Given this sample document:
> db.sample.find().pretty()
{
"_id" : ObjectId("570f76ca4fe66c8ae29f13cd"),
"a" : [
{
"b" : [
1,
2,
3
]
},
{
"b" : [
1,
2,
3,
4
]
},
{
"b" : [
4
]
}
]
}
I'm trying to add the number 4 to b array for each instance in the a array
I had hoped that
db.sample.update({},{$addToSet:{"a.b":4}})
would do the trick, but this yields the error:
cannot use the part (a of a.b) to traverse the element ({a: [ { b: [ 1.0, 2.0, 3.0 ] }, { b: [ 1.0, 2.0, 3.0, 4.0 ] }, { b: [ 4.0 ] } ]})
Is such a update possible? Obviously I can pull each document to the client side update and replace, but that's really only a last resort.

It looks like until SERVER-1243 Jira is implemented, you'll have to do it one-by-one for each item in the array, e.g.:
db.sample.update({},{$addToSet:{"a.0.b":4}})
db.sample.update({},{$addToSet:{"a.1.b":4}})
If you only need to update first element you could have used:
db.sample.update({},{$addToSet:{"a.$.b":4}})

Related

how to update mongodb with two field

db.auto_complete.find()
{ "_id" : ObjectId("6239a3c93a8e84c1b46d1bf4"), "category" : "음식", "keyword" : "삼겹살", "weight" : 0, "shard" : 5, "searchCount" : 3, "satisfactionCount" : 4, "force" : false }
but i want to update weight = (searchCount X 0.5 + satisfactionCount X 0.5)
my wanted result =
{ "_id" : ObjectId("6239a3c93a8e84c1b46d1bf4"), "category" : "음식", "keyword" : "삼겹살", "weight" : 3, "shard" : 5, "searchCount" : 3, "satisfactionCount" : 4, "force" : false }
weight = ( 3(searchCount)X0.5 + 4(satisfactionCount)X0.5 ) =>3.5 (+round off) => 3
i have tried with $set but i didn't solved this problem how to query this problem with mongo
and if weight have decimal point i want round off the decimal point
please help me....
Maybe something like this:
db.collection.update({},
[
{
$addFields: {
// weight = (searchCount X 0.5 + satisfactionCount X 0.5)
weight: {
$trunc: [
{
"$sum": [
{
"$multiply": [
"$searchCount",
0.5
]
},
{
"$multiply": [
"$satisfactionCount",
0.5
]
}
]
},
0
]
}
}
}
],{multi:true})
Explained:
Use the the update with aggregation pipeline method ( mongodb 4.2+ ) to replace the weight value with the rounded calculated based on provided formula. Add the option { multi:true } to apply to all documents in the collection.
playground

Force list type in $min update operator

I have documents with the following structure:
{
"_id" : 0,
"mins" : {
"ts1" : {
"node1" : [
1,
2,
3
],
"node2" : [
4,
5,
6
]
}
}
}
I'd like to update documents by taking the component-wise minimum for an array. As MongoDB does not support $min on arrays (I think), I'm updating each index individually like so:
db.foo.updateOne(
{"_id" : 0},
{$min: {
"mins.ts3.node1.0": 1,
"mins.ts3.node1.1": 2
}}
)
This works fine but the problem is that if the document does not have the array before updating, MongoDB creates a nested document instead of an array:
{
"_id" : 0,
"mins" : {
"ts1" : {
"node1" : [
1,
2,
3
],
"node2" : [
4,
5,
6
]
},
"ts3" : {
"node1" : {
"0" : 1,
"1" : 2
}
}
}
}
Is there a way to tell MongoDB it is updating a list even if the list does not exist yet?
I'd like to avoid creating empty lists for each document as that would break my current program design.

MongoDB, how to use document as the smallest unit to search the document in array?

Sorry for the title, but I really do not know how to make it clear. But I can show you.
Here I have insert two document
> db.test.find().pretty()
{
"_id" : ObjectId("557faa461ec825d473b21422"),
"c" : [
{
"a" : 3,
"b" : 7
}
]
}
{
"_id" : ObjectId("557faa4c1ec825d473b21423"),
"c" : [
{
"a" : 1,
"b" : 3
},
{
"a" : 5,
"b" : 9
}
]
}
>
I only want to select the first document with a value which is greater than 'a' and smaller than 'b', like '4'.
But when i search, i cannot get the result i want
> db.test.find({'c.a': {$lte: 4}, 'c.b': {$gte: 4}})
{ "_id" : ObjectId("557faa461ec825d473b21422"), "c" : [ { "a" : 3, "b" : 7 } ] }
{ "_id" : ObjectId("557faa4c1ec825d473b21423"), "c" : [ { "a" : 1, "b" : 3 }, { "a" : 5, "b" : 9 } ] }
>
Because '4' is greater than the '"a" : 1' and smaller than '"b" : 9' in the second document even it is not in the same document in the array, so the second one selected.
But I only want the first one selected.
I found this http://docs.mongodb.org/manual/reference/operator/query/elemMatch/#op._S_elemMatch, but it seems the example is not suitable for my situation.
You would want to
db.test.findOne({ c: {$elemMatch: {a: {$lte: 4}, b: {$gte: 4} } } })
With your query, you are searching for documents that have an object in the 'c' array that has a key 'a' with a value <= 4, and a key 'b' with a value >= 4.
The second record is return because c[0].a is <= 4, and c[1].b is >= 4.
Since you specified you wanted to select only the first document, you would want to do a findOne() instead of a find().
Use $elemMatch as below :
db.test.find({"c":{"$elemMatch":{"a":{"$lte":4},"b":{"$gte":4}}}})
Or
db.test.find({"c":{"$elemMatch":{"a":{"$lte":4},"b":{"$gte":4}}}},{"c.$":1})

Different semantics in $not $geoWithin with Polygon geometries between MongoDB 2.4 and 2.6

I have run the following experiment, comparing how MongoDB 2.4 and MongoDB 2.6 behave regarding the $geoWithin selector combined with $not with Polygons (i.e. "outside polygon" query). I'm including the particular versions (three numbers), alghouth I guess it would happend the same with other minor versions of 2.4 and 2.6.
Two documents (A and B) are created in a given collection: A with p field set to coordinates [1, 1] and B without p field. Next, I create a 2dsphere index in p and do a query for the area outside a triangle which vertices are [0, 0], [0, 4]and [4, 0]. Note that A is inside that polygon (so it is not supposed to be got with this query).
With 2.4.9:
db.x.insert({k: "A", p: [1,1]})
db.x.insert({k: "B"})
db.x.ensureIndex({"p": "2dsphere"})
db.x.find({p: { $not: { $geoWithin: { $geometry: { type: "Polygon", coordinates: [ [ [ 0, 0 ], [ 0, 4 ], [ 4, 0 ], [ 0, 0 ] ] ] } } } }})
--> no result
Makes sense: A is not returned (as it is inside the polygon) and B is not returned (given that it doesn't have a p field).
Next, testing with 2.6.1 the same script:
db.x.insert({k: "A", p: [1,1]})
db.x.insert({k: "B"})
db.x.ensureIndex({"p": "2dsphere"})
db.x.find({p: { $not: { $geoWithin: { $geometry: { type: "Polygon", coordinates: [ [ [ 0, 0 ], [ 0, 4 ], [ 4, 0 ], [ 0, 0 ] ] ] } } } }})
-> result: B
It seems that in 2.6 semantics have changed, so when the 2dsphere-indexed field is not in a given document, that document is considered outside any possible polygon.
Changing semantics between versions is ok as long as some mechanism in the new version allows to configure behaviour in the old way. I thought that mechanism was using { "2dsphereIndexVersion" : 1 } at index creation time (based on what I read here, maybe I misunderstood that information...). However, the result (with 2.6.1 again) is the same:
db.x.insert({k: "A", p: [1,1]})
db.x.insert({k: "B"})
db.x.ensureIndex({"p": "2dsphere"}, { "2dsphereIndexVersion" : 1 })
db.x.find({p: { $not: { $geoWithin: { $geometry: { type: "Polygon", coordinates: [ [ [ 0, 0 ], [ 0, 4 ], [ 4, 0 ], [ 0, 0 ] ] ] } } } }})
-> result B
Thus, is there any way of using MongoDB 2.6 with the same semantics that MongoDB 2.4 in the sense that any document without the 2dsphere-indexed not to be returned in "outside poylgon" queries?
The query result in 2.6 is right - the query result in 2.4 I think I would call incorrect. Technically, your query asks for documents that do not match the $geoWithin condition. The "k" : "B" document does not match the $geoWithin condition, so it should be returned by the query. You can drop results without the p field using $exists:
db.x.find({
"p" : {
"$exists" : true,
"$not" : { "$geoWithin" : {
"$geometry" : {
"type": "Polygon",
"coordinates" : [ [ [ 0, 0 ], [ 0, 4 ], [ 4, 0 ], [ 0, 0 ] ] ]
}
} } }
})
Also note that 1) your $not query isn't actually using the geo index, as you can check with an explain, and 2) when using a 2dsphere index you should store points as GeoJSON
{
"k" : "A",
"p" : {
"type" : "Point",
"coordinates" : [1,1]
}
}
Technically it's required in MongoDB >= 2.6, and the docs say it should be an error not to use GeoJSON, but it seems to work for us.

Mongodb -how to find records that contain certain keywords array

Recently I wanted to filter out records that contain a certain keyword array in MongoDB, for example: I have five records that contain keywords array:
{a:[1,2]}
{a:[1,3,8]}
{a:[1,2,5]}
{a:[3,5,1]}
{a:[4,5]}
If I input the array [1,2,3,5] for search, then I want to get:
{a:[1,2]}
{a:[1,2,5]}
{a:[3,5,1]}
Each of them is a sub array of [1,2,3,5].
Any idea?
Please don't use a where clause (when possbile). Thanks!
Its simple to do in mongodb, but the harder part is preparing the data for the query. Let me explain that in oder
Simple part
You can use $in to find the matching elements in an array. Let us try
db.coll.find({a:{$in:[1,2,3,5]})
and the result is
{ "_id" : ObjectId("4f37c41739ed13aa728e9efb"), "a" : [ 1, 2 ] }
{ "_id" : ObjectId("4f37c42439ed13aa728e9efc"), "a" : [ 1, 3, 8 ] }
{ "_id" : ObjectId("4f37c42c39ed13aa728e9efd"), "a" : [ 1, 2, 5 ] }
{ "_id" : ObjectId("4f37c43439ed13aa728e9efe"), "a" : [ 3, 5, 1 ] }
{ "_id" : ObjectId("4f37c43e39ed13aa728e9eff"), "a" : [ 4, 5 ] }
ohh, its not the result we expected. Yes because $in return an item if any matching element found (not necessarily all).
So we can fix this by passing the exact array elements to $in, for example if we want to find the items matching these exact arrays {a:[1,2]} {a:[1,2,5]} and {a:[4,5,6]}
db.coll.find({a:{$in:[[1,2],[1,2,5],[4,5,6]]}})
you will get
{ "_id" : ObjectId("4f37c41739ed13aa728e9efb"), "a" : [ 1, 2 ] }
{ "_id" : ObjectId("4f37c42c39ed13aa728e9efd"), "a" : [ 1, 2, 5 ] }
Thats all
Hardest part
The real hardest part is forming all the possible combination of your input array [1,2,3,5]. You need to find a way to get all the combination of the source array (from your client) and pass it to $in.
For example, this JS method will give you all the combinations of the given array
var combine = function(a) {
var fn = function(n, src, got, all) {
if (n == 0) {
if (got.length > 0) {
all[all.length] = got;
}
return;
}
for (var j = 0; j < src.length; j++) {
fn(n - 1, src.slice(j + 1), got.concat([src[j]]), all);
}
return;
}
var all = [];
for (var i=0; i < a.length; i++) {
fn(i, a, [], all);
}
all.push(a);
return all;
}
>> arr= combine([1,2,3,5])
will give you
[
[
1
],
[
2
],
[
3
],
[
5
],
[
1,
2
],
[
1,
3
],
[
1,
5
],
[
2,
3
],
[
2,
5
],
[
3,
5
],
[
1,
2,
3
],
[
1,
2,
5
],
[
1,
3,
5
],
[
2,
3,
5
],
[
1,
2,
3,
5
]
]
and you can pass this arr to $in to find all the macthing elements
db.coll.find({a:{$in:arr}})
will give you
{ "_id" : ObjectId("4f37c41739ed13aa728e9efb"), "a" : [ 1, 2 ] }
{ "_id" : ObjectId("4f37c42c39ed13aa728e9efd"), "a" : [ 1, 2, 5 ] }
Wait!, its still not returning the remaining two possible items.
Because have a good look at the arr, it finds only the combination. it returns [1,3,5] but the data in document is [3,5,1]. So its clear that $in checks the items in given order (weird!).
So now you understand its the really hard comparing the mongodb query!. You can change the above JS combination former code to find the possible permutation to each combination and pass it to mongodb $in. Thats the trick.
Since you didn't mention any language choice its hard to recommend any permutation code. But you can find lot of different approaches in Stackoverflow or googling.
If I understood, you want to return only the objects whose all values of property a are in the find array argument.
By following the Travis' suggestion in the comments, you must follow these steps:
Define a JS function to achieve your desires (since there's no native way to do that in MongoDB);
Save the function on the server;
Use the function within $where.
If define your function to use only to that specific property (a, in this case), you may want skip the step 2. However, since it can be an useful function for other properties of other documents, I defined a more generic function, which must to be save on the server to be used AFAIK (I'm new on Mongo, too).
Below there are my tests on the mongo shell:
<--! language: lang-js -->
// step 1: defining the function for your specific search
only = function(property, values) {
for(var i in property) if (values.indexOf(property[i]) < 0) return false
return true
}
// step 2: saving it on the server
db.system.js.save( { _id : 'only', value : only } )
// step 3: using the function with $where
db.coll.find({$where: "only(this.a, [1,2,3,5])"})
With the 5 objects you provided on the question, you will obtain:
{ "_id" : ObjectId("4f3838f85594f902212eb532"), "a" : [ 1, 2 ] }
{ "_id" : ObjectId("4f3839075594f902212eb534"), "a" : [ 1, 2, 5 ] }
{ "_id" : ObjectId("4f38390e5594f902212eb535"), "a" : [ 3, 5, 1 ] }
The downside is performance. See more.