---
# System prepended metadata

title: MongoDB-Validation

---

# 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()` 檢查有無該用戶的訂單。
