# MongoDB 學習筆記

## 前言
因為工作上遇到某個專案資料儲存相關的問題,我們專案需求使用 PostgreSQL,但這個資料特性是欄位長度不固定,因此起初的做法是將每一筆資料切片成多段(以欄位、值的方式切分)存進資料庫,來解決長度不一的問題;但實作後發現效率實在太差,才開始考慮使用 NoSQL 的資料庫比較一下差異。
* [安裝 MongoDB](#安裝-MongoDB)
* [安裝 MongoDB for Ubuntu 22.04](https://hackmd.io/@e9gZC-1CQNOzcD2pMY6apA/BybNPRlxA)
* [安裝管理工具](#安裝管理工具)
* [資料庫基本操作(CRUD)](#資料庫基本操作(CRUD))
## 安裝 MongoDB
> ### MongoDB 產品簡介
> MongoDB 是一個物件導向的 NoSQL 資料庫,他們家有兩個主要產品線:
> * **MongoDB Atlas**
> * **MongoDB Server**。
>
> MongoDB Atlas 提供雲端資料庫平台服務(按需付費),而 MongoDB Server 提供使用者下載至本地端自行部署的服務,其中又分為 Community Server 和 Enterprise Server 兩種版本,這次我們選擇使用 Community Server 免費開源的版本。
>
> 若對於雲端部署有興趣的可以參考這篇文章 [MongoDB Atlas 教學](https://mengchiehliu.github.io/posts/pymongo/)
1. 到 [MongoDB 官網的 Community Server](https://www.mongodb.com/try/download/community) 找到最新版本下載

2. 將下載檔案解壓縮後,拖曳至專案目錄下,並將資料夾名稱改名為`mongodb`,方便後續操作它

3. 在專案目錄下建立`mongodb-data`資料夾,用來存放資料

4. 輸入指令開啟資料庫`mongodb/bin/mongod --dbpath ./mongodb-data`
初次開啟時會跳出此警告,接著我們去系統設定調整一下

5. 打開隱私權與安全性,找到被阻擋的訊息,點選強制允許即可

6. 出現`Waiting for connections port 27017`,表示資料庫服務已啟用

## 安裝管理工具
資料庫啟用之後,我們需要安裝管理工具來連線操作資料庫,官網提供兩種選項,擇一安裝即可
1. **[MongoDB Shell (CLI)](https://www.mongodb.com/try/download/shell)**

2. **[MongoDB Compass (GUI)](https://www.mongodb.com/try/download/compass)**

> 上述兩種工具安裝後直接執行即可,需注意的是要確認資料庫服務已開啟
## 資料庫基本操作(CRUD)
首先我們要先了解在 MongoDB 資料庫裡最基本的**組成概念**、**資料型別**
**組成概念**
Database 包含了多個 Collections,而 Collection 由多個 Documents 所組成。

>這張圖以 SQL vs MongoDB 來比較兩者的對應關係。

**資料型別**
MongoDB 所儲存的格式是Binary JSON 通稱為 BSON,都是以 JSON 的格式呈現,但儲存的資料型別還是有差異的,以下列表取自[ MongoDB Docs](https://www.mongodb.com/docs/manual/reference/bson-types/) ,點進去可以看到詳細說明。
| Type | Number | Alias | Notes |
|--------------------|--------|------------|-----------------|
| Double | 1 | "double" | |
| String | 2 | "string" | |
| Object | 3 | "object" | |
| Array | 4 | "array" | |
| Binary data | 5 | "binData" | |
| Undefined | 6 | "undefined"| Deprecated. |
| ObjectId | 7 | "objectId" | |
| Boolean | 8 | "bool" | |
| Date | 9 | "date" | |
| Null | 10 | "null" | |
| Regular Expression | 11 | "regex" | |
| DBPointer | 12 | "dbPointer"| Deprecated. |
| JavaScript | 13 | "javascript"| |
| Symbol | 14 | "symbol" | Deprecated. |
| 32-bit integer | 16 | "int" | |
| Timestamp | 17 | "timestamp"| |
| 64-bit integer | 18 | "long" | |
| Decimal128 | 19 | "decimal" | |
| Min key | -1 | "minKey" | |
| Max key | 127 | "maxKey" | |
### 基本指令
* 列出所有 database
`show dbs`
* 切換至指定的 database (若不存在則創建新的 database)
`use <database>`
* 建立 collection
`db.createCollection("rawdata")`
* 列出當前 database 所有的 collection
`db.getCollectionNames()`
* 顯示當前的 database
`db.getName()`
* 刪除當前的 database
`db.fropDatabase()`
* 刪除指定的 collection
`db.`
### 新增指令
* 新增單筆資料

```mongodb=
# 新增單筆資料
# db.rawdata.insertOne(document, options)
db.rawdata.insertOne({
"identity/LineItemId": "q4rf7k5pgrrbbudrcjzpkdfhwpprovpgwklgdnw7xy5shpny25ha",
"identity/TimeInterval": "2024-01-01T00:00:00Z/2024-02-01T00:00:00Z",
"lineItem/UsageAccountId": "934075583300",
})
# 新增成功的話會回傳以下資訊
{
acknowledged: true,
insertedId: ObjectId('660a346f81e20dbb5ca2948f')
}
```
* 新增多筆資料

```mongodb=
# 新增多筆資料
# db.rawdata.insertMany(documents, options)
db.rawdata.insertMany([
{
"name": "wilson",
"age": 28,
},
{
"name": "tony",
"age": 46,
},
])
# 新增成功的話回傳以下資訊
{
acknowledged: true,
insertedIds: {
'0': ObjectId('660a365681e20dbb5ca29490'),
'1': ObjectId('660a365681e20dbb5ca29491')
}
}
```
### 查詢指令
* 查詢單筆資料
```mongodb=
# 查詢單筆資料
db.rawdata.findOne({"name": "wilson"})
# 查詢成功的話回傳第一筆符合項目,否則回傳 null
```

* 查詢多筆資料
```mongodb=
# 查詢多筆資料
db.rawdata.find({"name": "wilson"})
# 查詢成功的話會回傳所有符合的項目,否則回傳 null
```

### 進階查詢指令
這邊整理了一些常用的進階查詢 Query 範例,參考自[ MongoDB 官方文檔](https://www.mongodb.com/docs/manual/reference/operator/query-comparison/),介紹各類型運算子的定義及用法,有興趣的朋友可以點進去閱讀一下
| Name | Description | Note |
| ---- | ------------------------------------------------------------------- | --- |
| $eq | Matches values that are equal to a specified value. | 等於 |
| $gt | Matches values that are greater than a specified value. | 大於 |
| $gte | Matches values that are greater than or equal to a specified value. | 大於等於 |
| $in | Matches any of the values specified in an array. |符合其中一個 |
| $lt | Matches values that are less than a specified value. |小於 |
| $lte | Matches values that are less than or equal to a specified value. | 小於等於 |
| $ne | Matches all values that are not equal to a specified value. |不等於 |
| $nin | Matches none of the values specified in an array. |不符合其中一個 |
* 查詢 name 符合 "wil" 字段的所有項目(正則表達式)
`db.rawdata.find({name: { $regex: /wil/}})`

* 查詢 age 小於 30 的所有項目
`db.rawdata.find({age: { $lt: 30}})`

* 查詢 age 小於 30 的所有項目總數
`db.rawdata.find({age: { $lt: 30}}).count()`

> **Tips**
> `$eq`, `$in`這兩個蠻容易搞混,這邊我是這樣理解,他倆比較像是“且”跟“或”的關係
>
> 1. 這邊使用`$eq`篩選 name 符合 wilson 且符合 tony 的documents,故沒有回傳符合的結果
> 
>
> 2. 而這邊使用`$in`篩選 name 符合 wilson 或符合 tony 的 documents,因此回傳了兩筆結果
> 
### 修改指令
TODO
### 刪除指令
同樣也能運用上述的運算子來增加條件
* 刪除單筆資料(將 age 是 28 的刪除)
`db.rawdata.deleteOne({age: "28"})`

* 刪除多筆資料(正則表達式,將 name 是 m 開頭的刪除)
`db.rawdata.deleteMany({name: { $regex: /^m/}})`

## 參考文章
[Mongodb 教學:從安裝到啟動基本操作教學](https://tw.alphacamp.co/blog/mongodb-intro)
[從入門到精通 MongoDB 鐵人賽](https://ithelp.ithome.com.tw/users/20130448/ironman/3618)