Curious -- in the following example, why does the mongo REPL not store the user hash in variable 'a' past the first print?
Does it have something to do with mongo's lazy query evaluation?
> var a = db.users.find(0)
> a
{ "_id" : ObjectId("4eed6dc299cd67e275000001"), "provider" : "facebook", "uid" : "343323487", "name" : "Brian Jordan", "email" : "redacted#redacted.com" }
> a
>
You can use findOne.
> var a = db.testcoll.findOne()
> a
{
"_id" : ObjectId("4e7930a3ff647405d6000003"),
"bf" : false,
"df" : ISODate("2011-09-21T00:32:35.629Z")
}
Or you can look at its source and do something similar
> db.testcoll.findOne
function (query, fields) {
var cursor = this._mongo.find(this._fullName, this._massageObject(query) || {}, fields, -1, 0, 0, 0);
if (!cursor.hasNext()) {
return null;
}
var ret = cursor.next();
if (cursor.hasNext()) {
throw "findOne has more than 1 result!";
}
if (ret.$err) {
throw "error " + tojson(ret);
}
return ret;
}
Related
I use MongoDB version 2.6.10. Below is the collection structure. I use MapReduce function to group the names of the user based on (created(excluding seconds), event_name.
{
"_id" : ObjectId("59c11d79078dc54153c36ee8"),
"event_name" : "notification",
"created" : ISODate("2017-09-19T13:36:57.252Z"),
"sender_name" : "nathan",
"user_name": "Ragul"",
}
{
"_id" : ObjectId("59c11d79078dc54153c36eeb"),
"event_name" : "notification",
"created" : ISODate("2017-09-19T13:36:57.772Z"),
"sender_name" : "parmesh",
"user_name": "Ram",
}
{
"_id" : ObjectId("59c11d7a078dc54153c36ef0"),
"event_name" : "notification",
"created" : ISODate("2017-09-19T13:36:58.554Z"),
"sender_name" : "nathan",
"user_name": "Ram",
}
{
"_id" : ObjectId("59c11d7a078dc54153c36ef1"),
"event_name" : "message",
"created" : ISODate("2017-09-19T13:36:58.577Z"),
"sender_name" : "nathan",
"user_name": "Ragul"",
}
Below is my query using MapReduce function. My question is whether we can use calculated date as a mapper. Help me with your suggestions
var mapfn = function(){
if (this.event_name == "message"){
name = this.recipient_name
}
else if ((this.event_name == "notification") && (this.other_status == true)){
name = this.sender_name
}
else if ((this.event_name == "notification") && (this.other_status == false)){
name = "You"
}
this.cre = {$subtract:[this.created,{$add:[{$multiply:[{$second:this.created},1000]},{$millisecond:this.created}]}]}
emit({"event_name": this.event_name, "created": this.cre}, name)
}
var redfun = function(key, value){
return Array.append(value)
}
db.getCollection('users').mapReduce(mapfn, redfun, {out: "example"}).find()
Here instead calculating the date using MongoDB expression, I tried to use javascript to eliminate the seconds and then I mapped then its worked.
var mapfn = function(){
if (this.event_name == "message"){
name = this.recipient_name
}
else if ((this.event_name == "notification") && (this.other_status == true)){
name = this.sender_name
}
else if ((this.event_name == "notification") && (this.other_status == false)){
name = "You"
}
this.created.setSeconds(0);
this.created.setMilliseconds(0);
emit({"event_name": this.event_name, "created": this.created}, name)
}
var redfun = function(key, value){
var names = value.join(",")
return names
}
db.users.mapReduce(mapfn, redfun, {out: "example"}).find()
Hi I have a collection that has a complex structure, and the documents in this structures are different in the structure. I want to update all the keys V to have value 0 in this collection.
Example:
{
"_id" : ObjectId("5805dfa519f972b200ea2955"),
"s" : {
"id" : NumberLong(36435)
},
"a" : [
{
"XX-(random value)" : {
"V" : 4
},
"V" : 4,
"u" : {
"YY-(random value)" : {
"V" : 4,
"ZZ-(random value)" : {
"V" : 4,
"WW-(random value)" : {
"V" : 4
}
}
}
}
}
]
}
You can do this with a short javascript. You need to iterate through each property of each document, checking the name of the property. If it matches the name "V" then update it with the value 0. This can be done recursively using a loop. The following javascript code should do the needful:
function checkProperties(doc) {
for (var propertyName in doc) {
if (typeof doc[propertyName] == "object") {
checkProperties(doc[propertyName]);
} else {
if (propertyName == "V") {
doc[propertyName] = 0;
print(doc[propertyName]);
}
}
}
}
db.<your_collection_name>.find({}).forEach(function(doc) {
checkProperties(doc);
db.<your_collection_name>.save(doc);
});
If you save this code in a .js file you can then run it against your mongo database:
> mongo --host <host> --port <port> <script_name>.js
The MongoDB query language allows filtering documents based on the existence or absence of a given field with the $exists operator.
Is there a way, with the MongoDB syntax, and given a set K of allowed fields, to exclude documents that have fields not in K from the results, but:
not knowing in advance which extra fields (outside K) can be encountered
not using JavaScript, that is, the $where operator?
Example:
{
"Some field" : "foo"
}
{
"Some field" : "bar",
"Some other field" : "foobar"
}
With the set K = [ "Some field" ], only the first document is to be returned.
Note how this is not to be confused with a projection, which would return both documents but removing the extra field.
I'm not sure if MongoDB do support such kind of operations out of box but you can achieve so with help of mapReduce.
Assuming your sample data set;
// Variable for map
var map = function () {
var isAcceptable = true;
Object.keys(this).forEach(function (key) {
if (key != "_id" && white_list.indexOf(key) == -1) {
isAcceptable = false;
}
});
if (isAcceptable == true) {
emit(1, this);
}
};
// Variable for reduce
var reduce = function (key, values) {
return values;
};
db.collection.mapReduce(
map,
reduce,
{
scope: {"white_list": ["Some field"]},
out: {"inline": 1}
}
);
Will return:
{
"results" : [
{
"_id" : 1,
"value" : {
"_id" : ObjectId("57cd7503e55de957c62fb9c8"),
"Some field" : "foo"
}
}
],
"timeMillis" : 13,
"counts" : {
"input" : 2,
"emit" : 1,
"reduce" : 0,
"output" : 1
},
"ok" : 1
}
Desired result will be in results.values of returned document. However, keep in mind limitation of MongoDB mapReduce and maximum size of BSON document.
Given a set of known fields K, you can construct a query that takes the set as input and gives a query with the $exists operator along with the corresponding fields projection. Using an example, suppose you have the following documents in a test collection
db.test.insert({ "fieldX": "foo", "fieldY": "bar", "fieldZ": 1 })
db.test.insert({ "fieldX": "123", "fieldY": "bar", "fieldZ": 2 })
db.test.insert({ "fieldY": "abc", "fieldZ": 3 })
db.test.insert({ "fieldX": "xyz", "fieldZ": 4 })
db.test.insert({ "fieldZ": 5 })
Then you can construct a query Q and a projection P from an input set K as follows:
var K = [ "fieldX", "fieldZ" ];
var or = K.map(function(field) {
var obj = {};
obj[field] = { "$exists": true };
return obj;
});
var P = K.reduce(function(doc, field) {
doc[field] = 1;
return doc;
}, {} );
var Q = { "$or": or };
db.test.find(Q, P);
Sample Output:
/* 1 */
{
"_id" : ObjectId("57cd78322c241f5870c82b7d"),
"fieldX" : "foo",
"fieldZ" : 1
}
/* 2 */
{
"_id" : ObjectId("57cd78332c241f5870c82b7e"),
"fieldX" : "123",
"fieldZ" : 2
}
/* 3 */
{
"_id" : ObjectId("57cd78332c241f5870c82b7f"),
"fieldZ" : 3
}
/* 4 */
{
"_id" : ObjectId("57cd78332c241f5870c82b80"),
"fieldX" : "xyz",
"fieldZ" : 4
}
/* 5 */
{
"_id" : ObjectId("57cd78332c241f5870c82b81"),
"fieldZ" : 5
}
I am trying to compare dates in '$where' query to filter data. My 'where' query looks something like this:
function () {
var messageStatusInfoList = this.messageStatusInfoList
var startDate = someDate;
var result = false;
for (var counter = 0; counter < messageStatusInfoList.length; counter++) {
var currentMessageStatusInfo = messageStatusInfoList[counter]
if (counter > 0 && (currentMessageStatusInfo.messageStatus == "RESPONDED" )) {
var responseDate = currentMessageStatusInfo.effectiveDate
if(+responseDate >= +startDate) {
result = true;
break;
}
}
}
return result;
}
Here is sample input document:
{
"_id" : NumberLong(3687),
"messageStatusInfoList" : [
{
"effectiveDate" : ISODate("2014-08-01T13:29:26.456Z"),
"expirationDate" : ISODate("2014-08-04T11:40:29.824Z"),
"messageStatus" : "OPENED"
},
{
"effectiveDate" : ISODate("2014-08-04T11:40:29.824Z"),
"expirationDate" : ISODate("2014-08-05T13:01:00.135Z"),
"messageStatus" : "RESPONDED",
"userId" : NumberLong(8)
},
{
"effectiveDate" : ISODate("2014-08-05T13:01:00.135Z"),
"messageStatus" : "REPLY_TO_CUSTOMER",
"userId" : NumberLong(8)
}
],
"tenantId" : NumberLong(4),
"text" : "some text ..",
"version" : NumberLong(12)
}
As per the above document and given where query, if startDate is set to 2014-08-03, then the date comparison should evaluate to true(because responseDate is 2014-08-04T11:40:29.824Z and responseDate is greater than startDate). But it is not happening so. The comparison operator evaluates to false.
The issue was due to improper initialization of variable startDate in my where query.
I had data structure in MongoDB as below
{
"_id" : ObjectId("523aab00045624a385e5f549"),
"name" : "English Book 29",
"SKU" : 1000549081,
"price" : 249000,
"image" : null,
"category_id" : ObjectId("523a7802b50418baf38b4575"),
"category_name" : "English Book",
"details" : {
"Title" : "Title 549081",
"Binding" : 1,
"Author" : "Author 0",
"Publication data" : 0.5263832447608386,
"Publisher name" : "Publisher name 14",
"Number of page" : 90
}
}
Binding of book has 2 values:
0 that means soft binding, and 1 that means hard binding. I write Map Reduce to statistics for each values.
var map = function()
{
for(var key in this.details)
{
if(key == 'Binding')
{
emit({name: key}, {
'data':
[
{
name: this.details[key],
count: 1
}
]
});
}
}
};
var reduce = function (key, values) {
var reduced = {};
for(var i in values)
{
var inter = values[i];
for(var j in inter.data)
{
if(typeof(reduced[inter.data[j].name]) != "undefined")
{
reduced[inter.data[j].name] += inter.data[j].count;
}
else
{
reduced[inter.data[j].name] = 1;
}
}
}
return reduced;
};
When I run with small data (50 records) result return exactly. But when I run it with real data (192000 records) result return Not exactly. The result as below
{
"_id" : {
"name" : "Binding"
},
"value" : {
"0" : 50,
"1" : 50
}
}
I checked return data when Map/Reduce done, result as below
"counts" : {
"input" : 192000,
"emit" : 192000,
"reduce" : 1920,
"output" : 1
},
What wrong with it. Welcome any suggestion, explanation.
Thanks and best regards,
After researching about Map/Reduce yesterday, I realized that, "Emit" send 100 elements once, and "Reduce" perform on this data set. So my above code is wrong because it only "SUM" on small data set.
Below that is my new code for Map-Reduce
var map = function ()
{
for(var key in this.details)
{
if(key == 'Binding')
{
var value = {};
value[this.details[key]] = 1;
emit(key, value);
}
}
}
var reduce = function (key, values)
{
var reduced = {};
for(var idx = 0; idx < values.length; idx++)
{
var inner = values[idx];
for (var j in inner)
{
if (typeof (reduced[j]) == 'undefined')
{
reduced[j] = 0;
}
reduced[j] += inner[j];
}
}
return reduced;
}
I post here for anyone who meet similar situation. Thanks for reading.