# DBMS 中的范式：数据库中的 1NF，2NF，3NF 和 BCNF

> 原文： [https://beginnersbook.com/2015/05/normalization-in-dbms/](https://beginnersbook.com/2015/05/normalization-in-dbms/)

**范式**是在数据库中组织数据以避免数据冗余，插入异常，更新异常和数据的过程。删除异常。让我们首先讨论异常，然后我们将用例子讨论正常形式。

## DBMS 中的异常

数据库未范式时会发生三种类型的异常。这些是 - 插入，更新和删除异常。我们举一个例子来理解这一点。

**示例**：假设制造公司将员工详细信息存储在名为`employee`的表中，该表具有四个属性：`emp_id`用于存储员工的 id，`emp_name`用于存储员工的姓名，`emp_address`用于存储员工的地址，`emp_dept`用于存储部门详细信息员工的工作方式。在某些时候，表看起来像这样：

| `emp_id` | `emp_name` | `emp_address` | `emp_dept` |
| --- | --- | --- | --- |
| 101 | Rick | Delhi | D001 |
| 101 | Rick | Delhi | D002 |
| 123 | Maggie | Agra | D890 |
| 166 | Glenn | Chennai | D900 |
| 166 | Glenn | Chennai | D004 |

上表未范式。我们将看到当表未范式时我们面临的问题。

**更新异常**：在上表中，员工 Rick 有两行，因为他属于公司的两个部门。如果我们想要更新 Rick 的地址，那么我们必须在两行中更新相同的内容，否则数据将变得不一致。如果不知何故，正确的地址在一个部门更新，但在其他部门没有更新，那么根据数据库，Rick 将有两个不同的地址，这是不正确的，会导致数据不一致。

**插入异常**：假设一名新员工加入正在接受培训并且当前未分配到任何部门的公司，那么如果`emp_dept`字段不允许空值，我们将无法将数据插入表中。

**删除异常**：假设，如果公司关闭部门 D890 的某个时间点，那么删除具有`emp_dept`为 D890 的行也会删除员工 Maggie 的信息，因为她只被分配给该部门。

为了克服这些异常，我们需要范式数据。在下一节中，我们将讨论范式。

## 范式

以下是最常用的常规形式：

*   第一范式（1NF）
*   第二范式（2NF）
*   第三范式（3NF）
*   Boyce Codd 范式（BCNF）

## 第一范式（1NF）

根据第一范式的规则，表的属性（列）不能包含多个值。它应该只包含原子值。

**示例**：假设公司想要存储其员工的姓名和联系方式。它创建一个如下所示的表：

| `emp_id` | `emp_name` | `emp_address` | `emp_mobile` |
| --- | --- | --- | --- |
| 101 | Herschel | New Delhi | 8912312390 |
| 102 | Jon | Kanpur | 88121212129900012222 |
| 103 | Ron | Chennai | 7778881212 |
| 104 | Lester | Bangalore | 99900001238123450987 |

两名员工（Jon&amp; Lester）有两个手机号码，因此公司将它们存储在您在上表中看到的相同字段中。

该表是**不在 1NF** 中，因为规则说“表的每个属性必须具有原子（单个）值”，员工的`emp_mobile`值为`Jon & Lester`违反了这条规则。

为了使表符合 1NF，我们应该有这样的数据：

| `emp_id` | `emp_name` | `emp_address` | `emp_mobile` |
| --- | --- | --- | --- |
| 101 | Herschel | New Delhi | 8912312390 |
| 102 | Jon | Kanpur | 8812121212 |
| 102 | Jon | Kanpur | 9900012222 |
| 103 | Ron | Chennai | 7778881212 |
| 104 | Lester | Bangalore | 9990000123 |
| 104 | Lester | Bangalore | 8123450987 |

## 第二范式（2NF）

如果以下两个条件成立，则表示在 2NF 中：

*   表为 1NF（第一范式）
*   没有非主属性取决于表的任何候选键的适当子集。

不属于任何候选键的属性称为非主属性。

**示例**：假设学校想要存储教师的数据和他们教授的科目。他们创建了一个如下所示的表：由于教师可以​​教授多个科目，因此该表可以为同一位教师设置多行。

| `teacher_id` | `subject` | `teacher_age` |
| --- | --- | --- |
| 111 | Maths | 38 |
| 111 | Physics | 38 |
| 222 | Biology | 38 |
| 333 | Physics | 40 |
| 333 | Chemistry | 40 |

**候选键**：`{teacher_id, subject}`

**非主属性**：`teacher_age`

该表在 1 NF 中，因为每个属性都有原子值。但是，它不在 2NF 中，因为非主属性`teacher_age`仅依赖于`teacher_id`，它是候选键的适当子集。这违反了 2NF 的规则，因为规则说“**没有**非主属性依赖于表的任何候选键的正确子集”。

为了使表符合 2NF，我们可以在两个表中打破它：

**`teacher_details`表：**

| `teacher_id` | `teacher_age` |
| --- | --- |
| 111 | 38 |
| 222 | 38 |
| 333 | 40 |

**`teacher_subject `表：**

| `teacher_id` | `subject` |
| --- | --- |
| 111 | Maths |
| 111 | Physics |
| 222 | Biology |
| 333 | Physics |
| 333 | Chemistry |

现在表符合第二范式（2NF）。

## 第三范式（3NF）

如果满足以下条件，则表设计为 3NF：

*   表必须是 2NF
*   [应删除任何超键上非主要属性的传递函数依赖](https://beginnersbook.com/2015/04/transitive-dependency-in-dbms/)。

不属于任何[候选键](https://beginnersbook.com/2015/04/candidate-key-in-dbms/)的属性称为非主属性。

换句话说，3NF 可以这样解释：如果表在 2NF 中，则表在 3NF 中，并且对于每个函数依赖`X -> Y`至少满足下列条件之一：

*   `X`是表的[超键](https://beginnersbook.com/2015/04/super-key-in-dbms/)
*   `Y`是表的主要属性

作为候选键之一的一部分的属性称为主要属性。

**示例**：假设公司想要存储每个员工的完整地址，他们会创建一个名为`employee_details`的表，如下所示：

| `emp_id` | `emp_name` | `emp_zip` | `emp_state` | `emp_city` | `emp_district` |
| --- | --- | --- | --- | --- |
| 1001 | John | 282005 | UP | Agra | Dayal Bagh |
| 1002 | Ajeet | 222008 | TN | Chennai |  M-City |
| 1006 | Lora | 282007 | TN | Chennai | Urrapakkam |
| 1101 | Lilly | 292008 | UK | Pauri | Bhagwan |
| 1201 | Steve | 222999 | MP | Gwalior | Ratan |

**超键**：`{emp_id}`，`{emp_id, emp_name}`，`{emp_id, emp_name, emp_zip}`...等

**候选键**：`{emp_id}`

**非主属性**：除`emp_id`之外的所有属性都是非主，因为它们不是任何候选键的一部分。

在这里，`emp_state`，`emp_city`和`emp_district`依赖于`emp_zip`。并且，`emp_zip`依赖于`emp_id`，这使得非主属性（`emp_state`，`emp_city`和`emp_district`）可传递地依赖于超键（`emp_id`）。这违反了 3NF 的规则。

要使此表符合 3NF，我们必须将表拆分为两个表以删除传递依赖项：

**员工表：**

| `emp_id` | `emp_name` | `emp_zip` |
| --- | --- | --- |
| 1001 | John | 282005 |
| 1002 | Ajeet | 222008 |
| 1006 | Lora | 282007 |
| 1101 | Lilly | 292008 |
| 1201 | Steve | 222999 |

**`employee_zip`表：**

| `emp_zip` | `emp_state` | `emp_city` | `emp_district` |
| --- | --- | --- | --- |
| 282005 | UP | Agra | Dayal Bagh |
| 222008 | TN | Chennai | M-City |
| 282007 | TN | Chennai | Urrapakkam |
| 292008 | UK | Pauri | Bhagwan |
| 222999 | MP | Gwalior | Ratan |

## Boyce Codd 范式（BCNF）

它是 3NF 的高级版本，这也是它被称为 3.5NF 的原因。 BCNF 比 3NF 更严格。如果表是 3NF，则表符合 BCNF，对于每个[函数依赖](https://beginnersbook.com/2015/04/functional-dependency-in-dbms/)`X -> Y`，`X`应该是表的超键。

**示例**：假设有一家公司，员工在**多个部门工作**。他们存储这样的数据：

| `emp_id` | `emp_nationality` | `emp_dept` | `dept_type` | `dept_no_of_emp` |
| --- | --- | --- | --- | --- |
| 1001 | Austrian | Production and planning | D001 | 200 |
| 1001 | Austrian | stores | D001 | 250 |
| 1002 | American | design and technical support | D134 | 100 |
| 1002 | American | Purchasing department | D134 | 600 |

**上表**中的函数依赖：

+   `emp_id -> emp_nationality`
+   `emp_dept -> {dept_type，dept_no_of_emp}`

**候选键**：`{emp_id, emp_dept}`

该表不在 BCNF 中，因为`emp_id`和`emp_dept`都不是键。

为了使表符合 BCNF，我们可以在三个表中打破这样的表：

**`emp_nationality`表：**

| `emp_id` | `emp_nationality` |
| --- | --- |
| 1001 | Austrian |
| 1002 | American |

**`emp_dept`表：**

| `emp_dept` | `dept_type` | `dept_no_of_emp` |
| --- | --- | --- |
| Production and planning | D001 | 200 |
| stores | D001 | 250 |
| design and technical support | D134 | 100 |
| Purchasing department | D134 | 600 |

**`emp_dept_mapping`表：**

| `emp_id` | `emp_dept` |
| --- | --- |
| 1001 | Production and planning |
| 1001 | stores |
| 1002 | design and technical support |
| 1002 | Purchasing department |

**函数依赖**：

+   `emp_id -> emp_nationality`
+   `emp_dept -> {dept_type, dept_no_of_emp}`

**候选键**：

+   第一个表：`emp_id`
+   第二个表：`emp_dept`
+   第三个表：`{emp_id, emp_dept}`

现在这是在 BCNF 中，因为在函数依赖中左侧部分是关键。