mongodb $unwind with three nested arrays - mongodb

My data has several nested levels: root --> blocks --> children -->
"array of strings"
I want to use aggregate with $unwind to output some fields from the
"blocks" level and the lowest level, "array of strings".
My question: Based on the data diagram below, what is the code for using aggregate with $unwind to output these fields?
Fields from BLOCKS
block_id
block_type
definition
All entries from the lowest level array of strings
This is different than other postings I've found because I want some levels while skipping others and also because the lower two levels don't have names.
Duplicate rows in the output are not preferred, but I can dedupe at a later stage.
I uploaded an image of the data structure. If you can see the image, there's a blue box surrounding the fields I want.
Using "MongoDB Compass", I can see this structure
ROOT ARRAY
_id
edited_on
-->BLOCKS ARRAY (array of type object)
block_id
block_type
definition
edit_info (object)
edited_on
fields (object)
display_name
-->CHILDREN ARRAY (array of type array)
-->ARRAY (array of type string)
0: string
1: string
The first two arrays have names "blocks" and "children". The last two arrays don't have names.
This may help. I already have a query with $unwind that gives me some fields from the root & blocks levels.
mongo.exe MyDatabase --quiet --eval "printjson(db.modulestore.structures.aggregate( { $unwind: '$blocks' }, { $project: { _id: 1 , edited_on: 1 , 'definition': '$blocks.definition' , 'block_type': '$blocks.block_type' , 'block_id': '$blocks.block_id' , 'block_edited_on': '$blocks.edit_info.edited_on' , 'display_name': '$blocks.fields.display_name' } } , { $match: { block_type: 'openassessment' } } ).toArray())" > C:\Data\MyOutput.json

Doesn't clear your expectation , I guessed this may be help to you
`db.modulestore.structures.aggregate([
{
"$unwind": "$blocks"
},
{
"$unwind": "$fields.children"
},
{
"$project": {
"_id": 1,
"edited_on": 1,
"definition": "$blocks.definition",
"block_type": "$blocks.block_type",
"block_id": "$blocks.block_id",
"block_edited_on": "$blocks.edit_info.edited_on",
"display_name": "$blocks.fields.display_name"
}
},
{
"$match": {
"block_type": "openassessment"
}
}
]` )

Related

MongoDB new ObjectId over array giving me same Id for all

I am trying to update items in an array with unique ObjectIds (meaning add an object ID to array object that are missing them)
If I have an array of shirt objects in my collection and I try this:
db.people.update({
$and : [
_id: ObjectId('5eeb44c6a042791d28a8641f'),
{
$or: [
{ 'shirts._id': { $eq:null } },
{ 'shirts._id':{ $exists:false } }
]
}
]
},{
$set: { 'shirts.$[]._id': new ObjectId() }
},{
"multi" : true
}
);
It generates IDENTICAL ObjectsIDs for each array element, I would put an unique index on this however, the use case probably wont see more then 2-3 items in the array with edge cases hitting 5-6, which seems like an abuse of an index
How can I update multiple records or multiple array objects with a unique ObjectId?
When you use $set you're telling mongo to set that value to all matching elements. If the elements in the array are already defined as schemas, mongo will issue new ObjectIds for each one of them automatically.
Alternatively, you can use forEach and iterate over each matching element creating a new ObjectId.

In MongoDB, how do I update by matching an element within an array of an Embedded query?

Say you have an document saved like this:
{
_id: "1",
thingy: {
moreStuff: [
{
_id:"a",
moreComplicated: [
{
_id:"a1",
info: "test"
},
... // More similar elements
]
},
... // More similar elements
]
}
}
Suppose you already have a way to get the query of the document:
db.getCollection("thingies").find({"_id": 1});
Now you're looking for a way to update the "info" section inside the "a1" object nested within all those other objects and arrays. The only catch to this is that "moreStuff" and "moreComplicated" arrays are being updated constantly, so aiming by array location (like thingy.morestuff.0.moreComplicated.0.info) won't work. The only way to do it is by looking up the unique mongoID of each object.
How would you query the arrays by matching an element to value instead of an index? Bonus points if you can have a "find" query return a similar object.

mongodb positional operator in the find part of update()

I'm trying to update a certain field in my mongodb collection 'users'
Example of a document:
{
_id: ObjectId("536d4d3307221f847fbbea49"),
groups: [
{
match_id: "dc3acf1b",
groupName: "World Cup, Group A",
score:[0,1]
},
{
match_id: "d4f14940",
groupName: "World Cup, Group A",
score:[2,1]
}],
name:"Foo Bar",
points:10
}
I'm trying to update only score fields that have a certain score..
here is what I want (position operator) :
db.users.update({'groups.match_id':i_match , 'groups.score':i_score},
{$inc:{points:1}});
Now I figure this would look for a user that has both those fields, but I want to look in the same groups object that had the first part of the query.
As in:
db.users.update({'groups.match_id':d4f14940 , 'groups.score':[2,1]},
{$inc:{points:1}});
should give the user +1 points
But:
db.users.update({'groups.match_id':dc3acf1b , 'groups.score':[2,1]},
{$inc:{points:1}});
Should not
You get my drift :)
Now I know from the positional operator that I should use the '$' sign, but I think that can only be used in the second paramater of the update() - the updating part..and not the finding part
note - The actual comparison of the score in this way (arrays) might not work..but that's not the issue here
What you want is the $elemMatch operator. This allows you to select array elements that require multiple fields to match a condition:
db.users.update(
{ "groups": {
"$elemMatch": {
"match_id": "d4f14940",
"score": [2,1]
}
}},
{ "$inc": { "points": 1 } }
)
So that allows you to match the document as the "groups" array contains an element that has both match_id and score that meet the conditions you supplied. If you change that match_id without altering the score, then the document does not match and the increment is not made to your points value.
So this has nothing to do with the positional $ operator in this case as you are not updating and array at a matched position, which is what that is for.

Mongo: Query to get count of objects in a nested field

I needed some help to create a count query on nested objects in a field, across all documents. Each document json has a many fields. One particular field called "hotlinks" comprises of many internal dynamic object fields.
Doc1:
{
hotlinks : { 112222:{....} , 333333: {.....} , 545555: {.....} }
}
Doc2:
{
hotlinks : { 67756:{....} , 756767: {.....} , 1111111: {.....} }
}
Each document has a hotlinks fields. The hotlinks field comprises of varied inner hotlink objects. Each key is a java unique id and has objects that contain data (inner fields).
I needed a way to get the count of all the inner nested objects of the field – ‘hotlinks’.
For example the summation of inner objects of hotlinks in doc1 and doc2 would be 6.
Is there any way to do this via a single query to get the count across all documents.
Thanks a lot,
Karan
Quite possible if using MongoDB 3.6 and newer though the aggregation framework.
Use the $objectToArray operator within an aggregation pipeline to convert the document to an array. The return array contains an element for each field/value pair in the original document. Each element in the return array is a document that contains two fields k and v.
On getting the array, you can then leverage the use of the $size operator which returns the number of elements in the given array thus giving you the count per document.
Getting the count across all the documents requires a $group pipeline where you specify the _id key of null or a constant value which gives calculates accumulated values for all the input documents as a whole.
All this can be done in a single pipeline by nesting the expressions as follows:
db.collection.aggregate([
{ "$group": {
"_id": null,
"count": {
"$sum": {
"$size": { "$objectToArray": "$hotlinks" }
}
}
} }
])
Example Output
{
"_id" : null,
"count" : 6
}
this may not be the best approach, but you can define a javascript variable and sum up the counts. i.e;
var hotlinkTotal=0;
db.collection.find().forEach(function(x){hotlinkTotal+=x.hotlinks.length;});
print(hotlinkTotal);

remove documents with array field's size less than 3 in mongoDB

i have a mongoDB collection named col that has documents that look like this
{
{
intField:123,
strField:'hi',
arrField:[1,2,3]
},
{
intField:12,
strField:'hello',
arrField:[1,2,3,4]
},
{
intField:125,
strField:'hell',
arrField:[1]
}
}
Now i want to remove documents from collection col in which size of the array field is less than 2.
So i wrote a query that looks like this
db.col.remove({'arrField':{"$size":{"$lt":2}}})
Now this query doesnt do anything. i checked with db.col.find() and it returns all the documents. Whats wrong with this query?
With MongoDB 2.2+ you can use numeric indexes in condition object keys to do this:
db.col.remove({'arrField.2': {$exists: 0}})
This will remove any document that doesn't have at least 3 elements in arrField.
From the documentation for $size:
You cannot use $size to find a range of sizes (for example: arrays with more than 1 element).
The docs recommend maintaining a separate size field (so in this case, arrFieldSize) with the count of the items in the array if you want to try this sort of thing.
Note that for some queries, it may be feasible to just list all the counts you want in or excluded using (n)or conditions.
In your example, the following query will give all documents with less than 2 array entries:
db.col.find({
"$or": [
{ "arrField": {"$exists" => false} },
{ "arrField": {"$size" => 1} },
{ "arrField": {"$size" => 0} }
]
})
The following should work
db.col.remove({$where: "this.arrField.length < 2"})