I'am looking for a good solution on MongoDB for this problem:
There are some Category's and every Category has X items.
But some items can be in in "many" Category's!
I was looking for something like a symbolic link on Unix systems but I could't not find it.
What i thought is a good idea is:
"Category1/item1" is the Object and "category2/item44232" is only a reference to "item1" so when i change "item1" it also changes "item44232".
I looked into the MongoDB Data models documentation but there is no real solution for this.
Thank you for your response !
In RDBMSs, you use a join table to represent one-to-many relationships; in MongoDB, you use array keys. For example each product contains an array of category IDs, and both products and categories get their own collections. If you have two simple category documents
{ _id: ObjectId("4d6574baa6b804ea563c132a"),
title: "Epiphytes"
}
{ _id: ObjectId("4d6574baa6b804ea563c459d"),
title: "Greenhouse flowers"
}
then a product belonging to both categories will look like this:
{ _id: ObjectId("4d6574baa6b804ea563ca982"),
name: "Dragon Orchid",
category_ids: [ ObjectId("4d6574baa6b804ea563c132a"),
ObjectId("4d6574baa6b804ea563c459d") ]
}
for more: http://docs.mongodb.org/manual/reference/database-references/
Try looking at the problem inside-out: Instead of having items inside categories, have the items list the categories they belong into.
You'll be able to easily find all items that belong to a category (or even multiple categories), and there is no duplication nor any need to keep many instances of the same item updated.
This can be very fast and efficient, especially if you index the list of categories. Check out multikey indexes.
Related
I have 2 collection:
categories
id:
name:
books
id:
name:
categories: []
a book has an title and an array of the categories it belongs to.
I want make a query to show only all the categories if there a related book.
Otherwise a user sees category 'x' and when going to then next screen, there are 0 related books. This i don't want, so category 'x' should not be in the list of all the avaiable categories on that screen.
How to do that? Is my datamodel the right one?
Thanks inadvance for the answers ;)
In relational DBs this would be done with a Join but the issue is that Firestore doesn't have joins.
So this query will need to be performed manually by getting all the books, concatenating categories arrays and then removing repeated values.
Say I have two documents
/*1*/
{
"id":1,
"name":"natty",
"subject_enrolled":"english"
}
/*2*/
{
"id":2,
"name":"natty",
"subject_enrolled":"science"
}
Ideally, it should have been same document, with subject_enrolled being an array having both subjects. But for some reason, I maintained my data flat like this.
Now, I want to write a query which will retrieve all students who have enrolled for both "english" and "science".
I tried the below query:
db.students.find({"subject_enrolled":{"$in":["science", "english"]}})
But that is wrong, coz if any student who registered for only science will also be in the result. I cannot use "$all", as both science and english are in two different documents.
Is there a way to achieve this easily and effectively?
All I could think of now is to use an aggregate.
db.students.aggregate([{"$group":{"_id":"$name", "subject_enrolled":{"$addToSet":"$subject_enrolled"}}}, {"$match":{"subject_enrolled":{"$all":["english", "science"]}}}])
This satisfies my condition perfectly.
But I am worried about performance only. If I have say 10,000 documents, worst case I have all documents are for individual students and the querying Param is also a single value, then I will be aggregating for no good!
Mongo experts, please share your views on my situation.
I am storing some kind of filesystem in Mongo, where directories are named categories.
The category JSON looks like:
{
"name":"CategoryChildLevel2",
"parentId":"2",
"otherAttribute":"anyVal",
"breadcrumb":[
{
"name":"RootCategory",
"id":"1"
},
{
"name":"CategoryChildLevel1",
"id":"2"
}
]
}
The FS categories are linked together with the parentId attribute.
I need to display the categories breadcrumbs.
Through user navigation, we can know where we are on the FS, but categories can be accessed directly by their ID (bookmarked category, search engine...), without any FS navigation.
To avoid recursive calls to the DB, to be able to get the breadcrumb, I have denormalized it.
The problem is that this breadcrumb is hard to keep up to date, because a top level category can be moved and thus all its childs breadcrumbs must be updated. There can be many child categories to update, and there are different ways to deal with this problem.
Some of them are secure but expensive (recursion), and others are faster but could lead to some inconsistencies.
Here what I'd like to know is if it is possible to do a query to retrieve categories that have a bad breadcrumb. I need a query that permits to do:
Retrieve all the categories that do not have: last array element
breadcrumb.id = parentId
I don't think the "last array element" part is possible, but it would also be nice to be able to do:
Retrieve all the categories that do not have:
breadcrumb.id contains parentId
Any solution, available in Scala or Java driver?
I'm using Salat/Casbah.
This question may help you to understand what I'm facing: Which DB would you use? MongoDB/Neo4j/SQL... all of them?
You can do the Retrieve all the categories that do not have: last array element breadcrumb.id = parentId query with a $where operator:
db.test.find({
// Find docs were breadcrumb is empty or its last element's id != parentId
$where: '!this.breadcrumb.length || this.breadcrumb[this.breadcrumb.length-1].id !== this.parentId'
})
Here's the deal. Let's suppose we have the following data schema in MongoDB:
items: a collection with large documents that hold some data (it's absolutely irrelevant what it actually is).
item_groups: a collection with documents that contain a list of items._id called item_groups.items plus some extra data.
So, these two are tied together with a Many-to-Many relationship. But there's one tricky thing: for a certain reason I cannot store items within item groups, so -- just as the title says -- embedding is not the answer.
The query I'm really worried about is intended to find some particular groups that contain some particular items (i.e. I've got a set of criteria for each collection). In fact it also has to say how much items within each found group fitted the criteria (no items means group is not found).
The only viable solution I came up with this far is to use a Map/Reduce approach with a dummy reduce function:
function map () {
// imagine that item_criteria came from the scope.
// it's a mongodb query object.
item_criteria._id = {$in: this.items};
var group_size = db.items.count(item_criteria);
// this group holds no relevant items, skip it
if (group_size == 0) return;
var key = this._id.str;
var value = {size: group_size, ...};
emit(key, value);
}
function reduce (key, values) {
// since the map function emits each group just once,
// values will always be a list with length=1
return values[0];
}
db.runCommand({
mapreduce: item_groups,
map: map,
reduce: reduce,
query: item_groups_criteria,
scope: {item_criteria: item_criteria},
});
The problem line is:
item_criteria._id = {$in: this.items};
What if this.items.length == 5000 or even more? My RDBMS background cries out loud:
SELECT ... FROM ... WHERE whatever_id IN (over 9000 comma-separated IDs)
is definitely not a good way to go.
Thank you sooo much for your time, guys!
I hope the best answer will be something like "you're stupid, stop thinking in RDBMS style, use $its_a_kind_of_magicSphere from the latest release of MongoDB" :)
I think you are struggling with the separation of domain/object modeling from database schema modeling. I too struggled with this when trying out MongoDb.
For the sake of semantics and clarity, I'm going to substitute Groups with the word Categories
Essentially your theoretical model is a "many to many" relationship in that each Item can belong Categories, and each Category can then possess many Items.
This is best handled in your domain object modeling, not in DB schema, especially when implementing a document database (NoSQL). In your MongoDb schema you "fake" a "many to many" relationship, by using a combination of top-level document models, and embedding.
Embedding is hard to swallow for folks coming from SQL persistence back-ends, but it is an essential part of the answer. The trick is deciding whether or not it is shallow or deep, one-way or two-way, etc.
Top Level Document Models
Because your Category documents contain some data of their own and are heavily referenced by a vast number of Items, I agree with you that fully embedding them inside each Item is unwise.
Instead, treat both Item and Category objects as top-level documents. Ensure that your MongoDb schema allots a table for each one so that each document has its own ObjectId.
The next step is to decide where and how much to embed... there is no right answer as it all depends on how you use it and what your scaling ambitions are...
Embedding Decisions
1. Items
At minimum, your Item objects should have a collection property for its categories. At the very least this collection should contain the ObjectId for each Category.
My suggestion would be to add to this collection, the data you use when interacting with the Item most often...
For example, if I want to list a bunch of items on my web page in a grid, and show the names of the categories they are part of. It is obvious that I don't need to know everything about the Category, but if I only have the ObjectId embedded, a second query would be necessary to get any detail about it at all.
Instead what would make most sense is to embed the Category's Name property in the collection along with the ObjectId, so that pulling back an Item can now display its category names without another query.
The biggest thing to remember is that the key/value objects embedded in your Item that "represent" a Category do not have to match the real Category document model... It is not OOP or relational database modeling.
2. Categories
In reverse you might choose to leave embedding one-way, and not have any Item info in your Category documents... or you might choose to add a collection for Item data much like above (ObjectId, or ObjectId + Name)...
In this direction, I would personally lean toward having nothing embedded... more than likely if I want Item information for my category, i want lots of it, more than just a name... and deep-embedding a top-level document (Item) makes no sense. I would simply resign myself to querying the database for an Items collection where each one possesed the ObjectId of my Category in its collection of Categories.
Phew... confusing for sure. The point is, you will have some data duplication and you will have to tweak your models to your usage for best performance. The good news is that that is what MongoDb and other document databases are good at...
Why don't use the opposite design ?
You are storing items and item_groups. If your first idea to store items in item_group entries then maybe the opposite is not a bad idea :-)
Let me explain:
in each item you store the groups it belongs to. (You are in NOSql, data duplication is ok!)
for example, let's say you store in item entries a list called groups and your items look like :
{ _id : ....
, name : ....
, groups : [ ObjectId(...), ObjectId(...),ObjectId(...)]
}
Then the idea of map reduce takes a lot of power :
map = function() {
this.groups.forEach( function(groupKey) {
emit(groupKey, new Array(this))
}
}
reduce = function(key,values) {
return Array.concat(values);
}
db.runCommand({
mapreduce : items,
map : map,
reduce : reduce,
query : {_id : {$in : [...,....,.....] }}//put here you item ids
})
You can add some parameters (finalize for instance to modify the output of the map reduce) but this might help you.
Of course you need to have another collection where you store the details of item_groups if you need to have it but in some case (if this informations about item_groups doe not exist, or don't change, or you don't care that you don't have the most updated version of it) you don't need them at all !
Does that give you a hint about a solution to your problem ?
in mysql i use JOIN and one query is no problem. what about mongo?
imagine categories and products.
products may have more categories.
categories may have more product.
(many to many structure)
and administrator may edit categories in administration (categories must be separated)
its possible write product with categories names in one query?
i used this structure
categories {
name:"categoryName",
product_id:["4b5783300334000000000aa9","5783300334000000000aa943","6c6793300334001000000006"]
}
products {
name:"productName",
category_id:["4b5783300334000000000bb9","5783300334000000000bb943","6c6793300334001000000116"]
}
now i can simply get all product categories, and product in some category and categories alone for editation. but if i want write product with categories names i need two queries - one to get product categories id and second to get categories names from categories by that ids.
is this the right way? or this structure is unsuitable? i would like to have only one query but i dont know if its possible.
Yep, MongoDB is specifically bad at this particular type of operation. However, it's also a matter of scope. If you have 30 million products and you want to join the 3 million Products to their Category, you'll find that it's not very quick in many Databases (even though it's one line of code).
What MongoDB requires here is some de-normalization.
Your collections will probably look like this:
categories {
_id:"mongoid",
name:"categoryName",
...
}
products {
_id:"mongoid",
name:"productName",
categories:[
{"4b5783300334000000000bb9":'categoryName'},
{"5783300334000000000bb943":'CategoryName2'},
{"6c6793300334001000000116":'categoryName3'}
]
}
Here's how it will work (rough estimate, don't have my Mongo instance handy):
Grab products and their categories
db.products.find({"_id": {$in:[1,2,3]}, {name:1,categories:1})
Grab products with a certain category:
db.products.find({"categories.0": {$in:[x,y,z]}},{categories:1,name:1} }
Yes, it's not quite the same. And you will have to will have to update all of the product records when you change the name of a category. I know this seems weird, but this is standard for denormalization.
BTW, I'm assuming here that you're going to have lots of products and significantly less categories. This method is probably best if you have say 100 items in each category. If you have 5 items in each category, then you'll probably want to store more of the category information in each item.
Yes it's the only way you have. There are no join possibility.
But you can add a lot of information in your categories or product document like :
categories {
name:"categoryName",
product_id:[ {"4b5783300334000000000aa9": 'productName'},{"5783300334000000000aa943":'productName2'},{"6c6793300334001000000006":'productName3'}]
}
products {
name:"productName",
category_id:[{"4b5783300334000000000bb9":'categoryName'},{"5783300334000000000bb943":'CategoryName2',{"6c6793300334001000000116":'categoryName3'}]
}
But you need made some callback for update all document when on is change.