I am trying to write a query to group my records by the value inside an array inside another array inside a collection in MongoDB. Now if that doesn't have your head hurting I think a sample schema might be easier to understand:
{
"_id": ObjectId("...")
"attributes": [
[ "attributeA", "valueA" ],
[ "attributeB", "valueB" ],
[ "attributeC", "valueC" ],
...
]
}
Now I want to be able to group my records by the attributeB field based on valueB.
So far I can aggregate if I know the actual value of valueB:
collection.aggregate([
{ '$match': { 'attributes': [ "attributeB", "valueB" ] } },
{ '$group': {
'_id': { 'attributes': [ "attributeB", "valueB" ] } }
}
])
Basically seeing if the attributes array contains the pair: [ "attributeB", "valueB" ]. But now I want to be able to have the query determine what valueB is as it performs the aggregation.
To paraphrase: I can't seem to figure out how to group by the value if I don't know the value of valueB. I just want all records to group by their valueB's, when attributeB is found at the first position inside an array inside the attributes array.
Any help is appreciated. Thanks!
After grouping your data you should use the $unwind operator. It pairs up your other fields with every item in the array.
collection.aggregate([
{ '$match': { 'attributes': [ "attributeB", "valueB" ] } },
{ '$group': {
'_id': { 'attributes': [ "attributeB", "valueB" ] } }
},
{ '$unwind': 'attributes'},
... // here you can match again and continue aggregation
])
Most probably this is not the fastest solution. I will think of a better one.
Also note that the order of elements in the array is not preserved.
UPDATE
This is a similar question. So what I would do is create documents in the attribute array like
'attributes':[
{'attribute': 'attributeB', 'value': 'valueB'},
{'attribute': 'attributeC', 'value': 'valueA'},
]
So you can access your valueB after the $match or $unwind through $value.
Related
Hello folks struggling with this updatation
Any one can please help out here
"sheet_types": [
{
"areas": [
"FRONT",
"SIDE",
"UPPER"
]
}
]
}
I need to update the value of UPPER to TOP here
I have tried this query
db.company_settings.updateOne(
{"sheet_types.areas": "UPPER" },
{ $set: { "sheet_types.$.areas" : "TOP" } })
But the result i getting is
{
"sheet_types": [
{
"areas": [
"TOP"
]
}
]
}
Using "arrayFilters" seems to fit best for this use case:
db.collection.update({},
{
"$set": {
"sheet_types.$[].areas.$[filter]": "TOP"
}
},
{
"arrayFilters": [
{
"filter": "UPPER"
}
]
})
Explained:
Add arrayFIlter with one filter element equal to the value that you need to replace , use this filter to replace the value in the update statement. The advantage of this option is that it will replace all matching values that match the filter conditions in the document.
playground
I need to check if an ObjectId exists in a non nested array and in multiple nested arrays, I've managed to get very close using the aggregation framework, but got stuck in the very last step.
My documents have this structure:
{
"_id" : ObjectId("605ce5f063b1c2eb384c2b7f"),
"name" : "Test",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("6058e94c3994d04d2863962e")
],
"variations" : [
{
"varName" : "Var1",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
{
"varName" : "Var2",
"attrs" : [
ObjectId("60591791d4d41d0a6817d22a"),
ObjectId("60591791d4d41d0a6817d255"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
],
"storeId" : "9acdq9zgke49pw85"
}
Let´s say I need to check if this if this _id exists "6058e94c3994d04d28639616" in all arrays named attrs.
My aggregation query goes like this:
db.product.aggregate([
{
$match: {
storeId,
},
},
{
$project: {
_id: 0,
attrs: 1,
'variations.attrs': 1,
},
},
{
$project: {
attrs: 1,
vars: '$variations.attrs',
},
},
{
$unwind: '$vars',
},
{
$project: {
attr: {
$concatArrays: ['$vars', '$attrs'],
},
},
},
]);
which results in this:
[
{
attr: [
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
6058e94c3994d04d2863962e,
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f
]
},
{
attr: [
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
6058e94c3994d04d28639624,
6058e94c3994d04d28639627,
6058e94c3994d04d28639628,
6058e94c3994d04d2863963e
]
}
]
Assuming I have two products in my DB, I get this result. Each element in the outermost array is a different product.
The last bit, which is checking for this key "6058e94c3994d04d28639616", I could not find a way to do it with $group, since I dont have keys to group on.
Or with $match, adding this to the end of the aggregation:
{
$match: {
attr: "6058e94c3994d04d28639616",
},
},
But that results in an empty array. I know that $match does not query arrays like this, but could not find a way to do it with $in as well.
Is this too complicated of a Schema? I cannot have the original data embedded, since it is mutable and I would not be happy to change all products if something changed.
Will this be very expensive if I had like 10000 products?
Thanks in advance
You are trying to compare string 6058e94c3994d04d28639616 with ObjectId. Convert the string to ObjectId using $toObjectId operator when perform $match operation like this:
{
$match: {
$expr: {
$in: [{ $toObjectId: "6058e94c3994d04d28639616" }, "$attr"]
}
}
}
As part of my aggregation pipeline I have the following scenario. This is the result of grouping previously unwound fields from each document (so in this case there are two documents with the same _id but with a different value for UniqueFieldName)
TopLevelField: [
{
UniqueFieldName: "Values go here!"
},
{
UniqueFieldName: "More values go here too!"
}
]
All I want to do is merge the nested object fields into one field and push all the values into that field as an array, like so.
TopLevelField: {
UniqueFieldName: [
"Values go here!",
"More values go here too!",
],
}
The idea is that I could have multiple fields with multiple values under each field grouped together for easier iteration.
TopLevelField: {
UniqueFieldName: [
"Values go here!",
"More values go here too!",
],
SecondFieldName: [
"This is text",
],
AnotherOne: [
"TEXT",
"Here too!",
"More values",
],
}
The problem I run into is that trying to use dot notation in the $group stage throws an error. It seems that mongo doesn't like to group with nested objects like this?
The easy solution is to just change the TopLevelField to some concatenation of the nested fields like this,
TopLevelField-UniqueFieldName: [
"Values go here!",
"More values go here too!",
],
TopLevelField-SecondFieldName: [
"This is text",
],
TopLevelField-AnotherOne: [
"TEXT",
"Here too!",
"More values",
],
But this is suboptimal for my use case. Is there a solution to this or do I need to rethink the entire pipeline?
You can try this :
db.collection.aggregate([
{ $unwind: '$TopLevelField' },
{
$group: {
_id: '', 'UniqueFieldName': { $push: '$TopLevelField.UniqueFieldName' },
'UniqueFieldName2': { $push: '$TopLevelField.UniqueFieldName2' },
'UniqueFieldName3': { $push: '$TopLevelField.UniqueFieldName3' },
'UniqueFieldName4': { $push: '$TopLevelField.UniqueFieldName4' }
}
}, { $project: { _id: 0 } }, { $project: { 'TopLevelField': '$$ROOT' } }])
Test : MongoDB-Playground
I have 2 arrays:
"array1": [
"057a7",
"05790",
"0575d",
"0579f",
"0576b",
"05784",
"05775"
]
"array2": [
"0579f",
"057a7",
"05790",
"05784",
"0575d",
"0576a",
"0576b",
"05775"
]
I have tried $setDifference, $setUnion and $setIntersection and these only output the elements that match. I would like to output the one that does not ("0576a"). The examples I find in stack overflow only seem to show you how to output the duplicates and not the unique values. The final output should be an array like so:
"final_array": ["0576a"]
Trying to do this in mongodb aggregation and not have to tap into mapReduce.
{
"$project": {
"_id": 0,
"unique": {
"$setDifference": [
"$array2",
"$array1"
]
}
}
}
The following should work for you:
db.collection('test').aggregate({
$project: {
"unique": {
$concatArrays: [
{ $setDifference: [ "$array1", "$array2" ] },
{ $setDifference: [ "$array2", "$array1" ] }
]
}
}
})
The key thing to understand about $setDifference is that the argument order matters since according to the documentation it...
...takes two sets and returns an array containing the elements that only
exist in the first set; i.e. performs a relative complement of the
second set relative to the first.
That's why you'll have to look at your arrays from both directions which will give you all unique elements and then you can simply merge the two results using $concatArrays.
I have mongoDB content as below:
[
{
"_id":{
"$oid":"57c6699711bd6a0976cabe8a"
},
"ID":"1111",
"FullName":"AAA",
"Category":[
{
"CategoryId":{
"$oid":"57c66ebedcba0f63c1ceea51"
},
"_id":{
"$oid":"57e38a8ad190ea1100649798"
},
"Value":[
{
"Name":""
}
]
},
{
"CategoryId":{
"$oid":"57c3df061eb1e59d3959cc40"
},
"_id":{
"$oid":"57e38a8ad190ea1100649797"
},
"Value":[
[
"111",
"XXXX",
"2005"
],
[
"1212",
"YYYY",
"2000"
],
[
"232323",
"ZZZZZ",
"1999"
]
]
}
]
},
{
"_id":{
"$oid":"57c6699711bd6a0976cabe8a"
},
"ID":"1111",
"FullName":"BBB",
"Category":[
{
"CategoryId":{
"$oid":"57c66ebedcba0f63c1ceea51"
},
"_id":{
"$oid":"57e38a8ad190ea1100649798"
},
"Value":[
{
"Name":""
}
]
},
{
"CategoryId":{
"$oid":"57c3df061eb1e59d3959cc40"
},
"_id":{
"$oid":"57e38a8ad190ea1100649797"
},
"Value":[
[
"4444",
"XXXX",
"2005"
],
[
"7777",
"GGGG",
"2000"
],
[
"8888",
"ZZZZZ",
"1999"
]
]
}
]
}
]
Here I have an array named 'Category' where it contains objects with different category id.
I need to
select a particular category id - '57c3df061eb1e59d3959cc40'
From the above selected Category, we get 'Value' array
From Value array need to find if the second value is equal to 'ZZZZZ' ie. value[1] == 'ZZZZZ'
And now, update the matched value arrays with a new value at the end
Eg:
[
"232323",
"ZZZZZ",
"1999"
]
should be updated to
[
"232323",
"ZZZZZ",
"1999",
"update1"
]
and
[
"8888",
"ZZZZZ",
"1999"
]
should be updated to
[
"8888",
"ZZZZZ",
"1999",
"update1"
]
I have tried as below:
resume.update({
"Category.CategoryId": new ObjectId('57c3df191eb1e59d3959cc43'),
"Category.Value.$.1": 'ZZZZZ'
},
{"$set": {"Category.Value.$.3": "update1"}
}, function(err, resData){
res.send(resData);
});
But, nothing gets updated. Its there any way to get this work. Please help to update the inner array.
Thanks in advance.
Your goal is not possible at the moment since you need to update two positional elements.
There is a JIRA trackable for the sort of behaviour you want here: https://jira.mongodb.org/browse/SERVER-831
It's a problem since you need to match two elements positions:
the Category element with the matched CategoryId
the Value element in the Value array of arrays
If one of these wouldn't be an array it would have been possible.
Anyway, Your update try above was wrong. IF this feature was possible (and it is not!!!) it would have been something like this:
db.resume.update(
{
Category: {
$elemMatch: {
CategoryId: ObjectId('57c3df061eb1e59d3959cc40'),
Value: {
$elemMatch: {
'1': 'ZZZZZ'
}
}
}
}
},
{
$push: {
'Category.$.Value.$': 'update1'
}
}
)
The positional $ operator should be used during the update and not the find like you did, and it will update the first element that matched the query.
Doing the above will return the error:
Too many positional (i.e. '$') elements found in path 'Category.$.Value.$'
Because of the missing feature I explained at the top.
So, currently (version 3.2) you will not be able to do this unless you change your schema.