mongodb aggregate with $match and unwind - arrays

based on this question...
I am trying to make an aggregate function and filter my array based on values in subarray. Currently my array structure is as follows:
'id':1,
'data': {
'value':1
}
'id':2,
'data': {
'value':1
}
'id':3,
'data': {
'value':2
}
So I need to get all items where data.value is 1. Currently in order to do this I have to do the following:
db.ids.aggregate([
{$match:{id:{$exists:true}}}, //some more matching conditions here...
{$unwind:'$data'},
{$match:{'data.value':1}}//need to get rid of this and move it to the first $match
So the question is can I get rid of the second $match and put my subarray filter condition into the first match? I tried to do that but it did not work and returned the whole document. I really need to filter my array based on subarray value because my db would become very large and I dont want $unwind on the whole bunch of documents.
Thank you.

You could add multiple conditions in the first match itself.
db.ids.aggregate([
{$match:{id:{$exists:true}, "data.value": 1}}
])
and then do your unwind.

try below:
db.ids.aggregate([
{$match:{id:{$exists:true}}, 'data.value':1},
{$unwind:'$data'}

Related

A way to bring facet results together based on common id

I'm doing a mongodb aggregation with two facets. Each facet is a different operation performed on the same collection. Each facet's results had two fields per object; the id and the operation result. I want to combine each facet's results based on the common id.
The desired result is like this:
[
{
"id":"1",
"bind":"xxx",
"pres":"xxx"
},
{
"id":"2",
......
}
]
I would like unfound areas to be zero or not be included if that is supported.
I've started with
const combined_agg = [
{
"$facet":{
"bind":opp_bind,
"pres":opp_pres,
}
}
Where the two opp are the variables for the two operations. The above gives me:
[
{
"bind":
[
{"binding":6,"id":"xxxx"},
....
],
"pres":
[
{"presenting":4,"id":"xxxx"},
....
]
}
]
From here, I am running into trouble.
I have tried to concatenate the arrays with
{
"$project":{"result":{"$concatArrays":["$bind","$pres"]}}
}
which gives me one object with one large array. I tried to $unwind that large array so I objects are at the root but unwind only gives me the first 20 items of the array.
I tried using $group within the result array, but that gives me an id field with an array of all the ids and two other fields with arrays of their values.
{
"$group":{
"_id":"$result.id",
"fields":{
"$push":{"bind":"$result.bind","pres":"$result.pres"}
}
}
}
I don't know how to separate them out so I can recombine them. I also saw some somewhat similar problems using map but I couldn't wrap my head around it.
I was able to figure out how to do it. I used lookup with a pipeline to get the right format.
Lookup added the result to every object of the original query. Then I used project and filter to find the correct value from the second query. Then I used addFields and arrayElementAt to get the value I wanted along with another project to get only the values I needed. It wasn't very pretty though.

meteor mongo update spliced array

I'm currently working on a function which generates an mongodb modifier object based on an orginal object and an changed object.
The goal is to update the document as carefully as possible. (only changes will be added to the modifier)
Changed objects (which may be nested) work just fine. But I'm having issues with arrays:
If I take an array [1,2,3,4] and run arr.splice(1,1), the array looks like this: [1,3,4]. This is correct.
The problem is the following:
When I compare [1,2,3,4] to [1,3,4] my function would change the indexes 1 and 2 and then remove the 3rd index since its not needed anymore.
The generated modifier would look like this:
//document in collection
{
arr: [1,2,3,4]
}
//generated modifier
{
$set: {
'arr.1': 3,
'arr.2': 4
},
$push: {
$slice: 3
}
}
This produces the following error:
MongoError: Cannot update 'arr.1' and 'arr' at the same time
Do you have any suggestions how to remove this last index within one query?
Or do I have to use multiple updates to archive this?

Return only one element from strings array in elasticsearch

I have array of strings in one field "strArray":
strArray: ['browser:IE', 'device:PC', 'country:USA', 'state:CA']
I need do aggregations by browser (device, country or state). It's not a problem, if I know order of these values in strArray field.
I could to use those structure:
"aggs": {
"deviceAggs": {
"terms": {
"script": "doc['strArray'][1]"
}
}
}
But problem is that order of inserting these strings can be different.
How can I do this ? I think about several ways:
Scripting - use function like as substring and get only "correct" values.
Filtering - it's possible to filter one value (which contains string "device:") from array.
Sorting strArray values to put all values in definite order, but "sort" give me strange result - return only one element (without any filtering).
Don't ask me, why I have this structure (this is not my choice), if we have structure key: value - we would not have problems.
Scripting is only directly possible here.
To get an idea on how to use scripting in aggregations, you can refer this blog.
Something like below should work
for(element in doc['strArray'].values){
if(element.startsWith('browser')){
return element;
}
};
return null;
Both sorting and filtering is done on document level and not element level.
On element level if you can make this array as nested , filtering is possible. That is first you need to change the structure to -
strArray: [
{ "name" : 'browser:IE' } ,
{ "name" : 'device:PC' }
]
And then make the strArray field as nested.
In that case you can do a nested filter based on prefix query ( Using query filter ) and then , do a nested aggregation on the data.

Mongodb: Find all documents where at least one array element does not matches?

I have a group document defined as this in Mongoose:
var GroupSchema = new Schema({
name: String,
sections: [{type: ObjectId}]
});
As visible, group contains an array of sections. I also have another array of objectId's called archived_sections
I want to find all groups whose at least one section is NOT in archived_sections array. How to do that?
I was trying to use $nin operator like this:
Group.find({ sections: { $nin: archived_sections }).exec(function(err, groups){
res.send(groups);
});
But this is giving me only those groups whose sections field holds an array with NO elements matching an element in the array archived_sections.
I want to find all groups where at LEAST ONE section is NOT in archived_sections array. How to achieve that? Please help
You can do this by wrapping your $nin in an $elemMatch operator so that the $nin is applied separately to each element of sections instead of the set of elements as a group:
Group.find({ sections: { $elemMatch: { $nin: archived_sections } } })
.exec(function(err, groups){
res.send(groups);
}
);
If at least one element satisfies the $elemMatch query, the doc matches.

How to project only matching fields of nested array in mongo shell query

I am fairly new to mongodb, and I have what is hopefully a simple question:
I have a nested schema where I have a field that is an array, where each item of that array is an object that itself has an array field.
For example:
> db.mytest.insert({
name: 'a',
top: [
{x:1, y:2, nest: [{p:1, q:2}, {p:2, q:3}]},
{x:2, y:3, nest: [{p:4, q:5}, {p:6, q:7}]}
]
})
I can query for certain values of p just fine, and can even limit my result to the first matching element of top:
> db.mytest.findOne({'top.nest': {$elemMatch: {p:6}}}, {'top.nest.$': 1})
{"_id":ObjectId(...), top: [{x:2, y: 3, nest: [{p:4, q:5}, {p:6, q:7}]}]}
Which brings me to my question: {'top.nest.$': 1} and {'top.$': 1} as my projection document both return the same result. How can I limit my search results to only include the first matching element of nest?
Do I need a second pass that iterates over the result of this style of query?
Ok, the trick was the aggregation framework, specifically unwind.
> db.mytest.aggregate({$unwind: '$top'},
{$unwind: '$top.nest'},
{$match: {'top.nest.p': 6}}
)
Though in the case that I had multiple sub matches in a single object, this would return multiple results instead of in their original grouped form. I suppose I can put a $group into the pipeline, though.
Though the related links I found suggested schema redesign as the only complete fix right now, so this is definitely better than nothing.

Resources