Aggregate and match multiple subdocuments in mongoDB - mongodb

So I have the following document structure:
{
"id": 0,
"users": [
{
"name": "bob",
"id": 1000
},
{
"name": "sally",
"id": 2000
}
],
"comments": [
{
"commentor_index": 0,
"comment": "foo",
"emoji": "smile"
},
{
"commentor_index": 0,
"comment": "bar",
"emoji": "heart"
},
{
"commentor_index": 1,
"comment": "zee",
"emoji": "heart"
}
]
}
The comments have the users index in the array.
I would like to be able to get how many comments each user id left across all documents.
I have gotten so far to realize I need to unwind both users and comments with includeArrayIndex for users, but I have trouble rejoining them.

Related

How to avoid huge json documents in mongoDB

I am new to mongoDB modelling, I have been working in a small app that just used to have one collection with all my data like this:
{
"name": "Thanos",
"age": 999,
"lastName": "whatever",
"subjects": [
{
"name": "algebra",
"mark": 999
},
{
"name": "quemistry",
"mark": 999
},
{
"name": "whatever",
"mark": 999
}
]
}
I know this is standard in mongoDB since we don't have to map relotionships to other collections like in a relational database. My problem is that my app is growing and my json, even tho it works perfectly fine, it is starting to be huge since it has a few more (and quite big) nested fields:
{
"name": "Thanos",
"age": 999,
"lastName": "whatever",
"subjects": [
{
"name": "algebra",
"mark": 999
},
{
"name": "quemistry",
"mark": 999
},
{
"name": "whatever",
"mark": 999
}
],
"tutors": [
{
"name": "John",
"phone": 2000,
"status": "father"
},
{
"name": "Anne",
"phone": 200000,
"status": "mother"
}
],
"exams": [
{
"id": "exam1",
"file": "file"
},
{
"id": "exam2",
"file": "file"
},
{
"id": "exam3",
"file": "file"
}
]
}
notice that I have simplified the json a lot, the nested fields have way more fields. I have two questions:
Is this a proper way to model Mongodb one to many relationships and how do I avoid such long json documents without splitting into more documents?
Isn't it a performance issue that I have to go through all the students just to get subjects for example?

MongoDB lookup with multiple nested levels

In my application, I have a section of comments and replies under some documents.
Here's how my database schema looks like
db.updates.insertOne({
"_id": "62347813d28412ffd82b551d",
"documentID": "17987e64-f848-40f3-817e-98adfd9f4ecd",
"stream": [
{
"id": "623478134c449b218b68f636",
"type": "comment",
"text": "Hey #john, we got a problem",
"authorID": "843df3dbbdfc62ba2d902326",
"taggedUsers": [
"623209d2ab26cfdbbd3fd348"
],
"replies": [
{
"id": "623478284c449b218b68f637",
"type": "reply",
"text": "Not sure, let's involve #jim here",
"authorID": "623209d2ab26cfdbbd3fd348",
"taggedUsers": [
"26cfdbbd3fd349623209d2ab"
]
}
]
}
]
})
db.users.insertMany([
{
"_id": "843df3dbbdfc62ba2d902326",
"name": "Manager"
},
{
"_id": "623209d2ab26cfdbbd3fd348",
"name": "John"
},
{
"_id": "26cfdbbd3fd349623209d2ab",
"name": "Jim"
},
])
I want to join those two collections, and replace user ids with complete user information on all levels. So the final JSON should look like this
{
"_id": "62347813d28412ffd82b551d",
"documentID": "17987e64-f848-40f3-817e-98adfd9f4ecd",
"stream": [
{
"id": "623478134c449b218b68f636",
"type": "comment",
"text": "Hey #john, we got a problem",
"author": {
"_id": "843df3dbbdfc62ba2d902326",
"name": "Manager"
},
"taggedUsers": [
{
"_id": "623209d2ab26cfdbbd3fd348",
"name": "John"
}
],
"replies": [
{
"id": "623478284c449b218b68f637",
"type": "reply",
"text": "Not sure, let's involve #jim here",
"author": {
"_id": "623209d2ab26cfdbbd3fd348",
"name": "John"
},
"taggedUsers": [
{
"_id": "26cfdbbd3fd349623209d2ab",
"name": "Jim"
}
]
}
]
}
]
}
I know how to do the $lookup on the top-level fields, including pipelines, but how can I do with the nested ones?

MongoDB: Return deeply nested document from it's id regardless of level

I have a collection consisting of documents that are made up of an id, name and an array of items; the array in each document is supposed to nest other documents of the same structure when applicable, and there is no limit to how many nested arrays of documents there are (nested documents can also have nested documents).
Using the MongoClient package, I'm trying to query my collection and return a document based on it's id, regardless of it's location in the data (be it at the top level or 3 levels down).
So far I can return any top level data okay, but my query is not finding any nested data. I've seen similar questions where the data structure is limited and consistent, but as my data is dynamic and multi-layered, I haven't find a solution that fits this particular issue.
Here's my data:
[
{
"_id": 1,
"name": "Test 1",
"items": []
},
{
"_id": 2,
"name": "Test 2",
"items": [
{
"_id": 3,
"name": "Test 3",
"items": []
},
{
"_id": 4,
"name": "Test 4",
"items": [
{
"_id": 6,
"name": "Test 6",
"items": []
}
]
}
]
},
{
"_id": 5,
"name": "Test 5",
"items": []
}
]
Here's my Mongo query:
MongoClient.connect(connString, function(err, db) {
var collection = db.collection('items');
collection.findOne({_id: ObjectID("4")}, function(err, result) {
console.log(result);
});
db.close();
});
And result is intended to return the following, but returns null instead:
{
"_id": 4,
"name": "Test 4",
"items": [
{
"_id": 6,
"name": "Test 6",
"items": []
}
]
}
Can anyone share a solution as to how I can retrieve my data as intended?
It is not possible to query an infinate amount of document of documents due to the change of properties at each level. See Query Selectors (https://docs.mongodb.com/manual/reference/operator/query/#query-selectors) for a list of selectors that can be used.
However if it's possbile to limit or give a depth limit on the items then you could use a query such as:-
db.col.find( {$or: [ { "_id" : id }, { "items._id" : id }, { "items.items._id" : id }, { "items.items.items._id" : id } ] } );
If it's not possible to give a depth limit I'd advise re-modeling the document in to something like:
[
{
"_id": 1,
"name": "Test 1"
},
{
"_id": 3,
"name": "Test 3",
"parentId": 2
},
{
"_id": 6,
"name": "Test 6",
"parentId": 4
},
{
"_id": 4,
"name": "Test 4",
"parentId": 2
},
{
"_id": 2,
"name": "Test 2",
},
{
"_id": 5,
"name": "Test 5",
}
]
Then you could do a simple find by _id query:
> db.collection.find({_id: 4})
{ "_id" : 4, "name" : "Test 4", "parentId" : 2 }
If you also need to retain the document structure of the query you can use the $graphLookup aggregation stage.

mongo searching more than 1 collection for same criteria

I have a mongo DB with several collections that contain JSON document formats shown below:
{
"questions": [
{
"questionEntry": {
"id": 1,
"info": {
"seasonNumber": 1,
"episodeNumber": 1,
"episodeName": "Days Gone Bye"
},
"questionItem": {
"theQuestion": "q1",
"attachedElement": {
"type": 1,
"value": ""
}
},
"options": [
{
"type": 1,
"value": "o1"
},
{
"type": 1,
"value": "o1"
}
],
"answer": {
"questionId": 1,
"answer": 1
},
"metaTags": [
"Season 1",
"Episode 1",
"Rick Grimmes"
]
}
},
{
"questionEntry": {
"id": 1,
"info": {
"seasonNumber": 1,
"episodeNumber": 1,
"episodeName": "Days Gone Bye"
},
"questionItem": {
"theQuestion": "q2",
"attachedElement": {
"type": 1,
"value": ""
}
},
"options": [
{
"type": 1,
"value": "o2"
},
{
"type": 1,
"value": "o2"
}
],
"answer": {
"questionId": 1,
"answer": 1
},
"metaTags": [
"Season 1",
"Episode 1",
"Rick Grimmes",
"Glenn Rhee"
]
}
}
]
}
I'm able to search for questions.questionEntry.questionItem.theQuestion for a matching criteria with:
db.questions.find({"questions.questionEntry.questionItem.theQuestion" : "q1"},{'questions.$':1}).pretty()
This works well for the questions collection but how would I do the same search across multiple collections?
Many thanks
To use the same query across multiple collections you may have to use the JavaScript bracket notation to access the collections in a loop. For example, the following queries the records database for all the collections (using the db.getCollectionNames() command) with the specified query:
use records
var colls = db.getCollectionNames(), // get all the collections in records db
query = {"questions.questionEntry.questionItem.theQuestion" : "q1"},
projection = {"questions.$": 1};
colls.forEach(function (collection){
var docs = db[collection].find(query, projection).toArray(); // use the bracket notation
docs.forEach(function (doc){ printjson(doc); });
})
You will have to do this by yourself. There is no out-of-the-box support.
You can query MongoDB multi-threaded (depending on your programming language) and aggregate the results to a unified result.

Order by nested document Mongodb

I'm having multiple documents in a collection, each document has this data structure :
{
"_id": {
"$id": "5429409c9ac25ebe338b4567"
},
"data": [
{
"data_id": "70",
"info_data": [
{
"data_id": "98",
"data_index": 0,
"value": "info data"
},
{
"data_id": "99",
"data_index": 0,
"value": "some info data
}
]
},
{
"data_id": "71",
"info_data": [
{
"data_id": "98",
"data_index": 0,
"value": "some data"
},
{
"data_id": "99",
"data_index": 0,
"value": "more data"
}
]
}
]
},
{
"_id": {
"$id": "542940ac9ac25ef6358b4567"
},
"data": [
{
....
I need to conditionally sort these documents. for example I need to sort all the data.info_data documents only when the data.info_data.data_id = 98 by data.info_data.value only
So basically I need to sort an inner document that only matches to some criteria (the inner document no the external one).
I guess I need to use aggregation with unwind but I'm not sure how.