Mongo Query to retrieve documents having redundant values within an Array - mongodb

In my mongo db collection , I have few documents as below :
Each Document has a 'Bricks' array .
In the Bricks Array , some documents have same 'brick_id' ( see - Document 1)
Some documents have unique 'brick_id' ( see - Document 2)
Now, My Question is that I need to write a Mongo Query to retrieve all the Documents which contains redundant 'brick_id' in the "bricks" array .
Means , I need to get Document 1 as output
Please help . I'm new to Mongo Db
**Document 1** :
{
"_id" : 123 ,
"page" : "new_page_1" ,
"bricks" : [
{
"brick_id" : 160,
"name" : "dev-br"
},
{
"brick_id" : 160,
"name" : "dev-br2"
}
]
}
**Document 2**
{
"_id" : 150 ,
"page" : "new_page_1" ,
"bricks" : [
{
"brick_id" : 200,
"name" : "dev-br"
},
{
"brick_id" : 201,
"name" : "dev-br2"
}
]
}

You might use the $setUnion operator to join "$bricks.brick_id" with an empty array, and compare its $size with that of the unmodified "$bricks.brick_id" array. If the sizes are not equal, there was at least one duplicate.

Related

MongoDB Query double nested documents in array

I was wondering how I can query from an embedded document inside an array. I have following structure:
{ "targetId" : 2, "metaData" : [ { "key" : "id", "value" : 1 }, { "key" : "name", "value" : "Parisa" }, { "key" : "img", "value" : { "imgid" : 1, "imgName" : "img1" } } ]
I could search simple key-values like key = id and value =1, but I could not search based on the values with embedded document e.g. key="img"
I tried following query but it does not work:
db.test.find({"metaData":{$elemMatch:{"key":"img", "value":{"imgid":1}}}})
Could you please help me!
I think the "value" part of your query is a little off. You need to put the document element in the criteria:
b.test.find({"metaData":{$elemMatch:{"key":"img", "value.imgid":1}}})

How do I update 2 specific elements in an array with 2 different values?

I have a document of the sorts:
{ _id:ObjectID, list:Array }
And the list contains elements of the form (which I will refer to as listElement):
{ _id:ObjectID, time:Number }
I want to update the time subfield of 2 specific listElements each with its own distinct value. I have access to both _id of the listElements.
A related thing about this issue: would it be better to transform list from Array to an Object who's keys are the _id values? so I would do db.update( ( _id:"Document id" }, { "list.423rfasf2q3.time":200, "list.fjsdhfksjdh2432.time":100 } ) ?
I am unsure how one could use an ObjectID as a key, but I guess I can just string it and have both a _id value and the key containing that listElement be the same string.
You can't update two docs with two different values, you'll need to run it as two separate updates.
You do not want to put data into your key values, it's better to leave them as they are. There's no good way to match key values outside of $exists and even that does not support wildcards. So you'd have to get the ObjectId, convert it to a string, concatenate it into the key, then query for documents where that key exists. It's much easier to just put the value in a document in an array. Additionally, there's no way to index key values in MongoDB, so if you want to index your data you'll need to have it in the array as a data element.
Edit to include positional match example
First, insert two documents
> db.test.insert( {list: [ {_id: new ObjectId(), time: 1234}, {_id: new ObjectId(), time: 4556} ] } )
>
Demonstrate that they are both there
> db.test.find()
{ "_id" : ObjectId("5215036749177daf439a2ffe"), "list" : [{ "_id" : ObjectId("5215036749177daf439a2ffc"), "time" : 1234 }, { "_id" :
ObjectId("5215036749177daf439a2ffd"), "time" : 4556 } ] }
>
Show that if we do a normal array filter we get all elements of the array
> db.test.find({"list._id":ObjectId("5215036749177daf439a2ffc")})
{ "_id" : ObjectId("5215036749177daf439a2ffe"), "list" : [ { "_id" : ObjectId("5215036749177daf439a2ffc"), "time" : 1234 }, { "_id" :
ObjectId("5215036749177daf439a2ffd"), "time" : 4556 } ] }
>
Demonstrate that we can use the $ operator to only return the first array element that was matched
> db.test.find({"list._id":ObjectId("5215036749177daf439a2ffc")}, {_id: 0, "list.$": 1})
{ "list" : [ { "_id" : ObjectId("5215036749177daf439a2ffc"), "time" : 1234 } ] }

How to access data from a nested array structured collection

I have the following structure in my MongoDB .
I have a nested structure of my collection named chains which is shown below .
I am trying to access options of a particular date as shown below which is 2015-01-17 in my case .
db.chains.find({ "symbol" : "UBSC" ,"option_exp.expiration_dt" : "2015-01-17"}).pretty()
But the following query above is returning me all the data related to that Symbol .
{
"_id" : ObjectId("52000a90d293b0e4134e8c35"),
"symbol" : "UBSC",
"option_exp" : [
{
"expiration_dt" : "2015-01-17",
"options" : [
{
"mult" : "10"
},
{
"mult" : "10"
}
]
},
{
"expiration_dt" : "2014-01-18",
"options" : [
{
"prem_mult" : "10"
},
{
"prem_mult" : "10"
}
}
]
}
],
}
This is the way i was trying to access through java
BasicDBObject query = new BasicDBObject();
query.append("symbol", "UBSC");
query.append("option_exp.expiration_dt", "2015-01-17");
Could anybody please help me as how to access data of a particular date .
Use $elemMatch to limit content of option_exp array field that is included in result:
db.chains.find({symbol : "UBSC" ,"option_exp.expiration_dt" : "2015-01-17"},
{option_exp: {$elemMatch: {expiration_dt: "2015-01-17"}}})
This will select documents which have symbol equal to "UBSC" and option_exp array items with expiration_dt equal to "2015-01-17". Then we limit option_exp array content to items which have required expiration date (otherwise whole document will all option_exp items will be returned).

How can I select a number of records per a specific field using mongodb?

I have a collection of documents in mongodb, each of which have a "group" field that refers to a group that owns the document. The documents look like this:
{
group: <objectID>
name: <string>
contents: <string>
date: <Date>
}
I'd like to construct a query which returns the most recent N documents for each group. For example, suppose there are 5 groups, each of which have 20 documents. I want to write a query which will return the top 3 for each group, which would return 15 documents, 3 from each group. Each group gets 3, even if another group has a 4th that's more recent.
In the SQL world, I believe this type of query is done with "partition by" and a counter. Is there such a thing in mongodb, short of doing N+1 separate queries for N groups?
You cannot do this using the aggregation framework yet - you can get the $max or top date value for each group but aggregation framework does not yet have a way to accumulate top N plus there is no way to push the entire document into the result set (only individual fields).
So you have to fall back on MapReduce. Here is something that would work, but I'm sure there are many variants (all require somehow sorting an array of objects based on a specific attribute, I borrowed my solution from one of the answers in this question.
Map function - outputs group name as a key and the entire rest of the document as the value - but it outputs it as a document containing an array because we will try to accumulate an array of results per group:
map = function () {
emit(this.name, {a:[this]});
}
The reduce function will accumulate all the documents belonging to the same group into one array (via concat). Note that if you optimize reduce to keep only the top five array elements by checking date then you won't need the finalize function, and you will use less memory during running mapreduce (it will also be faster).
reduce = function (key, values) {
result={a:[]};
values.forEach( function(v) {
result.a = v.a.concat(result.a);
} );
return result;
}
Since I'm keeping all values for each key, I need a finalize function to pull out only latest five elements per key.
final = function (key, value) {
Array.prototype.sortByProp = function(p){
return this.sort(function(a,b){
return (a[p] < b[p]) ? 1 : (a[p] > b[p]) ? -1 : 0;
});
}
value.a.sortByProp('date');
return value.a.slice(0,5);
}
Using a template document similar to one you provided, you run this by calling mapReduce command:
> db.top5.mapReduce(map, reduce, {finalize:final, out:{inline:1}})
{
"results" : [
{
"_id" : "group1",
"value" : [
{
"_id" : ObjectId("516f011fbfd3e39f184cfe13"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.498Z"),
"contents" : 0.23778377776034176
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe0e"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.467Z"),
"contents" : 0.4434165076818317
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe09"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.436Z"),
"contents" : 0.5935856597498059
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe04"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.405Z"),
"contents" : 0.3912118375301361
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfdff"),
"name" : "group1",
"date" : ISODate("2013-04-17T20:07:59.372Z"),
"contents" : 0.221651989268139
}
]
},
{
"_id" : "group2",
"value" : [
{
"_id" : ObjectId("516f011fbfd3e39f184cfe14"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.504Z"),
"contents" : 0.019611883210018277
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe0f"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.473Z"),
"contents" : 0.5670706110540777
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe0a"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.442Z"),
"contents" : 0.893193120136857
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe05"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.411Z"),
"contents" : 0.9496864483226091
},
{
"_id" : ObjectId("516f011fbfd3e39f184cfe00"),
"name" : "group2",
"date" : ISODate("2013-04-17T20:07:59.378Z"),
"contents" : 0.013748752186074853
}
]
},
{
"_id" : "group3",
...
}
]
}
],
"timeMillis" : 15,
"counts" : {
"input" : 80,
"emit" : 80,
"reduce" : 5,
"output" : 5
},
"ok" : 1,
}
Each result has _id as group name and values as array of most recent five documents from the collection for that group name.
you need aggregation framework $group stage piped in a $limit stage...
you want also to $sort the records in some ways or else the limit will have undefined behaviour, the returned documents will be pseudo-random (the order used internally by mongo)
something like that:
db.collection.aggregate([{$group:...},{$sort:...},{$limit:...}])
here there is the documentation if you want to know more

How do you update objects in a document's array (nested updating)

Assume we have the following collection, which I have few questions about:
{
"_id" : ObjectId("4faaba123412d654fe83hg876"),
"user_id" : 123456,
"total" : 100,
"items" : [
{
"item_name" : "my_item_one",
"price" : 20
},
{
"item_name" : "my_item_two",
"price" : 50
},
{
"item_name" : "my_item_three",
"price" : 30
}
]
}
I want to increase the price for "item_name":"my_item_two" and if it doesn't exists, it should be appended to the "items" array.
How can I update two fields at the same time? For example, increase the price for "my_item_three" and at the same time increase the "total" (with the same value).
I prefer to do this on the MongoDB side, otherwise I have to load the document in client-side (Python) and construct the updated document and replace it with the existing one in MongoDB.
This is what I have tried and works fine if the object exists:
db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}})
However, if the key doesn't exist, it does nothing.
Also, it only updates the nested object. There is no way with this command to update the "total" field as well.
For question #1, let's break it into two parts. First, increment any document that has "items.item_name" equal to "my_item_two". For this you'll have to use the positional "$" operator. Something like:
db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } ,
{$inc : {"items.$.price" : 1} } ,
false ,
true);
Note that this will only increment the first matched subdocument in any array (so if you have another document in the array with "item_name" equal to "my_item_two", it won't get incremented). But this might be what you want.
The second part is trickier. We can push a new item to an array without a "my_item_two" as follows:
db.bar.update( {user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} ,
{$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
false ,
true);
For your question #2, the answer is easier. To increment the total and the price of item_three in any document that contains "my_item_three," you can use the $inc operator on multiple fields at the same time. Something like:
db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
{$inc : {total : 1 , "items.$.price" : 1}} ,
false ,
true);
There is no way to do this in single query. You have to search the document in first query:
If document exists:
db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } ,
{$inc : {"items.$.price" : 1} } ,
false ,
true);
Else
db.bar.update( {user_id : 123456 } ,
{$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
false ,
true);
No need to add condition {$ne : "my_item_two" }.
Also in multithreaded enviourment you have to be careful that only one thread can execute the second (insert case, if document did not found) at a time, otherwise duplicate embed documents will be inserted.
We can use $set operator to update the nested array inside object filed update the value
db.getCollection('geolocations').update(
{
"_id" : ObjectId("5bd3013ac714ea4959f80115"),
"geolocation.country" : "United States of America"
},
{ $set:
{
"geolocation.$.country" : "USA"
}
},
false,
true
);
One way to ensure there are no duplicates of the "item_name" fields would be the do the same actions as given in the answer https://stackoverflow.com/a/10523963 for Question #1 but in reverse order!
Push into the document if "items.item_name":{"$ne":"my_name"} - the update filter MUST contain some uniquely indexed field! And upsert is false.
Increment the document if "items.item_name":"my_name".
The first update should be atomic, thus it doesn't do anything if the array already contains the element with the item_name "my_name".
By the time the second update happens, there must be an array element with the "item_name"="my_name"