mongodb - project element in array - mongodb

a doc {"m":[1,2,3], "others":xxx}, get the first element in array 'm' by:
db.find({query},{"m":{$slice:1}, "m":1})
the return is {"m":[1]}, the element in doc is an array. But in this query, only one element in array will be get, so I don't need the return doc contain array which has only one element. This is like SQL local variable in sub-query. The element I want has no name in original doc, if I want get it then I need to make a name for it, the doc returned I wanted is like: {"localVariable":1} rather than {"m":[1]}
I try to project out the first element by:
db.find({query},{"m":{$slice:1}, "m.1":1})
but this don't work.

You seem to be looking for a positional $ operator match. It works like this:
db.collection.find({ m: 2 },{ "m.$": 1 })
That will return you the matched element
If you are in fact looking to always get the second element, then you need the two argument form of $slice:
db.collection.find({ },{ "m": { "$slice": [1,1] } })
Tested output:
db.test.insert({ m: [1,2,3] })
db.test.find({ },{ "m": {$slice: [1,1]} })
{ "_id" : ObjectId("5323cc2770fde63cf1146ba3"), "m" : [ 2 ] }

Related

Is it possible to write a Mongo query that validates a condition in each element of an array?

Consider the following documents:
{
_id: 1,
a: [{b:true},{b:true}]
},
{
_id: 2,
a: [{b:false},{b:true}]
},
{
_id: 3,
a: [{b:true}]
}
I'd like to write a query that will return all of the top level documents that have an array ("a") that contain only elements matching {b : true}. In this example, I'm expecting the first and third document to be returned, but not the second.
When writing the query like this, all 3 documents are returned..
{a : {b : true}}
Is there an operator that I'm missing? I've reviewed quite a few of them ($all) and I'm not sure which would match best for this use case
Thanks so much
Simply use $allElementsTrue on the array a.b.
db.collection.find({
$expr: {
"$allElementsTrue": "$a.b"
}
})
Here is the Mongo Playground for your reference.

MongoDB: How can I aggregate node values of nested arbitrary k-v objects using the aggregate pipeline?

I have a structure whereby a user-created object can end up in a specific Document key. I know what the key is, but I have no idea what the structure of the underlying value is. For the purposes of my problem, let's assume it's an array, a single value, or a dictionary.
For extra fun, I am also trying to solve this problem for nested dictionaries.
What I am trying to do is run an aggregation across all objects that have this key, and summarize the values of the terminal nodes of the structure. For example, if I have the following:
ObjectA.foo = {"a": 2, "b": 4}
ObjectB.foo = {"a": 8, "b": 16}
ObjectC.bar = {"nested": {"d": 20}}
ObjectD.bar = {"nested": {"d": 30}}
I want to end up with an output value of
foo.a = 10
foo.b = 20
bar.nested.d = 50
My initial thought is to try to figure out how to get Mongo to flatten the keys of the hierarchy. If I could break the source data down from objects to a series of key-values where a key represents the entire path to the value, I could easily do the aggregation on that. However, I am not sure how to do that.
Ideally, I'd have something like $unwindKeys, but alas there is no such operator. There is $objectToArray, which I imagine I could then $unwind, but at that point I already start getting lost in stacking these operators. It also does not answer the problem of arbitrary depth, though I suppose a single-depth solution would be a good start.
Any ideas?
EDIT: So I've solved the single-depth problem using $objectToArray. Behold:
db.mytable.aggregate(
[
{
'$project': {
'_id': false,
'value': {
'$objectToArray': '$data.input_field_with_dict'
}
}
},
{
'$unwind': '$value'
},
{
'$group': {
'_id': '$value.k',
'sum': {
'$sum': '$value.v'
}
}
}
]
)
This will give you key-value pairs across your chosen docs that you can then iterate on. So in case of my sample above involving ObjectA and ObjectB, the result of the above query would be:
{"_id": "a", "sum": 10}
{"_id": "b", "sum": 20}
I still don't know how to traverse the structure recursively though. The $objectToArray solution works fine on a single known level with unknown keys, but I don't have a solution if you have both unknown keys and unknown depth.
The search goes on: how do I recursively sum or at least project fields with nested structures and preserve their key sequences? In other words, how do I flatten a structure of unknown depth? If I could flatten, I could easily aggregate on keys at that point.
If your collection is like this
/* 1 */
{
"a" : 2,
"b" : 4
}
/* 2 */
{
"a" : 8,
"b" : 16
}
/* 3 */
{
"nested" : {
"d" : 20
}
}
/* 4 */
{
"nested" : {
"d" : 30
}
}
the below the query will get you the required result.
db.sof.aggregate([
{'$group': {
'_id': null,
'a': {$sum: '$a'},
'b': {$sum: '$b'},
'd': {$sum: '$nested.d'}
}}
])

removing element of an array that with a match of certain element

I have a mongodb collection.
{ user_id: 1,
items : [ { _id: 1 }, { _id: 2}, {_id:3} ] }
I want to remove the items of the array having specific id. Can anybody explain what is wrong with the above query.
db.col.findOneAndUpdate({user_id:1},{$pull:{items:{$elemMatch:{_id:2}}}})
$pull takes an expression as a parameter so you don't have to use $elemMatch (doesn't work in this case). Try:
db.col.update({user_id:1},{$pull:{items:{_id:2}}})
So expression in this case means that MongoDB will remove the document having _id set to 2 but that document can have other properties as well.

Is there a way to prevent mongo queries "branching" on arrays?

If I have the following documents:
{a: {x:1}} // without array
{a: [{x:1}]} // with array
Is there a way to query for {'a.x':1} that will return the first one but not the second one? IE, I want the document where a.x is 1, and a is not an array.
Please note that future version of MongoDB would incorporate the $isArray aggregation expression. In the meantime...
...the following code will do the trick as the $elemMatch operator matches only documents having an array field:
> db.test.find({"a.x": 1, "a": {$not: {$elemMatch: {x:1}}}})
Given that dataset:
> db.test.find({},{_id:0})
{ "a" : { "x" : 1 } }
{ "a" : [ { "x" : 1 } ] }
{ "a" : [ { "x" : 0 }, { "x" : 1 } ]
It will return:
> db.test.find({"a.x": 1, "a": {$not: {$elemMatch: {x:1}}}}, {_id:0})
{ "a" : { "x" : 1 } }
Please note this should be considered as a short term solution. The MongoDB team took great cares to ensure that [{x:1}] and {x:1} behave the same (see dot-notation or $type for arrays). So you should consider that at some point in the future, $elemMatch might be updated (see JIRA issue SERVER-6050). In the meantime, maybe worth considering fixing your data model so it would no longer be necessary to distinguish between an array containing one subdocument and a bare subdocument.
You can do this by adding a second term that ensures a has no elements. That second term will always be true when a is a plain subdoc, and always false when a is an array (as otherwise the first term wouldn't have matched).
db.test.find({'a.x': 1, 'a.0': {$exists: false}})

MongoDB $pull syntax

I'm having a (hopefully) small syntax problem with $pull in Mongodb.
bulk.find({_id: new mongo.ObjectID(req.session._id)}).updateOne({$pull: {
firstArray: {id: req.params.id},
'secondArray.firstArrayIds': req.params.id
'secondArray.$.firstArrayIds': req.params.id
}});
The firstArray $pull works just fine.
But the secondArray.firstArrayIds and/or secondArray.$.firstArrayIds does not. What am I doing wrong here?
This is my data structure:
clients: {
firstArray: [
{
_id: '153'.
someField1: 'someVal1',
}
...
]
secondArray: [
{
_id: '7423'.
someField1: 'someVal1',
firstArrayIds: [153, 154, 155, ...]
}
...
]
}
EDIT What if there are more than one embedded object in secondArray which firstArrayIds can contain the id i want to delete. In other words, when deleting an object in firstdArray i want to delete references in all secondArray's firstArrayIds Not just the first match.
Your "secondArray" has a nested element structure, so you must identify the outer element you want to match in your query when using a positional $ operator in the update. You basically need something like this:
bulk.find({
"_id": new mongo.ObjectID(req.session._id),
"secondArray._id": "7423"
}).update({
"$pull": {
"firstArray": { "_id": "153" },
"secondArray.$.firstArrayIds": 153
}
});
So there are in fact "two" id values you need to pass in with your request in addition to the general document id. Even though this is nested it is okay since you are only matching on the "outer" level and only on one array. If you tried to match the position on more than one array then this is not possible with the positional operator.