query document nested multiarray-array mongodb elemMatch - mongodb

I have this documents:
//document 1
{
info : [
{
id : 100,
field : {
a : 1,
b : 2
}
},
{
id : 200,
field : {
a : 3,
b : 4
}
},
{
id : 300,
field : {
a : 5,
b : 6
}
}
]
},
//document 2
{
info : [
{
id : 400,
field : {
a : 7,
b : 8
}
},
{
id : 500,
field : {
a : 9,
b : 10
}
}
]
}
I need to find the id of the subdocument with the values field.a = 7 and field.b = 8 , that means the id value is 400.
What i have tried is $elemMatch but I can't get the result.
My attemps :
attemp 1:
db.mycollection.findOne({info : {$elemMatch : { 'field.$.a':7,'field.$.b':8 } } });
attemp 2:
db.mycollection.findOne({info:{$elemMatch:{$elemMatch:{'field.$.a':7,'field.$.b':8,}}}});
attemp 3:
db.mycollection.findOne({info:{$elemMatch:{$elemMatch:{'field.a.$':7,'field.b.$':8,}}}});
attemp 4:
db.mycollection.findOne({info:{$elemMatch:{'field.$.a':7,'field.$.b':8,}}});

The $elemMatch operator works like a "mini query" against the specified array element it is acting on, so arguments go inside. Also the positional $ operator here is a propery of "projection" and not the query document itself, so this is a separate element:
db.mycollection.find(
{
"info": {
"$elemMatch": { "field.a": 7 , "field.b": 8 }
}
},
{ "info.$": 1 }
)
Which both matches the document containing the matched element, and then only returns the matched element due to the projection:
{
"_id" : ObjectId("564d52979f28c6e0feabceee"),
"info" : [
{
"id" : 400,
"field" : {
"a" : 7,
"b" : 8
}
}
]
}

Related

MongoDB query to update dates doesn't work on all documents

Basically I want to update all documents inside one collection. The update is just adding 2 hours to date fields present in each document.
The documents all follow a basic structure like this :
{
code : 1,
file : {
dates : {
start : 2018-05-27 22:00:00.000Z,
end : 2018-05-27 22:00:00.000Z,
},
otherInfos : {
...
...
}
}
}
Here is my query :
var cursor = db.getCollection('files').find({});
while(cursor.hasNext()){
e = cursor.next();
let delta = 120*60*1000; //2 hours
if(e.file.dates) {
let fileStartDate = e.file.dates.start ? new Date(e.file.dates.start.getTime() + delta) : null;
let fileEndDate = e.file.dates.end ? new Date(e.file.dates.end.getTime() + delta) : null;
if(fileStartDate) {
e.file.dates.start = fileStartDate;
}
if(fileEndDate) {
e.file.dates.end = fileEndDate;
}
}
print(e);
db.getMongo().getDB('myDB').files.updateOne(
{"code":e.code},
{
$set: {"file.dates.start": fileStartDate, "file.dates.end": fileEndDate}
})
}
I am testing the query with around 20 documents and the first 10 are perfectly printed and updated with +2hours as expected but then for the second half the dates remain the exact same than before (both with the print and update).
All the documents have the same structure and same Date type so I don't understand why the query doesn't go all the way.
EDIT :
Here is a document that was succesfully updated :
{
"_id" : ObjectId("5b36c7fdd515e80009e7cc84"),
"code" : "1",
"file" : {
"dates" : {
"start" : ISODate("2018-06-11T22:00:00.000Z"),
"end" : ISODate("2018-06-11T22:00:00.000Z")
}
}
}
became as expected
{
"_id" : ObjectId("5b36c7fdd515e80009e7cc84"),
"code" : "1",
"file" : {
"dates" : {
"start" : ISODate("2018-06-12T00:00:00.000Z"),
"end" : ISODate("2018-06-12T00:00:00.000Z")
}
}
}
but for example this document :
{
"_id" : ObjectId("5b36c7ffd515e80009e7cf03"),
"code" : "15",
"file" : {
"dates" : {
"start" : ISODate("2018-09-02T22:00:00.000Z"),
"end" : ISODate("2019-09-26T22:00:00.000Z")
}
}
}
stayed the exact same
With MongoDBv4.2+, you can do an update with aggregation pipeline. Use $add to increment 2 hour * 60 minute * 60 seconds * 1000 milliseconds.
db.collection.update({},
[
{
"$set": {
"file.dates.start": {
$add: [
"$file.dates.start",
7200000
]
},
"file.dates.end": {
$add: [
"$file.dates.end",
7200000
]
}
}
}
],
{
multi: true
})
Here is the Mongo playground for your reference.
db.getMongo().getDB('myDB').files.updateOne(
{"code":e.code},
{
$set: {"file.dates.start": fileStartDate, "file.dates.end": fileEndDate}
})
updateOne only allows update on one document
You should use updateMany() to update more than 1 document
https://www.mongodb.com/docs/manual/reference/method/db.collection.updateMany/

Insert new fields to document at given array index in MongoDB

I have the following document structure in a MongoDB collection :
{
"A" : [ {
"B" : [ { ... } ]
} ]
}
I'd like to update this to :
{
"A" : [ {
"B" : [ { ... } ],
"x" : [],
"y" : { ... }
} ]
}
In other words, I want the "x" and "y" fields to be added to the first element of the "A" array without loosing "B".
Ok as there is only one object in A array you could simply do as below :
Sample Collection Data :
{
"_id" : ObjectId("5e7c3cadc16b5679b4aeec26"),
A:[
{
B: [{ abc: 1 }]
}
]
}
Query :
/** Insert new fields into 'A' array's first object by index 0 */
db.collection.updateOne(
{ "_id" : ObjectId("5e7c3f77c16b5679b4af4caf") },
{ $set: { "A.0.x": [] , "A.0.y" : {abcInY :1 }} }
)
Output :
{
"_id" : ObjectId("5e7c3cadc16b5679b4aeec26"),
"A" : [
{
"B" : [
{
"abc" : 1
}
],
"x" : [],
"y" : {
"abcInY" : 1.0
}
}
]
}
Or Using positional operator $ :
db.collection.updateOne(
{ _id: ObjectId("5e7c3cadc16b5679b4aeec26") , 'A': {$exists : true}},
{ $set: { "A.$.x": [] , "A.$.y" : {abcInY :1 }} }
)
Note : Result will be the same, but functionally when positional operator is used fields x & y are inserted to first object of A array only when A field exists in that documents, if not this positional query would not insert anything (Optionally you can check A is an array condition as well if needed). But when you do updates using index 0 as like in first query if A doesn't exist in document then update would create an A field which is an object & insert fields inside it (Which might cause data inconsistency across docs with two types of A field) - Check below result of 1st query when A doesn't exists.
{
"_id" : ObjectId("5e7c3f77c16b5679b4af4caf"),
"noA" : 1,
"A" : {
"0" : {
"x" : [],
"y" : {
"abcInY" : 1.0
}
}
}
}
However, I think I was able to get anothe#whoami Thanks for the suggestion, I think your first solution should work. However, I think I was able to get another solution to this though I'm not sure if its better or worse (performance wise?) than what you have here. My solution is:
db.coll.update( { "_id" : ObjectId("5e7c4eb3a74cce7fd94a3fe7") }, [ { "$addFields" : { "A" : { "x" : [ 1, 2, 3 ], "y" : { "abc" } } } } ] )
The issue with this is that if "A" has more than one array entry then this will update all elements under "A" which is not something I want. Just out of curiosity is there a way of limiting this solution to only the first entry in "A"?

mongodb findAndModify update element in array

There is an bson document:
{
"_id" : ObjectId("5718441f5116a60b08000b8c"),
"mails" : [
{
"id" : 2,
"a" : [
{
"a" : 1
},
{
"a" : 2
}
]
},
{
"id" : 1,
"a" : [
{
"a" : 1
},
{
"a" : 2
}
]
}
]
}
I need to return and clear the array "a" which belong to "mails.id == x" for given document. So I use findAndModify like:
db.mail.findAndModify({query: {"_id":ObjectId("5718441f5116a60b08000b8c")}, update: {$set:{"mails.$.a":[]}}, new: false, fields:{"mails":{$elemMatch:{"id":1}}}})
However this don't work. The problem is the $set should apply on one document in array rather than the whole document. So I need a projection to project it out.
If I left update to blank, it will return the desired part:
{
"_id" : ObjectId("5718441f5116a60b08000b8c"),
"mails" : [
{
"id" : 1,
"a" : [
{
"a" : 1
},
{
"a" : 2
}
]
}
]
}
But I don't know how to clear the array 'a' in 'mails'
You have to specify array element match in the query:
db.mail.findAndModify({query: {"_id":ObjectId("5718441f5116a60b08000b8c"), "mails":{$elemMatch:{"id":1}}}, update: {$set:{"mails.$.a":[]}}, new: false, fields:{"mails":{$elemMatch:{"id":1}}}})
mails.$ in you update matches the first matched element in the doc, so you have to match it in the query. Also, this query will update the doc, but it will return the old version, since you use new: false, if you want to get the updated version set it to true.

MongoDB find where key equals string from array

I am trying to find in a collection all of the documents that have the given key equal to one of the strings in an array.
Heres an example of the collection.
{
roomId = 'room1',
name = 'first'
},
{
roomId = 'room2',
name = 'second'
},
{
roomId = 'room3',
name = 'third'
}
And heres an example of the array to look through.
[ 'room2', 'room3' ]
What i thought would work is...
collection.find({ roomId : { $in : [ 'room2', 'room3' ]}}, function( e, r )
{
// r should return the second and third room
});
How can i achieve this?
One way this could be solve would be to do a for loop...
var roomIds = [ 'room2', 'room3' ];
for ( var i=0; i < roomIds.length; i++ )
{
collection.find({ id : roomIds[ i ]})
}
But this is not ideal....
What you posted should work - no looping required. The $in operator does the job:
> db.Room.insert({ "_id" : 1, name: 'first'});
> db.Room.insert({ "_id" : 2, name: 'second'});
> db.Room.insert({ "_id" : 3, name: 'third'});
> // test w/ int
> db.Room.find({ "_id" : { $in : [1, 2] }});
{ "_id" : 1, "name" : "first" }
{ "_id" : 2, "name" : "second" }
> // test w/ strings
> db.Room.find({ "name" : { $in : ['first', 'third'] }});
{ "_id" : 1, "name" : "first" }
{ "_id" : 3, "name" : "third" }
Isn't that what you expect?
Tested w/ MongoDB 2.1.1

MongoDB group by Functionalities

In MySQL
select a,b,count(1) as cnt from list group by a, b having cnt > 2;
I have to execute the group by function using having condition in mongodb.
But i am getting following error. Please share your input.
In MongoDB
> res = db.list.group({key:{a:true,b:true},
... reduce: function(obj,prev) {prev.count++;},
... initial: {count:0}}).limit(10);
Sat Jan 7 16:36:30 uncaught exception: group command failed: {
"errmsg" : "exception: group() can't handle more than 20000 unique keys",
"code" : 10043,
"ok" : 0
Once it will be executed, we need to run the following file on next.
for (i in res) {if (res[i].count>2) printjson(res[i])};
Regards,
Kumaran
MongoDB group by is very limited in most cases, for instance
- the result set must be lesser than 10000 keys.
- it will not work in sharded environments
So its better to use map reduce. so the query would be like this
map = function() { emit({a:true,b:true},{count:1}); }
reduce = function(k, values) {
var result = {count: 0};
values.forEach(function(value) {
result.count += value.count;
});
return result;
}
and then
db.list.mapReduce(map,reduce,{out: { inline : 1}})
Its a untested version. let me know if it works
EDIT:
The earlier map function was faulty. Thats why you are not getting the results. it should have been
map = function () {
emit({a:this.a, b:this.b}, {count:1});
}
Test data:
> db.multi_group.insert({a:1,b:2})
> db.multi_group.insert({a:2,b:2})
> db.multi_group.insert({a:3,b:2})
> db.multi_group.insert({a:1,b:2})
> db.multi_group.insert({a:3,b:2})
> db.multi_group.insert({a:7,b:2})
> db.multi_group.mapReduce(map,reduce,{out: { inline : 1}})
{
"results" : [
{
"_id" : {
"a" : 1,
"b" : 2
},
"value" : {
"count" : 2
}
},
{
"_id" : {
"a" : 2,
"b" : 2
},
"value" : {
"count" : 1
}
},
{
"_id" : {
"a" : 3,
"b" : 2
},
"value" : {
"count" : 2
}
},
{
"_id" : {
"a" : 7,
"b" : 2
},
"value" : {
"count" : 1
}
}
],
"timeMillis" : 1,
"counts" : {
"input" : 6,
"emit" : 6,
"reduce" : 2,
"output" : 4
},
"ok" : 1,
}
EDIT2:
Complete solution including applying having count >= 2
map = function () {
emit({a:this.a, b:this.b}, {count:1,_id:this._id});
}
reduce = function(k, values) {
var result = {count: 0,_id:[]};
values.forEach(function(value) {
result.count += value.count;
result._id.push(value._id);
});
return result;
}
>db.multi_group.mapReduce(map,reduce,{out: { replace : "multi_result"}})
> db.multi_result.find({'value.count' : {$gte : 2}})
{ "_id" : { "a" : 1, "b" : 2 }, "value" : { "_id" : [ ObjectId("4f0adf2884025491024f994c"), ObjectId("4f0adf3284025491024f994f") ], "count" : 2 } }
{ "_id" : { "a" : 3, "b" : 2 }, "value" : { "_id" : [ ObjectId("4f0adf3084025491024f994e"), ObjectId("4f0adf3584025491024f9950") ], "count" : 2 } }
You should use MapReduce instead. Group has its limitations.
In future you'll be able to use the Aggregation Framework. But for now, use map/reduce.
Depends on the number of your groups, you might find a simpler and faster solution than group or MapReduce by using distinct:
var res = [];
for( var cur_a = db.list.distinct('a'); cur_a.hasNext(); ) {
var a = cur_a.next();
for( var cur_b = db.list.distinct('b'); cur_b.hasNext(); ) {
var b = cur_b.next();
var cnt = db.list.count({'a':a,'b':b})
if (cnt > 2)
res.push({ 'a': a, 'b' : b 'cnt': cnt}
}
}
It will be faster if you have indexes on a and b
db.list.ensureIndex({'a':1,'b':1})