Dynamically updating and getting data in MongoDB - mongodb

so I've been messing around with Mongo lately using Mongoose and I came to a bump lately. I want to update and get something but without specifically targeting it. Let me explain myself better.
I have this schema:
id: {
required: true,
type: String
},
information: {
number: String,
Identification: String,
title: String,
address: String
},
products: {
}
Now ofcourse I won't hardcode every product into the schema because there are a lot of products..etc, what I eventually want to do is to update doc.updateOne({'products.productIDHere.review': newReviewData}, { new: true, upsert: true, setDefaultsOnInsert: true })
So whenever a client changes their review or rating..etc it will update that.
Here are my questions:
1- How do I insert the products individually without overwriting everything within products:{}.
2- How do I update the review or rating value within a certain product.
3- How do I get information about that product because I cannot do something like doc.products.product.id.review, product.id is the only information I have about the product.
4- Do I need to change something about the schema?
Please try to answer with Mongoose as some answers are different in MongoDB than how their executed in Mongoose. No problem if you rather answer in MongoDB sense though.

This is a time-honored data design: products and reviews. A good, simple, scalable way to approach it is with two collections: product and reviews. The product collection contains all details about a product and carries the product ID (pid):
{pid: "ABC123", name: "TV", manu: "Sony", ...}
{pid: "G765", name: "Fridge", manu: "Whirlpool", ...}
The reviews collection is an ever-growing list of pid, timestamp, and review information.
{pid: "G765", ts: ISODate("2020-03-04), author: "A1", review: "Great", rating: 4}
{pid: "G765", ts: ISODate("2020-03-05), author: "A2", review: "Good", rating: 3}
{pid: "G765", ts: ISODate("2020-03-06), author: "A3", review: "Awesome", rating: 5}
If you're thinking this sounds very relational, that's because it is and it is a good design pattern.
It answers the OP questions easily:
1- How do I insert the products individually without overwriting everything within products:{}. ANSWER: You simply add a new product doc with a new pid to the product collection.
2- How do I update the review or rating value within a certain product. ANSWER Not sure you want to do that; you probably want to accumulate reviews over time. But since each review is a separate doc (with a separate _id) you can easily do this:
db.reviews.update({_id:targetID},{$set: {review:"new text"}});
3- How do I get information about that product because I cannot do something like doc.products.product.id.review, product.id is the only information I have about the product.
Easy:
db.product.find({pid:"ABC123"})
or
db.product.find({name:"TV"})

Related

MongoDB multiple schemas in one collection

I am new to mongo and have done a lot of reading and a proof of concept. There are many discussions about multiple collections or embedded documents. Isn't there another choice? Ignoring my relationalDB mind... Couldn't you put two different schemas in the same collection?
Crude example:
{
_id: 'f48a2dea-e6ec-490d-862a-bd1791e76d9e',
_owner: '7a147aad-e3fd-4e55-9fd5-e2cb48d31a83'
manufacturer: 'Porsche',
model: '911',
img: '<byte array>'
},{
_id: '821ca9b7-faa1-4516-a27e-aec79fcb89a9',
_owner: '46ade116-cd59-4d0c-a4d3-cd2e517a256c',
manufacturer: 'Nissan',
model: 'GT-R',
img: '<byte array>'
},{
_id: '87999e27-c98b-4cad-b444-75626f161840'
_owner: 'fba765c8-32dd-49ba-91d3-d361b40bf4a7',
manufacturer: 'BMW',
model: 'M3',
wiki:'http://en.wikipedia.org/wiki/Bmw_m3',
img: '<byte array>'
}
and a totally difference schema in the same collection as well
{
_id: '7a147aad-e3fd-4e55-9fd5-e2cb48d31a83',
name: 'Keeley Bosco',
email: 'katlyn#jenkinsmaggio.net,
city": 'Lake Gladysberg',
mac: '08:fd:0b:cd:77:f7',
timestamp: '2015-04-25 13:57:36 +0700',
},{
_id: '46ade116-cd59-4d0c-a4d3-cd2e517a256c',
name: 'Rubye Jerde',
email: 'juvenal#johnston.name',
city: null,
mac: '90:4d:fa:42:63:a2',
timestamp: '2015-04-25 09:02:04 +0700',
},{
_id: 'fba765c8-32dd-49ba-91d3-d361b40bf4a7',
name: 'Miss Darian Breitenberg',
email: null,
city: null,
mac: 'f9:0e:d3:40:cb:e9',
timestamp: '2015-04-25 13:16:03 +0700',
}
(The reason I don't use an embedded document (in my real POC) is that a person may have 80000 "cars" and go over the 16MB limit).
Besides the aching desire to compartmentalize data is there a downfall here?
The reasoning for doing this may be so that we can correlate the records... I do see that 3.2 has join. The project it too new to know all of the business cases.
Although Mongodb supports different schema within a same collection. However, as a good practice, better to stick to one schema or similar schema through out the collection, so your application logic will be simpler.
In your case, yes, it is good that you didn't use a embedded document considering the size of the sub document. However, I would suggest to go for normalized data model which is not really bad in this kind of situation.
Further you can refer here: https://docs.mongodb.com/master/core/data-model-design/

Meteor/Mongodb - arrays, subdocuments & overlapping subscriptions

I'm making a game; players form leagues and make competing predictions. A league looks like this:
{ leagueName: "Premier League",
players:[
{name: "Goodie", secretPrediction: "abc"},
{name: "Baddie", secretPrediction: "def"}
] }
For each player, I need to publish to the client the names of all the players in the league, but only their own secret prediction. So from above, if Goodie is logged in, the document on mini-mongo should be:
{ leagueName: "Premier League",
players:[
{name: "Goodie", secretPrediction: "abc"},
{name: "Baddie"}
] }
To do this, I have two publications - one to get the whole League document but excluding ALL secret predictions, and one to get the current player's subdocument in the players array including her secret prediction. My publications are:
// Publish whole players array excluding secretPrediction
Leagues.find({"players.name": "Goodie"}, {fields: {"players.secretPrediction": 0}})
// Publish the whole Goodie item in the players array and nothing else
Leagues.find({"players.name": "Goodie"}, {fields: {players: {$elemMatch: {name: "Goodie"}}}})
The problem is that when I subscribe to both the above publications, I don't get the document I want - the secret prediction is excluded even with the second publication. (On their own, the publications behave as expected, it's only when I subscribe to both.)
Now, I understand from this answer that the two publications should be "merged" on the client
Down to the level of top level fields, Meteor takes care to perform a set union among documents, such that subscriptions can overlap - publish functions that ship different top level fields to the client work side by side and on the client, the document in the collection will be the union of the two sets of fields.
So I have two main questions (and well done / thanks for making it this far!):
Is the union of documents not happening because I'm not dealing with top level fields? Is there a way around this?
Am I going about this completely the wrong way? Is there a better way to get the results I want?
Yes, the merging multiple subscriptions of Meteor only works with the top level fields, it is mentioned in the Meteor docs: Meteor.subscribe
I can not say that you are heading the wrong direction, this really depends on your situation, what features you want to help. Only speak of myself, I would decouple the above collection to two separate collections. Because players may join many leagues and leagues may have many players, so their relation is many-to-many (n-n). For this kind of relation, we should split them to two collections and use an associative table to reflect their relation
So in your case, I would have:
League collection:
[{
_id: 'league1',
name: 'League 1',
// ...
}]
Player collection:
[{
_id: 'player1',
name: 'Player 1',
// ...
}]
League2Player collection:
[{
_id: 'league1palyer1',
playerId: 'player1',
leagueId: 'league1',
secretPrediction: 'abc',
// ...
}]
Could you instead rearrange the data document so that you can use a single query e.g.
{ leagueName: "Premier League",
players:[
{name: "Goodie"},
{name: "Baddie"}
]
playerPredictions:[
{name: "Goodie", secretPrediction: "abc"},
{name: "Baddie", secretPrediction: "def"}
]
}
That way it would be possible in a single query to return all the players and only the playerPrediction for the given person.

Meteor Collection: find element in array

I have no experience with NoSQL. So, I think, if I just try to ask about the code, my question can be incorrect. Instead, let me explain my problem.
Suppose I have e-store. I have catalogs
Catalogs = new Mongo.Collection('catalogs);
and products in that catalogs
Products = new Mongo.Collection('products');
Then, people add there orders to temporary collection
Order = new Mongo.Collection();
Then, people submit their comments, phone, etc and order. I save it to collection Operations:
Operations.insert({
phone: "phone",
comment: "comment",
etc: "etc"
savedOrder: Order //<- Array, right? Or Object will be better?
});
Nice, but when i want to get stats by every product, in what Operations product have used. How can I search thru my Operations and find every operation with that product?
Or this way is bad? How real pro's made this in real world?
If I understand it well, here is a sample document as stored in your Operation collection:
{
clientRef: "john-001",
phone: "12345678",
other: "etc.",
savedOrder: {
"someMetadataAboutOrder": "...",
"lines" : [
{ qty: 1, itemRef: "XYZ001", unitPriceInCts: 1050, desc: "USB Pen Drive 8G" },
{ qty: 1, itemRef: "ABC002", unitPriceInCts: 19995, desc: "Entry level motherboard" },
]
}
},
{
clientRef: "paul-002",
phone: null,
other: "etc.",
savedOrder: {
"someMetadataAboutOrder": "...",
"lines" : [
{ qty: 3, itemRef: "XYZ001", unitPriceInCts: 950, desc: "USB Pen Drive 8G" },
]
}
},
Given that, to find all operations having item reference XYZ001 you simply have to query:
> db.operations.find({"savedOrder.lines.itemRef":"XYZ001"})
This will return the whole document. If instead you are only interested in the client reference (and operation _id), you will use a projection as an extra argument to find:
> db.operations.find({"savedOrder.lines.itemRef":"XYZ001"}, {"clientRef": 1})
{ "_id" : ObjectId("556f07b5d5f2fb3f94b8c179"), "clientRef" : "john-001" }
{ "_id" : ObjectId("556f07b5d5f2fb3f94b8c17a"), "clientRef" : "paul-002" }
If you need to perform multi-documents (incl. multi-embedded documents) operations, you should take a look at the aggregation framework:
For example, to calculate the total of an order:
> db.operations.aggregate([
{$match: { "_id" : ObjectId("556f07b5d5f2fb3f94b8c179") }},
{$unwind: "$savedOrder.lines" },
{$group: { _id: "$_id",
total: {$sum: {$multiply: ["$savedOrder.lines.qty",
"$savedOrder.lines.unitPriceInCts"]}}
}}
])
{ "_id" : ObjectId("556f07b5d5f2fb3f94b8c179"), "total" : 21045 }
I'm an eternal newbie, but since no answer is posted, I'll give it a try.
First, start by installing robomongo or a similar software, it will allow you to have a look at your collections directly in mongoDB (btw, the default port is 3001)
The way I deal with your kind of problem is by using the _id field. It is a field automatically generated by mongoDB, and you can safely use it as an ID for any item in your collections.
Your catalog collection should have a string array field called product where you find all your products collection items _id. Same thing for the operations: if an order is an array of products _id, you can do the same and store this array of products _id in your savedOrder field. Feel free to add more fields in savedOrder if necessary, e.g. you make an array of objects products with additional fields such as discount.
Concerning your queries code, I assume you will find all you need on the web as soon as you figure out what your structure is.
For example, if you have a product array in your savedorder array, you can pull it out like that:
Operations.find({_id: "your operation ID"},{"savedOrder.products":1)
Basically, you ask for all the products _id in a specific operation. If you have several savedOrders in only one operation, you can specify too the savedOrder _id, if you used the one you had in your local collection.
Operations.find({_id: "your_operation_ID", "savedOrder._id": "your_savedOrder_ID"},{"savedOrder.products":1)
ps: to bad-ass coders here, if I'm doing it wrong, please tell me.
I find an answer :) Of course, this is not a reveal for real professionals, but is a big step for me. Maybe my experience someone find useful. All magic in using correct mongo operators. Let solve this problem in pseudocode.
We have a structure like this:
Operations:
1. Operation: {
_id: <- Mongo create this unique for us
phone: "phone1",
comment: "comment1",
savedOrder: [
{
_id: <- and again
productId: <- whe should save our product ID from 'products'
name: "Banana",
quantity: 100
},
{
_id:,
productId: <- Another ID, that we should save if order
name: "apple",
quantity: 50
}
]
And if we want to know, in what Operation user take "banana", we should use mongoDB operator"elemMatch" in Mongo docs
db.getCollection('operations').find({}, {savedOrder: {$elemMatch:{productId: "f5mhs8c2pLnNNiC5v"}}});
In simple, we get documents our saved order have products with id that we want to find. I don't know is it the best way, but it works for me :) Thank you!

Mongodb. Find documents by key from other collection

mongodb:
#Authors collection
db.authors.insert({name: 'Kobo', birthday: '1860', country: 'jp', tags: ['japan', 'avant-garde', 'screen', 'Akutagawa Prize']})
db.authors.insert({name: 'Sartr', birthday: '1905', country: 'fr', tags: [...]})
db.authors.insert({name: 'Braun', birthday: '1913', country: 'us', tags: [...]}
...
#Books collection
db.books.insert({title: 'book1', author: 'Kobo', year :''});
db.books.insert({title: 'book2', author: 'Sartr', year :''});
db.books.insert({title: 'book3', author: 'author', year :''});
...
Autor tags are regularly added to the collection Authors.
Using book tags is not considered in this question.
Need to find all the books in which the author has a certain tag. Such as 'avant-garde'.
What is the most effective way to do this?
How to do it in pymongo?
MongoDB does not support joins. So, with the data model you have, you'll have to break it down into 2 queries:
Query the authors collection to get list of all authors that have a certain tag
Query the books collection using the results from Step 1 to get list of books
If your data was modeled using embedded documents, i.e., either embedding authors inside books or the other way around, you can get the results in one query.

How to store related records in mongodb?

I have a number of associated records such as below.
Parent records
{
_id:234,
title: "title1",
name: "name1",
association:"assoc1"
},
Child record
{
_id:21,
title: "child title",
name: "child name1",
},
I want to store such records into MongoDb. Can anyone help?
Regards.
Even MongoDB doesn't support joins, you can organize data in several different ways:
1) First of all, you can inline(or embed) related documents. This case is useful, if you have some hierarchy of document, e.g. post and comments. In this case you can like so:
{
_id: <post_id>,
title: 'asdf',
text: 'asdf asdf',
comments: [
{<comment #1>},
{<comment #2>},
...
]
}
In this case, all related data will be in the save document. You can fetch it by one query, but pushing new comments to post cause moving this document on disk, frequent updates will increase disk load and space usage.
2) referencing is other technique you can use: in each document, you can put special field that contains _id of parent/related object:
{
_id: 1,
type: 'post',
title: 'asdf',
text: 'asdf asdf'
},
{
_id:2
type: 'comment',
text: 'yep!',
parent_id: 1
}
In this case you store posts and comments in same collection, therefor you have to store additional field type. MongoDB doesn't support constraints or any other way to check data constancy. This means that if you delete post with _id=1, comments with _id=2 store broken link in parent_id.
You can separate posts from comments in different collections or even databases by using database references, see your driver documentation for more details.
Both solutions can store tree-structured date, but in different way.