MongoDB with flexible fields. How to find all records with specific field name? - mongodb

I've a scheme
{
"_id" : ObjectId("50ec1d93ba02ece1979ee4a5"),
"url" : "google.com"
"results" : [
{ "1357651347" : { "data1" : "a", "data2" : "b", "data3" : "c" }},
{ "1357651706" : { "data1" : "d", "data2" : "e", "data3" : "f" }},
{ "1357651772" : { "data1" : "g", "data2" : "h", "data3" : "i" }}
]
}
I'm interested in the results with id 1357651706. How do I get them (in PHP)?

You can check if something exists or you can check if something is null (or not).
So for $exists ( http://docs.mongodb.org/manual/reference/operator/exists/ ):
db.col.find({"results.1357651706": {$exists:true}})
And for checking if something is not null:
db.col.find({ "results.1357651706": {$ne: null} })
Note: It is normally better to use the null query the other way around to check if something is null and then do the process of judgement in your app. This way you can use sparse index on your query too to make it leaner.

+1 to Sammaye's answer, but consider reworking your schema to get rid of the dynamic field names which make queries like this awkward.
Something like this instead:
{
"_id" : ObjectId("50ec1d93ba02ece1979ee4a5"),
"url" : "google.com"
"results" : [
{ id: 1357651347, "data1" : "a", "data2" : "b", "data3" : "c" },
{ id: 1357651706, "data1" : "d", "data2" : "e", "data3" : "f" },
{ id: 1357651772, "data1" : "g", "data2" : "h", "data3" : "i" }
]
}
Then you can query for the doc containing the result you're looking for like this:
db.col.find({'results.id': 1357651706})

Related

Update a object field in an array

In mondodb I want to update a field of an object within an array. The example database looks like this:
{
"_id" : ObjectId("5ad237559d30d918c89c7f46"),
"myArray" : [
{
"name" : "a",
"name2" : "a",
"value" : 900000 //<--- instead of this...
},
{
"name" : "b",
"name2" : "b",
"value" : 0
}
]
},
{
"_id" : ObjectId("5ad238049d30d918c89c7f47"),
"myArray" : [
{
"name" : "b",
"name2" : "b",
"value" : 0
},
{
"name" : "c",
"name2" : "a",
"value" : 0 //... I want to update this
}
]
}
I want to update the last value field by querying name:c AND name2:a. I tried it with the following instruction, but it sets the value of the first object (name:a name2:a). Does the problem lie near the $ char?
db.test.updateOne({$and:[{"myArray.name" : "c"}, {"myArray.name2" : "a"}]},
{$set:{"myArray.$.value" : 900000}})
You need to do an $elemMatch to match the specific item in the array and then you can use the positional operator:
db.test.updateOne(
{ "myArray": { $elemMatch: { "name": "c", "name2"; "a" } } },
{ $set: { "myArray.$.value": 900000 } }
);
You can use arrayFilters.
db.test.updateOne({}, {$set:{"myArray.$[element].value" : 900000}} {
multi: true,
arrayFilters: [ {$and:[{"element.name" : "c"}, {"element.name2" : "a"}]} ]
},
)
Sorry, I have no mongodb right there to test it, the query will probably need to be tuned a little

Is there a `$slice` like comparison for MongoDB's filters?

In MongoDB there is a projection operator $slice which allows projecting a subarray.
Is there any way to filter by an array slice as well? Something like:
db.testdb.find( {arrayofstring: { $eqSlice: {$slice: [0,1], $val: [ "a" ] } } }, {...})
Edit: An example and its expected output
> db.studentsTestDataTypes.find({},{ _id: 1, int: 1, arraystring: 1})
{ "_id" : ObjectId("56977186756088b586154f9d"), "int" : 2001, "arraystring" : [ "a", "b", "c" ] }
{ "_id" : ObjectId("56977186756088b586154f9e"), "int" : 2002, "arraystring" : [ "d", "e", "f" ] }
Example of expected result: Filtering by those entries with value "a" at the first position of arraystring:
{ "_id" : ObjectId("56977186756088b586154f9d"), "int" : 2001, "arraystring" : [ "a", "b", "c" ] }
Suppose you have the following document in your collection:
{ "_id" : ObjectId("56977186756088b586154f9d"), "int" : 2001, "arraystring" : [ "a", "b", "c" ] }
{ "_id" : ObjectId("56977186756088b586154f9e"), "int" : 2002, "arraystring" : [ "d", "e", "f" ] }
{ "_id" : ObjectId("56978e21ae9bb55c0d7cdc67"), "int" : 2001, "arraystring" : [ "b", "a", "c" ] }
The easier and best way is to use dot notation
db.collection.find({ "arraystring.0": "a" } )
Which yields:
{
"_id" : ObjectId("56977186756088b586154f9d"),
"int" : 2001,
"arraystring" : [
"a",
"b",
"c"
]
}

Count and group by with mongo db

I m actually facing a problem with mongoDB.
I need to display some statistics :
- A treatment is an information that contain a date, the user who treated, a list of anomalies
Can you help me with the request to get :
"The numbers of anomalies by users ?"
Thanks for all :D
db.treatment.aggregate(
{
$group : {_id : "$anomalies", totalUser : { $sum : 1 }}
}
);
Note : change your collection and document key name if I put wrong.
Source : http://www.mkyong.com/mongodb/mongodb-aggregate-and-group-example/
So, if your collection had the following documents:
> db.treatments.find()
{ "_id" : 1, "date" : ISODate("2014-08-29T15:44:45.843Z"), "user" : "A", "anomalies" : [ "a", "b", "c" ] }
{ "_id" : 2, "date" : ISODate("2014-08-29T15:45:01.782Z"), "user" : "A", "anomalies" : [ "e", "f", "g" ] }
{ "_id" : 3, "date" : ISODate("2014-08-29T15:45:34.889Z"), "user" : "B", "anomalies" : [ "a", "b", "c", "e", "f", "g" ] }
{ "_id" : 4, "date" : ISODate("2014-08-29T15:48:01.860Z"), "user" : "B", "anomalies" : [ "a", "b", "c", "e", "f", "g" ] }
{ "_id" : 5, "date" : ISODate("2014-08-29T15:48:28.937Z"), "user" : "A", "anomalies" : [ "x", "y", "z" ] }
You can use $group stage to $sum the $size of the anomalies array
> db.treatments.aggregate([ { $group: { _id: "$user", allAnomalies: { $sum: { $size: "$anomalies" } } } } ] )
{ "_id" : "B", "allAnomalies" : 12 }
{ "_id" : "A", "allAnomalies" : 9 }

How to update multiple MongoDB subdocuments in one shot?

I have created a couple of documents in MongoDB:
> db.myCollection.insert( {"BUCKET": [{"Field1":"X"},{"Field2":{"A":"B","C":"D","E":"F"}}]} )
> db.myCollection.insert( {"BUCKET": [{"Field1":"Y"},{"Field2":{"G":"H","I":"J","K":"L"}}]} )
> db.myCollection.find()
{ "_id" : ObjectId("534102c492970f1b06b1edc8"), "BUCKET" : [ { "Field1" : "X" }, { "Field2" : { "A" : "B", "C" : "D", "E" : "F" } } ] }
{ "_id" : ObjectId("5341054e92970f1b06b1edc9"), "BUCKET" : [ { "Field1" : "T" }, { "Field2" : { "G" : "H", "I" : "J", "K" : "L" } } ] }
Now I wold like to update it such that all values of "BUCKET" are set to []
So I try this:
db.myCollection.update({},{"BUCKET":[]}, false, false)
This partially worked. But it only changed the first bucket to []:
> db.myCollection.find()
{ "_id" : ObjectId("534102c492970f1b06b1edc8"), "BUCKET" : [ ] }
{ "_id" : ObjectId("5341054e92970f1b06b1edc9"), "BUCKET" : [ { "Field1" : "T" }, { "Field2" : { "G" : "H", "I" : "J", "K" : "L" } } ] }
So I tried to change the "multi" field to true and tried it again. But it didn't work:
> db.myCollection.update({},{"BUCKET":[]}, false, true)
multi update only works with $ operators
How can I update multiple subdocuments using the update() method?
What you need is the $set operator for your update query to work. Try this:
db.myCollection.update({},{$set:{"BUCKET":[]}}, false, true)

MapReduce on a "Parent Links" tree in MongoDB

I have a collection of entities, which represents a tree. Each entity has a property containing an array of attributes.
For example:
{
"_id" : 1,
"parent_id" : null,
"attributes" : [ "A", "B", "C" ]
}
I would like to use MapReduce to generate another collection which is similar to the original collection, but for each item in the collection it not only contains the attributes directly associated with the entity, but also those of its ancestors, all the way up to the root of the hiearchy.
So given the following entities:
{
"_id" : 1,
"parent_id" : null,
"attributes" : [ "A", "B", "C" ]
}
{
"_id" : 2,
"parent_id" : 1,
"attributes" : [ "D", "E", "F" ]
}
{
"_id" : 3,
"parent_id" : 2,
"attributes" : [ "G", "H", "I" ]
}
The result of the MapReduce job would be the following:
{
"_id" : 1,
"attributes" : [ "A", "B", "C" ]
}
{
"_id" : 2,
"attributes" : [ "A", "B", "C", "D", "E", "F" ]
}
{
"_id" : 3,
"attributes" : [ "A", "B", "C", "D", "E", "F", "G", "H", "I" ]
}
I've managed produce MapReduce jobs which do simple things like count the attributes for each entity but can't get my head round how I might deal with a hierarchy. I am open to alternative ways of storing the data but don't want to store the whole hierarchy in a single document.
Is this kind of thin possible with MapReduce in MongoDB or am I just thinking about the problem in the wrong way?
Ok, so I don't think this will be very performant/scalable, because you have to recursively find the parent ids from the child nodes. However, it does provide the output you want.
var mapFunc = function(doc, id) {
// if this is being invoked by mapReduce, it won't pass any parameters
if(doc == null) {
doc = this;
id = this._id;
} else if (doc.parent_id != null) {
// if this is a recursive call, find the parent
doc = db.test.findOne({_id:doc.parent_id});
}
// emit the id, which is always the id of the child node (starting point), and the attributes
emit(id, {attributes: doc.attributes});
// if parent_id is not null, call mapFunc with the hidden parameters
if(doc.parent_id != null) {
// recursive mapFunc call
mapFunc(doc, id);
}
}
// since we're going to call this from within mapReduce recursively, we have to save it in the system JS
db.system.js.save({ "_id" : "mapFunc", "value" : mapFunc});
var reduceFunc = function(key, values) {
var result = {attributes:[]};
values.forEach(function(value) {
// concat the result to the new values (I don't think order is guaranteed here)
result.attributes = value.attributes.concat(result.attributes);
});
return result;
}
// this just moves the attributes up a level
var finalize = function(key, value) {return value.attributes};
// quick test...
db.test.mapReduce(mapFunc, reduceFunc, {out: {inline: 1}, finalize: finalize});
Provides:
"results" : [
{
"_id" : 1,
"value" : [
"A",
"B",
"C"
]
},
{
"_id" : 2,
"value" : [
"A",
"B",
"C",
"D",
"E",
"F"
]
},
{
"_id" : 3,
"value" : [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I"
]
}
],
"timeMillis" : 2,
"counts" : {
"input" : 3,
"emit" : 6,
"reduce" : 2,
"output" : 3
},
"ok" : 1,
}