Retrieving a subset of data from MongoDB - mongodb

If I have a collection similar to:
[
{ "test": [ { "a": 1, "b": 2 }, { "a": 10, "b": 1 } ] },
{ "test": [ { "a": 5, "b": 1 }, { "a": 14, "b": 2 } ] },
...
]
How do I obtain only a subset of data consisting of the a values when b is 2? In SQL, this would be something similar to:
SELECT test.a FROM collection WHERE test.b = 2
I do understand that I can limit what data I get with something like:
collection.find({ }, { "test.a": 1 })
But that returns all the a values. How can I limit it so that it returns only the values in which b is 2 (the WHERE test.b = 2 part of the SQL equivalent)?

You can do this by adding a selector object as the first parameter of your find call and using the $elemMatch projection operator:
collection.find({ 'test.b': 2 }, { test: { $elemMatch: { b: 2 } }, 'test.a': 1 })
But this will only return the first test array element per-doc where b is 2. You would need to use the aggregation framework if there can be multiple b:2 elements in your test arrays.

Related

MongoDB update array if condition matches

I'm looking for a MongoDB aggregation pipeline which updates an array with a conditional statement.
My data looks like the following:
{
"_id": 1,
"locations": [
{
"controllerID": 1,
"timestamp": 1234
},
{
"controllerID": 2,
"timestamp": 2342
}
]
},...
Potential new entry:
{
"controllerID": 2,
"timestamp": //will be set automatically
}
At first I want to match the _id (not a problem) and then push the new entry to the locations array if the element with the newest/latest timestamp has a different controllerID.
When pushing a new location object the timestamp will be set automatically.
Example 1
Input:
{
"controllerID": 2,
}
Expected Result:
{
"_id": 1,
"locations": [
{
"controllerID": 1,
"timestamp": 1234
},
{
"controllerID": 2,
"timestamp": 2342
}//noting is added because the newset entry in the array has the same controllerID
]
},
Example 2
Input:
{
"controllerID": 1,
}
Expected Result:
{
"_id": 1,
"locations": [
{
"controllerID": 1,
"timestamp": 1234
},
{
"controllerID": 2,
"timestamp": 2342
},
{//added because the controllerID is different to te last element
"controllerID": 1,
"timestamp": 4356
}
]
},
Thanks in advance!
Here's a solution.
var candidate = 2;
rc=db.foo.update({}, // add matching criteria here; for now, match ALL
[
[
// We cannot say "if condition then set fld = X else do nothing".
// We must say "set fld to something based on condition."
// The common pattern becomes:
// "Set fld to (if condition then X else fld)"
// in other words, set the fld to *itself*
//
// Note the use of the dot operator on the $locations field.
// Also, not sure about what sort of new timestamp is desired so let's
// just throw in an ISODate() for now.
{$set: {'locations': {$cond: [
{$ne:[candidate, {$last:'$locations.controllerID'}]}, // IF not same as candidate...
{$concatArrays: ['$locations',
// $concatArrays wants arrays, not objects, so we must wrap our new
// object with [] to make an array of 1:
[ {controllerId:candidate,timestamp:new ISODate() } ]
]}, // THEN concat a new entry to end of existing locations
'$locations' // ELSE just set back to existing locations
]}
}}
],
{multi:true}
);
The engine is "smart enough" to realize that setting a field to itself will not trigger a modification so the approach is performant and will not rewrite the entire set of matched objects; this can be seen in the output of the update() call, e.g.:
printjson(rc);
{ "nMatched" : 1002, "nUpserted" : 0, "nModified" : 1 }

Mongodb: push element to nested array if the condition is met

I have the following collection:
{
"_id": 11,
"outerArray": [
{ "_id" : 21,
"field": {
"innerArray" : [
1,
2,
3
]
}
},
{ "_id" : 22,
"field": {
"innerArray" : [
2,
3
]
}
},
{ "_id" : 23,
"field": {
"innerArray" : [
2
]
}
}
]
}
I need to go through all documents in collection and push to innerArray new element 4, if innerArray already contains element 1 or element 3
I tried to do it this way, and few others, similar to this one, but it didn't work as expected, it only pushes to innerArray of first element of outerArray
db.collection.updateMany(
{ "outerArray.field.innerArray": { $in: [ 1, 3 ] } },
{ $push: { "outerArray.$.field.innerArray": 4} }
)
How to make it push to all coresponding innerArrays?
Problem here is your missunderstanding a copule things.
When you do "outerArray.field.innerArray": { $in: [ 1, 3 ] } into your query, your are not getting only innerArray where has 1 or 3. You are gettings documents where exists these arrays.
So you are querying the entire document.
You have to use arrayFilter to update values when the filter is match.
So, If I've understood you correctly, the query you want is:
db.collection.update(
{}, //Empty object to find all documents
{
$push: { "outerArray.$[elem].field.innerArray": 4 }
},
{
"arrayFilters": [ { "elem.field.innerArray": { $in: [ 1, 3 ] } } ]
})
Example here
Note how the first object into update is empty. You have to put there the field to match the document (not the array, the document).
If you want to update only one document you have to fill first object (query object) with values you want, for example: {"_id": 11}.

How can I return only one field in a mongo array of objects without removing rest of object

I am querying a collection and in that collection I have an array of objects. In that array I want to use projection to only return one field in every object. However, I do not want to remove all the data outside that object.
Let's say one document in the collection looks like this
{
"a": "some val",
"b": "some other val",
"c": [
{
"d": "some array val",
"e": "some other array val"
}
]
}
And let's say I want to remove every field in the array besides 'd' and end up with
{
"a": "some val",
"b": "some other val",
"c": [
{
"d": "some array val",
}
]
}
I tried:
db.collection.find({}, { "c.d": 1 })
but this removed "a" and "b" as well and just returned:
{
"c": [
{
"d": "some array val",
}
]
}
Also, I cannot do:
db.collection.find({}, { "c.e": 0 })
because there may be other fields besides 'e' and those should be hidden as well.
You can run $addFields to overwrite existing field and $map to transform c array and take only d values, try:
db.collection.aggregate([
{
$addFields: {
c: {
$map: {
input: "$c",
in: { d: "$$this.d" }
}
}
}
}
])

MongoDB project the documents with count greater than 2 [duplicate]

This question already has answers here:
Query for documents where array size is greater than 1
(14 answers)
Closed 6 years ago.
I have a collection like
{
"_id": "201503110040020021",
"Line": "1", // several documents may have this Line value
"LineStart": ISODate("2015-03-11T06:49:35.000Z"),
"SSCEXPEND": [{
"Secuence": 10,
"Title": 1,
},
{
"Secuence": 183,
"Title": 613,
},
...
],
} {
"_id": "201503110040020022",
"Line": "1", // several documents may have this Line value
"LineStart": ISODate("2015-03-11T06:49:35.000Z"),
"SSCEXPEND": [{
"Secuence": 10,
"Title": 1,
},
],
}
SSCEXPEND is an array. I am trying to count the size of SSC array and project if the count is greater than or equal to 2. My query is something like this
db.entity.aggregate(
[
{
$project: {
SSCEXPEND_count: {$size: "$SSCEXPEND"}
}
},
{
$match: {
"SSCEXPEND_count2": {$gte: ["$SSCEXPEND_count",2]}
}
}
]
)
I am expecting the output to be only the the first document whose array size is greater than 2.
Project part is working fine and I am able to get the counts but I need to project only those which has count greater than or equal to two but my match part is not working. Can any one guide me as where am I going wrong?
You need to project the other fields and your $match pipeline will just need to do a query on the newly-created field to filter the documents based on the array size. Something like the following should work:
db.entity.aggregate([
{
"$project": {
"Line": 1,
"LineStart": 1, "SSCEXPEND": 1,
"SSCEXPEND_count": { "$size": "$SSCEXPEND" }
}
},
{
"$match": {
"SSCEXPEND_count": { "$gte": 2 }
}
}
])
Sample Output:
/* 0 */
{
"result" : [
{
"_id" : "201503110040020021",
"Line" : "1",
"LineStart" : ISODate("2015-03-11T06:49:35.000Z"),
"SSCEXPEND" : [
{
"Secuence" : 10,
"Title" : 1
},
{
"Secuence" : 183,
"Title" : 613
}
],
"SSCEXPEND_count" : 2
}
],
"ok" : 1
}
This is actually a very simple query, where the trick is to use a property of "dot notation" in order to test the array. All you really need to ask for is documents where the array index of 2 $exists, which means the array must contain 3 elements or more:
db.entity.find({ "SSCEXPEND.2": { "$exists": true } })
It's the fastest way to do it and can even use indexes. No need for calculations in aggregation operations.

mongodb check an element has one nested attribute

I have a collection of documents like this
[
{ "name": "pika", "attrs": { "A": 1, "B": 2 ... } },
{ "name": "chu", "attrs": { "C": 3 } },
{ "name": "plop", "attrs": { "A": 1, "C": 3 } }
]
I would like to delete records that have a "C" and only a "C" attribute in their "attrs" (line named "chu") using mongodb 2.4. The number of possible attributes under the attrs key is possibly large (> 100).
I can use several queries.
How would you do that ?
Edit : I want to keep attr C in lines containing other attributes.
You have two choices. If your key space is small you can do
db.collection.remove( {C : {$exists:true}, A: {$exists:false}, B: {$exists: false} })
Otherwise you'll need to do
var col = db.collection.find( {C : {$exists:true}} );
for(doc in col) {
var found = false
for(key in obj) {
if( key !== 'C' ) {
found = true;
break;
}
}
if(found === false) {
db.collection.remove(doc);
}
}
There's no way to count the number of keys in a document directly within MongoDB and there's no way to query on wildcards in key names (e.g. you can't do "key not equal to C : {$exists: false}"). So you either need to test all keys explicitly or test each document in your application layer.
If the "attrs" is a array, in other words, you collections like this:
{ "name": "pika", "attrs": [{ "A": 1}, {"B": 2}] };
{ "name": "chu", "attrs": [{ "C": 3 }] };
{ "name": "plop", "attrs": [{ "A": 1}, {"C": 3 }] }
Then you can write a query like below to find the specific record you want:
db.entities.find({"attrs.C": {$exists: true}, "attrs": {$size: 1}});
You can check the mongodb website to find the $size operation, http://docs.mongodb.org/manual/reference/operator/size/