Modelling relationships in Mongo
This commit is contained in:
parent
b82603023b
commit
46f0e76119
2 changed files with 138 additions and 66 deletions
73
Databases/MongoDB/Modelling_relationships.md
Normal file
73
Databases/MongoDB/Modelling_relationships.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
catagories:
|
||||
- Databases
|
||||
tags:
|
||||
- mongo_db
|
||||
- node-js
|
||||
- mongoose
|
||||
---
|
||||
|
||||
# Modelling relationships between data
|
||||
|
||||
So far we have taken the values of a document to be simple: the `author` value is just a string but what if `author` was itself another collection for instance a collection with the proprties `age` and `publications` and the author name, say `Tosh Gnomay` was a document in this collection? This means we would have an interaction between two collections. In this entry we will look at how to work with interrelated collections. This is equivalent to establishing [joins](/Databases/SQL/10_Joins.md) in a relational database.
|
||||
|
||||
There are two main approaches to modelling relationships between data: **normalisation** and **denormalisation**.
|
||||
|
||||
## Modelling relationships with normalisation: using references
|
||||
|
||||
In the case of normalisation we use a UUID from one document to connect it to another document.
|
||||
|
||||
Let's say that we have a course document as follows which is an instance of the `courses` collection:
|
||||
|
||||
```js
|
||||
{
|
||||
_id: "ceds89e"
|
||||
name: "Java course",
|
||||
author: "Tosh Gnomay"
|
||||
}
|
||||
```
|
||||
|
||||
And we have a document which is an instance of another type of collection called `authors`. One instance of it could be:
|
||||
|
||||
```js
|
||||
{
|
||||
_id: "de89w9",
|
||||
name: "Tosh Gnomay",
|
||||
courses: ["Java course", "C# course"]
|
||||
}
|
||||
```
|
||||
|
||||
Using normalisation, we would establish the relationship by using the value of the `_id` propety in the `courses` document as the value of the `name` property in the `authors` document. We would rewrite the `courses` document like so:
|
||||
|
||||
```js
|
||||
{
|
||||
_id: "ceds89e"
|
||||
name: "Java course",
|
||||
author: "de89w9"
|
||||
}
|
||||
```
|
||||
|
||||
## Modelling relationships with denormalisation: embedding
|
||||
|
||||
With denormalisation we achieve the same outcome but instead of using a reference we embed one value in another:
|
||||
|
||||
```js
|
||||
{
|
||||
_id: "ceds89e"
|
||||
name: "Java course",
|
||||
author: {
|
||||
name: "Tosh Gnomay",
|
||||
courses: ["Java course", "C# course"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tradeoffs: which approach to use?
|
||||
|
||||
There are drawbacks and benefits with each approach.
|
||||
|
||||
- One benefit of normalisation is consistency. Because we are using the UUID and referencing it in another document, if we make a change to the values in the source document, it will automatically apply to all documents that reference it. We make a change in one place and it is reflected everywhere.
|
||||
|
||||
- Conversely, a drawback of denormalisation is that it invites inconsistency between documents. Embedded values can easily go out of sync with their usage elsewhere as you don't have a direct reference correspondence.
|
||||
|
||||
- On the other hand a drawback of normalisation is performance when querying. Any time you have a document that references another document than this document also has to be retrieved. Therefore for every query you are returning two documents. As denormalised references embed the second document, only one document is being returned on each query.
|
|
@ -1,12 +1,13 @@
|
|||
---
|
||||
catagories:
|
||||
- Databases
|
||||
tags:
|
||||
- Databases
|
||||
- mongo_db
|
||||
- node-js
|
||||
- mongoose
|
||||
- mongo_db
|
||||
- node-js
|
||||
- mongoose
|
||||
---
|
||||
|
||||
# Query a Mongo collection with Mongoose
|
||||
# Query a Mongo collection with Mongoose
|
||||
|
||||
We now have the following entries in our `courses` collection:
|
||||
|
||||
|
@ -35,23 +36,23 @@ We now have the following entries in our `courses` collection:
|
|||
|
||||
Now we will query the collection. This capability is provided via the Mongoose schema class we used to create the `Course` [model](/Databases/MongoDB/Create_collections_and_documents_with_Mongoose.md#models). We have the following methods available to use from the schema:
|
||||
|
||||
* `find`
|
||||
* `findById`
|
||||
* `findByIdAndRemove`
|
||||
* `findByIdAndUpdate`
|
||||
* `findOne`
|
||||
* `findOneAndUpdate`
|
||||
* `findOneAndRemove`
|
||||
* ...
|
||||
- `find`
|
||||
- `findById`
|
||||
- `findByIdAndRemove`
|
||||
- `findByIdAndUpdate`
|
||||
- `findOne`
|
||||
- `findOneAndUpdate`
|
||||
- `findOneAndRemove`
|
||||
- ...
|
||||
|
||||
The various `find` methods return a value that is promisified.
|
||||
|
||||
## Return values with `find`
|
||||
|
||||
```js
|
||||
async function getCourses(){
|
||||
const courses = await Course.find()
|
||||
console.log(courses)
|
||||
async function getCourses() {
|
||||
const courses = await Course.find();
|
||||
console.log(courses);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -60,49 +61,49 @@ async function getCourses(){
|
|||
This will return all the published courses where Tosh Gnomay is the author:
|
||||
|
||||
```js
|
||||
async function getCourses(){
|
||||
const courses = await Course.find({author: 'Tosh Gnomay', isPublished: true})
|
||||
console.log(courses)
|
||||
async function getCourses() {
|
||||
const courses = await Course.find({author: 'Tosh Gnomay', isPublished: true});
|
||||
console.log(courses);
|
||||
}
|
||||
```
|
||||
|
||||
This time we will filter by the same author but we will add additional parameters to distinguish:
|
||||
|
||||
* only the first ten entries (using `.limit(10)`)
|
||||
* sort ascending by name (using `.sort({name: 1}))` , to descend we would use `-1`)
|
||||
* only return the properties `name` and `tags` for the item in the collection (using `.select({name: 1, tags: 1})`)
|
||||
- only the first ten entries (using `.limit(10)`)
|
||||
- sort ascending by name (using `.sort({name: 1}))` , to descend we would use `-1`)
|
||||
- only return the properties `name` and `tags` for the item in the collection (using `.select({name: 1, tags: 1})`)
|
||||
|
||||
```js
|
||||
async function getCourses(){
|
||||
const courses = await Course
|
||||
.find({author: 'Tosh Gnomay', isPublished: true})
|
||||
.limit(10)
|
||||
.sort({name: 1})
|
||||
.select({name: 1, tags: 1})
|
||||
console.log(courses)
|
||||
```js
|
||||
async function getCourses() {
|
||||
const courses = await Course.find({author: 'Tosh Gnomay', isPublished: true})
|
||||
.limit(10)
|
||||
.sort({name: 1})
|
||||
.select({name: 1, tags: 1});
|
||||
console.log(courses);
|
||||
}
|
||||
```
|
||||
|
||||
This returns:
|
||||
This returns:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
_id: new ObjectId("62f4f07a875cff48827b8731"),
|
||||
_id: new ObjectId('62f4f07a875cff48827b8731'),
|
||||
name: 'Java Course',
|
||||
tags: [ 'java', 'backend' ]
|
||||
tags: ['java', 'backend'],
|
||||
},
|
||||
{
|
||||
_id: new ObjectId("62f4e2527ac4aa2c30d41d24"),
|
||||
_id: new ObjectId('62f4e2527ac4aa2c30d41d24'),
|
||||
name: 'Javascript Course',
|
||||
tags: [ 'js', 'frontend' ]
|
||||
}
|
||||
]
|
||||
tags: ['js', 'frontend'],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
> Note that the UUID is always returned, whether we specify it or not.
|
||||
|
||||
## Querying with operators
|
||||
|
||||
So far when filtering we have been doing so with reference to properties that exist on the document's model (`author`, `isPublished` etc) and we have applied tranformations on the data returned (sorting, limiting the number or matches etc.). However we can also apply **operators** within our queries. Operators allow us to perform computations on the data, for example: for a given numerical property on an object, return the objects for which this value is within a certain range.
|
||||
|
||||
When we apply operators we use the `$` symbol before the operator and pass the operator function as an object.
|
||||
|
@ -114,10 +115,11 @@ Model.find( { property: { $operator: conditions } } )
|
|||
```
|
||||
|
||||
### Comparison operators
|
||||
|
||||
The following comparison operators are available in MongoDB:
|
||||
|
||||
| Operator | Function |
|
||||
|----------|---------------------------|
|
||||
| -------- | ------------------------- |
|
||||
| `eq` | Equal to |
|
||||
| `ne` | Not equal to |
|
||||
| `gt` | Greater than |
|
||||
|
@ -127,37 +129,36 @@ The following comparison operators are available in MongoDB:
|
|||
| `in` | In |
|
||||
| `nin` | Not in |
|
||||
|
||||
We can employ these comparators within a `.find` filter. For example let's imagine that our `courses` instances have a property of `price`.
|
||||
We can employ these comparators within a `.find` filter. For example let's imagine that our `courses` instances have a property of `price`.
|
||||
|
||||
To filter course prices that are greater than or equal to 10 and less than or equal to 29:
|
||||
To filter course prices that are greater than or equal to 10 and less than or equal to 29:
|
||||
|
||||
```js
|
||||
Course.find(({price: {$gte: 10, $lte: 20} }))
|
||||
Course.find({price: {$gte: 10, $lte: 20}});
|
||||
```
|
||||
|
||||
To filter course prices that are either 10, 15 or 20:
|
||||
|
||||
```js
|
||||
Course.find(({price: {$in: [10, 15, 20] } }))
|
||||
Course.find({price: {$in: [10, 15, 20]}});
|
||||
```
|
||||
|
||||
### Logical operators
|
||||
|
||||
When we apply logical operators, we do not apply the query within the main `find` method. We use a dedicated method that corresponds to the logical predicate.
|
||||
When we apply logical operators, we do not apply the query within the main `find` method. We use a dedicated method that corresponds to the logical predicate.
|
||||
|
||||
For example to query by logical [OR](/Logic/Truth-functional_connectives.md#disjunction):
|
||||
|
||||
```js
|
||||
async function getCourses(){
|
||||
const courses = await Course
|
||||
.find()
|
||||
.or([ { author: "Tosh Gnomay"}, {isPublished: true} ])
|
||||
console.log(courses)
|
||||
async function getCourses() {
|
||||
const courses = await Course.find().or([{author: 'Tosh Gnomay'}, {isPublished: true}]);
|
||||
console.log(courses);
|
||||
}
|
||||
```
|
||||
|
||||
We write each disjunct as an object representing the conditions we are filtering for within an array that is passed to the `.or()` method.
|
||||
|
||||
The same syntax applies for conjunction.
|
||||
The same syntax applies for conjunction.
|
||||
|
||||
### Regular expressions
|
||||
|
||||
|
@ -168,6 +169,7 @@ Previously we filtered by the author name:
|
|||
```js
|
||||
.find({author: "Tosh Gnomay"})
|
||||
```
|
||||
|
||||
To demonstrate regex we could filter by names beginning with `T`:
|
||||
|
||||
```js
|
||||
|
@ -175,18 +177,17 @@ To demonstrate regex we could filter by names beginning with `T`:
|
|||
```
|
||||
|
||||
```js
|
||||
async function getCourses(){
|
||||
const courses = await Course
|
||||
.find()
|
||||
.or([ { author: "Tosh Gnomay"}, {isPublished: true} ])
|
||||
.count()
|
||||
console.log(courses)
|
||||
async function getCourses() {
|
||||
const courses = await Course.find()
|
||||
.or([{author: 'Tosh Gnomay'}, {isPublished: true}])
|
||||
.count();
|
||||
console.log(courses);
|
||||
}
|
||||
```
|
||||
|
||||
This will return a number.
|
||||
|
||||
### Pagination
|
||||
### Pagination
|
||||
|
||||
We previously used the `limit()` method to control how many matches we return from a query. We can extend this functionality by creating pagination. This allows us to meter the return values into set chunks. This is used frequently when interacting with a database through a mediating RESTful API. For example to return values from endpoints such as:
|
||||
|
||||
|
@ -194,18 +195,16 @@ We previously used the `limit()` method to control how many matches we return fr
|
|||
/api/courses?pageNumber=2&pageSize=10
|
||||
```
|
||||
|
||||
To do this you pass two values as query parameters
|
||||
|
||||
```js
|
||||
To do this you pass two values as query parameters
|
||||
|
||||
```js
|
||||
const pageNumber = 2;
|
||||
const pageSize = 10;
|
||||
|
||||
async function getCourses(){
|
||||
const courses = await Course
|
||||
.find()
|
||||
.skip((pageNumber - 1 * pageSize))
|
||||
.limit(pageSize)
|
||||
console.log(courses)
|
||||
async function getCourses() {
|
||||
const courses = await Course.find()
|
||||
.skip(pageNumber - 1 * pageSize)
|
||||
.limit(pageSize);
|
||||
console.log(courses);
|
||||
}
|
||||
```
|
||||
```
|
||||
|
|
Loading…
Add table
Reference in a new issue