技术世界中的许多不同角色都会将数据标准化作为许多项目的常规部分。开发人员、数据库管理员、领域建模者、业务利益相关者以及更多人在规范化过程中取得了进展,就像他们呼吸一样。然而,看起来如此不可或缺的东西会变得过时吗?

随着数据库环境变得更加多样化,硬件变得更加强大,我们可能想知道是否还需要数据规范化的实践。我们是否应该担心优化数据存储和查询以便返回最少的数据量?或者,如果我们应该这样做,某些数据结构是否比其他数据结构更能解决这些问题?

在本文中,我们将回顾数据标准化的过程,并评估何时需要此过程,或者它是否仍然是数字化存储和检索数据的必要部分。

什么是数据标准化?

数据规范化正在优化关系数据库中的数据结构,以确保数据完整性和查询效率。它通过将数据经过一系列步骤来标准化结构(范式)来减少冗余并提高准确性。从本质上讲,数据规范化有助于避免插入、更新和删除数据异常。这些异常在创建新数据、更新现有数据或删除数据时发生,并对保持数据值同步(完整性)造成挑战。当我们逐步完成规范化过程时,我们将详细讨论这一点。

这些步骤需要验证键(相关数据的链接)、将不相关的实体与其他表分开,以及将行和列作为统一的数据对象进行检查。虽然范式步骤的完整列表相当严格,但我们将重点关注商业实践中最常用的范式:第一范式、第二范式和第三范式。其他范式主要用于学术和统计学。范式步骤必须按顺序完成,在前一个范式完成之前我们不能移动到下一个范式。

我们如何进行数据标准化?

由于我们有三种范式来获取数据,因此我们将分为三个步骤来完成此过程。它们如下:

  1. 第一范式 (1NF)
  2. 第二范式 (2NF)
  3. 第三范式 (3NF)

一位大学数据库教授教我的班级记住三种范式:“关键,整个关键,除了关键之外什么都没有”(就像在法庭上宣誓真相一样)。我不得不刷新本文的一些正常形式的详细信息,但这个基本短语一直困扰着我。希望它也能帮助您记住它们。

我最近发现了一个咖啡店数据集,它似乎很适合我们用作标准化数据集的示例。通过对此处的示例进行一些调整,我们可以逐步完成该过程。

非规范化数据

<表格样式=“最大宽度:100%;宽度:自动;表格布局:固定;显示:表格;”宽度=“自动”>
<正文>

交易日期

transaction_time

instore_yn

客户

loyalty_num

line_item_id

产品

数量

单价

promo_item_yn

2019-04-01

12:24:53

Y

卡米尔·泰勒

102-192-8157

1

哥伦比亚中度烘焙咖啡

1

2.00

N

2019-04-01

12:30:00

N

格里菲斯·林赛

769-005-9211

1,2

牙买加咖啡河 Sm,燕麦烤饼

1,1

2.45,3.00

N,N

2019-04-01

16:44:46

Y

斯图尔特·努涅斯

796-362-1661

1

早晨日出柴 Rg

2

2.50

N

2019-04-01

14:24:55

Y

阿利斯泰尔·拉米雷斯

253-876-9471

1,2

卡布奇诺 Lg、特大咸味烤饼

2,1

4.25,3.75

N,N

该数据包含公司的销售收据,最初发布在 Kaggle Coffee Shop 示例数据存储库,尽管我还创建了一个 今天帖子的 GitHub 存储库。上面显示的数据显示了向客户订购的产品的销售额。

为什么这个数据有问题?前面,我们提到规范化来解决插入、更新和删除异常。如果我们尝试向此数据插入新行,则可能会创建重复行,或者更糟糕的是,必须收集有关客户、产品和收货日期/时间的所有信息才能创建它。如果我们需要更新或删除收据上购买的产品,我们需要对每个产品列中的列表进行排序以搜索值。那么让我们看看如何通过标准化这些数据来提高冗余和完整性。

第一范式:密钥

对于“键、整个键,只有键”的第一步,表应该有一个主键(单个列或一组列),以确保行是唯一的。每个行中的列也应仅包含单个值;即没有嵌套表。

我们的示例数据集需要一些工作才能达到 1NF。虽然我们可以通过日期/时间或日期/时间/客户的组合来获取唯一行,但引用具有某种生成的唯一值的行通常要简单得多。我们通过在收据表中添加一个 transaction_id 字段来实现这一点。

还有几行订购了多个项目(transaction_id 156 和 199),因此有几列的行项目具有多个值。我们可以通过将具有多个值的行分成单独的行来纠正此问题。

1NF 数据

<表格样式=“最大宽度:100%;宽度:自动;表格布局:固定;显示:表格;”宽度=“自动”>
<正文>

transaction_id

交易日期

transaction_time

instore_yn

客户

loyalty_num

line_item_id

产品

数量

单价

promo_item_yn

150

2019-04-01

12:24:53

Y

卡米尔·泰勒

102-192-8157

1

哥伦比亚中度烘焙咖啡

1

2.00

N

156

2019-04-01

12:30:00

N

格里菲斯·林赛

769-005-9211

1

牙买加咖啡河 Sm

1

2.45

N

156

2019-04-01

12:30:00

N

格里菲斯·林赛

769-005-9211

2

燕麦烤饼

1

3.00

N

165

2019-04-01

16:44:46

Y

斯图尔特·努涅斯

796-362-1661

1

早晨日出柴 Rg

2

2.50

N

199

2019-04-01

14:24:55

Y

阿利斯泰尔·拉米雷斯

253-876-9471

1

卡布奇诺 Lg

2

4.25

N

199

2019-04-01

14:24:55

Y

阿利斯泰尔·拉米雷斯

253-876-9471

2

巨型美味烤饼

1

3.75

N

使用此数据,复合(多列)键通过 transaction_idline_item_id 的组合唯一标识一行,作为单个收据不能包含多个订单项 #1。如果我们将表简化为这些主键值,则可以看到以下数据。

<表格样式=“最大宽度:100%;宽度:自动;表格布局:固定;显示:表格;”宽度=“自动”>
<正文>

transaction_id

line_item_id

150

1

156

1

156

5

165

1

199

1

199

5

这两个值的每个组合都是唯一的。我们已将第一范式应用于数据,但仍然存在一些潜在的数据异常。如果我们想要添加新收据,我们可能需要创建多行(取决于它包含多少行项目),并在每行上重复交易 ID、日期、时间和其他信息。更新和删除会导致类似的问题,因为我们需要确保所有受影响的行数据保持一致。这就是第二范式发挥作用的地方。

第二范式:整个密钥

第二范式确保每个非键列完全依赖于整个键。对于具有多个列作为主键的表(例如我们的咖啡收据表),这更值得关注。这是我们的数据的第一范式:

<表格样式=“最大宽度:100%;宽度:自动;表格布局:固定;显示:表格;”宽度=“自动”>
<正文>

transaction_id

交易日期

transaction_time

instore_yn

客户

loyalty_num

line_item_id

产品

数量

单价

promo_item_yn

150

2019-04-01

12:24:53

Y

卡米尔·泰勒

102-192-8157

1

哥伦比亚中度烘焙咖啡

1

2.00

N

156

2019-04-01

12:30:00

N

格里菲斯·林赛

769-005-9211

1

牙买加咖啡河 Sm

1

2.45

N

156

2019-04-01

12:30:00

N

格里菲斯·林赛

769-005-9211

2

燕麦烤饼

1

3.00

N

165

2019-04-01

16:44:46

Y

斯图尔特·努涅斯

796-362-1661

1

早晨日出柴 Rg

2

2.50

N

199

2019-04-01

14:24:55

Y

阿利斯泰尔·拉米雷斯

253-876-9471

1

卡布奇诺 Lg

2

4.25

N

199

2019-04-01

14:24:55

Y

阿利斯泰尔·拉米雷斯

253-876-9471

2

巨型美味烤饼

1

3.75

N

我们需要评估每个非关键字段,看看是否有任何部分依赖关系;即,该列仅依赖于键的一部分而不是整个键。由于 transaction_idline_item_id 构成了我们的主键,因此我们从 transaction_date 字段开始。交易日期确实取决于交易ID,因为同一交易ID不能在另一天再次使用。但是,交易日期根本不取决于行项目 ID。订单项可以跨交易、跨天、甚至跨客户重复使用。

好的,我们已经发现该表不遵循第二范式,但是让我们检查另一列。客户栏呢?客户不依赖于交易 ID 和行项目 ID。如果有人给我们一个交易 ID,我们就会知道哪个客户进行了购买,但如果给我们一个行项目 ID,我们就不会知道该收据属于哪个客户。毕竟,多个顾客可能在他们的收据上订购了一件、两件或六件商品。客户链接到交易 ID(假设多个客户无法拆分收据),但客户不依赖于行项目。我们需要修复这些部分依赖关系。

最直接的解决方案是为订单行项目创建一个单独的表,将仅依赖于 transaction_id 的列保留在收据表中。第二范式中更新后的数据如下所示。

收据

<表格样式=“最大宽度:100%;宽度:自动;表格布局:固定;显示:表格;”宽度=“自动”>
<正文>

transaction_id

交易日期

transaction_time

instore_yn

客户

loyalty_num

150

2019-04-01

12:24:53

Y

卡米尔·泰勒

102-192-8157

156

2019-04-01

12:30:00

N

格里菲斯·林赛

769-005-9211

165

2019-04-01

16:44:46

Y

斯图尔特·努涅斯

796-362-1661

199

2019-04-01

14:24:55

Y

阿利斯泰尔·拉米雷斯

253-876-9471

收据行项目

<表格样式=“最大宽度:100%;宽度:自动;表格布局:固定;显示:表格;”宽度=“自动”>
<正文>

transaction_id

line_item_id

product_id

产品

数量

单价

promo_item_yn

150

1

28

哥伦比亚中度烘焙咖啡

1

2.00

N

156

1

34

牙买加咖啡河 Sm

1

2.45

N

156

2

77

燕麦烤饼

1

3.00

N

165

1

54

早晨日出柴 Rg

2

2.50

N

199

1

41

卡布奇诺 Lg

2

4.25

N

199

2

79

巨型美味烤饼

1

3.75

N