# MongoDB-Validation
類似 關係型數據庫(如 MySQL)為了資料完整性會設置約束條件,確保寫入的資料具有一致性以維持資料完整。
MongoDB 則是透過 Schema Validation 來進行資料結構的驗證。
## 建立資料集合時建立 Validation
通過為集合指定 `validator` 屬性來指定資料的約束條件,例如:
```js
db.createCollection("students", {
validator: {
$jsonSchema: { // 驗證器類型: JSON Schema
bsonType: "object",
title: "Student驗證", // 條件名稱
required: [ "name" , "age" , "major" ], // 必要欄位
properties: { // 分別指定各欄位驗證規則
name: {
bsonType: "string", // 表示 name 是字串
description: "要有姓名,且須要是字串" // 錯誤提示內容
},
age: {
bsonType: [ "string","int","null" ], // 表示 age 可以是 字串 數字 或者 null
minimum: 18, // 限制數字範圍,但輸入字串時檢查會失效
maximum: 30,
description: "本校入學年齡在 18~30 歲"
},
courses: {
bsonType: "array", // 表示 courses 必須是陣列
description: "修課表必須是陣列",
items: {
bsonType: "string", // 陣列的內容要求是字串
description: "課程名稱請以字串表示"
}
}
}
}
},
validationLevel: "strict", // "strict" 或 "moderate"
validationAction: "error" // "error" 或 "warn"
} )
```
### JSON Schema 驗證
最直觀的驗證器就是通過撰寫 Schema 來描述集合中的每一筆資料(BSON)的格式。
- `required`: 必要欄位,以陣列形式表示
- `properties`: 描述各個屬性的格式
```js
$jsonSchema: {
required: [ "name", "address" ],
properties: {
name: {
bsonType: "string",
},
address: {
bsonType: "object",
required: [ "zipcode" ],
properties: { // JSON 允許巢狀結構,Schema 也可以巢狀描述
"street": { bsonType: "string" },
"zipcode": { bsonType: "string" }
}
}
}
}
```
#### 限定欄位的允許值
使用 enum 枚舉欄位允許值,避免欄位中輸入無效值。
```js
db.createCollection("sales", {
validator: {
$jsonSchema: {
bsonType: "object",
title: "驗證欄位是否為允許值",
properties: {
type: { // 這個欄位要通過 enum 驗證,只允許特定值
enum: [ "中杯", "大杯", "超大杯" ],
description: "不好意思我們沒有賣小杯"
}
}
}
}
} )
```
#### 禁止額外欄位
避免集合中的文檔出現新欄位,可以限制欄位必須為 `properties` 所指定的欄位,可以避免像是因為輸入錯誤而新增欄位。
```js
db.createCollection("users", {
validator: {
"$jsonSchema": {
"required": [ "username", "password" ],
"properties": {
"_id": { "bsonType": "objectId" },
"username": { "bsonType": "string" },
"password": { "bsonType": "int" }
},
"additionalProperties": false // 不允許添加額外欄位
}
}
})
```
### 查詢表達式驗證
`$expr` 可以使用 MongoDB 特有的查詢表達式來表達在進行資料儲存前應該先進行的驗證運算。
```js
db.createCollection( "orders", {
validator: {
$expr: { // 使用表達式進行驗證
$eq: [ // 用查詢表達式描述驗證規則
"$total", // 總價是價格乘以數量
{ $multiply: [ "$price", "$quantity" ] } // $ 符號表示引用文檔中的欄位
]
}
}
}
)
```
#### 多條件驗證
使用 `$and` 表達多條件驗證:
```js
db.createCollection("sales", {
validator: {
"$and": [ // $and 表示需要通過多個條件驗證
{
"$expr": { // 使用表達式進行數據驗證
"$lt": ["$discountedPrice", "$price"] // 要求折扣價需要小於原價
}
},
{
"$jsonSchema": {
required: [ "discountedPrice" , "price" ],
}
}
]
}
}
)
```
### 驗證強度 ValidationLevel
預設的驗證強度,是針對**每一筆**插入與更新都進行檢查 (預設"strict")
但若是添加驗證之前就已經存在舊資料,因為舊資料可能採取不同的規則管理,預設模式就會造成舊資料無法照舊規則更新。
因此選擇 "moderate" 和緩模式,就不會對不符合驗證的舊資料進行驗證,但是已經符合驗證的舊資料則不能修改為不符合驗證的狀態。(很人性化的說)
除非是完全不希望驗證器進行任何阻擋,否則不會設置 `validationAction: "warn"`
## 修改集合的 Validation
```js
db.runCommand( { collMod: "students", // 使用 runCommand 來修改 collMod 指定集合的驗證
validator: {
$jsonSchema: {
// 新的驗證條件內容...
}
}
} )
```
> [!🚨]
> 修改後的規則不朔及既往。只驗證新加入的資料。
## 查看集合的 Validation
查看目前 db 中的 collection 資訊使用 `getCollectionInfos()` 方法:
```js
db.getCollectionInfos( { name: "students" } ) // 可以查看名為 "students" 的 collection 資訊
```
```js
db.getCollectionInfos( { name: "students" } )[0].options.validator // 指定查看其 validator 資訊
```
## 查找符合/不符合 Schema 的資料
將 Schema 存到變數,用來匹配查找資料:
```js
let myschema = {
$jsonSchema: {
required: [ "name", "age" ],
properties: {
name: { bsonType: "string" },
age: { bsonType: "int" }
}
}
}
```
查找匹配:
```js
db.inventory.find(myschema)
```
查找不匹配:
```js
db.inventory.find( { $nor: [ myschema ] } )
```
如此,就可以此條件去進行 `$set` 資料更新,或者 delete 指令的刪除。
## 繞過驗證
有些特殊的狀況可能會需要強制繞過現有的資料驗證模式,比如說,進行資料的災難還原時使用了舊資料...等。
```js
db.runCommand( { // 使用 runCommand 而非 insert
insert: "importantOrders",
documents: [
// 要插入的舊資料的內容
],
bypassDocumentValidation: true // <- 強制繞過驗證的指令
} )
```
## 其他資料完整性約束
### 唯一值(Unique Indexes)
透過 創建索引 的方式來保證欄位的唯一性。
```js
db.users.createIndex({ firstname: 1, lastname: 1 }, { unique: true })
```
### 引用完整性(Reference Integrity)
由於 MongoDB 沒有外鍵約束,因此需要手動維護資料參考的完整性。
例如:刪除用戶時 `find()` 檢查有無該用戶的訂單。