← 返回蜂巢洞察

我们还需要数据标准化吗?

技术世界中的许多不同角色都会将数据标准化作为许多项目的常规部分。开发人员、数据库 … Read More

Socrates

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

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

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

什么是数据标准化?

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

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

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

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

  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

现在让我们测试一下我们的更改是否解决了问题并遵循第二范式。对于我们的收据表,transaction_id 成为唯一的主键。交易日期基于 transaction_id 是唯一的,transaction_time 也是如此;即,一笔交易 ID 只能有一个日期和时间。

订单不能在店内或店外下单,因此是否在店内购买的价值取决于 transaction_id。由于客户无法拆分收据,因此交易也会告诉我们一个唯一的客户。最后,如果有人给我们一个交易 ID,我们就可以识别附加到它的单个客户忠诚度号码。

接下来是收据行项目表。行项目取决于与其关联的交易(收据),因此我们在行项目表中保留了交易 ID。 transaction_idline_item_id 的组合成为订单项表上的复合键。 Product_idproduct 是根据交易和订单项共同确定的。单个交易 ID 不会告诉我们哪种产品(如果收据包含购买的多种产品),并且单个行项目 ID 不会告诉我们正在引用哪个购买(不同的收据可以订购相同的产品)。这意味着 product_idproduct 值取决于整个密钥。

我们还可以将 transaction_idline_item_id 中的数量关联起来。收据或行项目 ID 中的数量可能相同,但是两个键的组合为我们提供了单个数量值。如果没有交易 ID 和订单项 ID 字段,我们也无法唯一地标识 unit_pricepromo_item_yn 列值。

虽然我们已经满足了第二范式,但是一些数据异常仍然存在。如果我们尝试创建新的购买产品或新客户,我们无法在当前的表中创建它们,因为我们可能还没有与它们绑定的收据。如果我们需要更新产品或客户(由于拼写错误或名称更改),我们需要使用这些值更新所有行项目行。如果我们想删除产品或客户,除非删除引用它们的收据或行项目,否则我们无法删除。为了解决这些问题,我们可以转向第三范式。

第三范式:除了密钥什么都没有

第三范式确保非键字段只依赖于键。换句话说,它们不依赖于其他非关键字段,从而导致传递依赖。让我们再次回顾一下我们的 2NF 数据:

收据

<表格样式=“最大宽度: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

在我们的收据表上,我们需要检查非关键字段(除了 transaction_id 之外的所有字段)以查看这些值是否依赖于其他非关键字段。交易日期、时间和店内的值不会根据彼此或关联的客户或忠诚度号码而变化,因此它们只依赖于密钥。

但是客户信息呢?如果客户发生变化,忠诚度编号的值可能会发生变化。例如,如果我们需要删除或更新进行购买的客户,则忠诚度编号也需要随之删除或更新。因此,忠诚度数字取决于客户,这是一个非关键字段。这意味着我们的收据表不符合第三范式。

我们的收据行项目表怎么样?数量、单价和促销商品价值不会因彼此的价值或产品信息而变化,因为这三个字段表示购买时商品的价值。但是,产品依赖于 product_id,因为该值会根据引用的产品 ID 而变化。所以这个表还需要一些更新以符合第三范式。

同样,解决这些问题的最佳方法是将相关列拉到单独的表中,并留下引用 ID(外键)以将原始表与新表链接起来。消除插入、更新、删除时的数据异常,减少数据冗余,提高存储和查询效率。

收据

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

transaction_id

交易日期

transaction_time

instore_yn

customer_id

150

2019-04-01

12:24:53

Y

604

156

2019-04-01

12:30:00

N

32

165

2019-04-01

16:44:46

Y

127

199

2019-04-01

14:24:55

Y

112

收据行项目

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

transaction_id

line_item_id

product_id

数量

单价

promo_item_yn

150

1

28

1

2.00

N

156

1

34

1

2.45

N

156

2

77

1

3.00

N

165

1

54

2

2.50

N

199

1

41

2

4.25

N

199

2

79

1

3.75

N

产品

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

product_id

产品

28

哥伦比亚中度烘焙咖啡

34

牙买加咖啡河 Sm

77

燕麦烤饼

54

早晨日出柴 Rg

41

卡布奇诺 Lg

79

巨型美味烤饼

客户

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

customer_id

客户

loyalty_num

604

卡米尔·泰勒

102-192-8157

32

格里菲斯·林赛

769-005-9211

127

斯图尔特·努涅斯

796-362-1661

112

阿利斯泰尔·拉米雷斯

253-876-9471

关系数据库外部的数据规范化

那么这个数据规范化过程在其他数据库之外是否有意义?文档、柱状、键值和/或图形数据库是否需要它?

从我的角度来看,无论您使用什么数据库,数据规范化的目标 – 减少冗余、提高数据完整性和提高查询性能 – 仍然非常有价值。然而,关系数据规范化中范式的过程和规则可能与其他数据模型并不一一对应。让我们看一些使用我们的记忆键(“键、整个键、只有键”)来存储三个主要类别的数据库的示例:关系型数据库、文档型数据库和图形型数据库。

关系数据库的目标是通过在 SQL 查询中连接表来优化将数据组装成各种有意义的集合。我们已经从这个角度逐步完成了规范化过程,因此冗余、效率和数据完整性的好处有望从我们之前的讨论中清楚地看出。

文档数据库中,模型经过优化,可将相关信息分组到单个文档中,以便查找单个客户时检索所有收据和任何其他详细信息。此声明已经与我们的数据冗余目标相冲突,因为我们可能会重复产品信息或允许产品信息不一致,以便与客户存储该数据。将被查询的文档的主键对于避免多次查找仍然有意义,但是额外的规范化步骤可能会或可能不会与数据库模型本身的目标相冲突。

图数据库平衡关系提供的数据完整性和文档提供的预烘焙关系数据,以创建针对组装数据关系而优化的独特模型,而无需创建更多数据冗余。通过主键的唯一实体对于提高查询和存储效率仍然很重要,但连接存储为单独的实体,自然地将相关数据整理到单独的实体中,而无需分析每个字段的部分或非键依赖关系。这里存在标准化,但感觉更有机,更少流程驱动。

总结

总之,我们介绍了与传统关系数据库世界相关的数据规范化过程。我们讨论了三种范式的每个步骤,并将每个步骤应用于咖啡店收据数据集。

最后,我们研究了数据规范化是否像其他类型的数据库(文档和图形)一样,以及基于数据库模型的结构哪些形式有意义。

相关文章