将 CSV 列拆分为单独的表(强制执行 1NF)是不必要的复杂化吗?

Asc*_*ant 5 normalization database-design best-practices denormalization

我非常仔细地问过表设计师为什么他选择在多个表中创建 CSV 列。

设计师的回答是,将 CSV 列拆分为单独的表是“不必要的复杂”。

在隐式连接中使用一个 CSV 列来计算 CSV 列中众多值中的一个值已被使用的次数。

where绑定两个表的原因使用了一个like % | table_b.csv_column | %操作。

我阅读并了解到,如果有充分的理由,并不总是需要规范化。
但是,我不知道什么是不正常化的充分理由。

这是(避免“复杂化”)不强制执行第一范式(1NF)的一个很好的理由吗?

桌子: TB_DISASTER_CAUSE_CATE

CATEGORY_ID   CATEGORY_N CATEGORY_L L
------------- ---------- ---------- -
DC006001002   ****                3 Y
DC006001003   ****                3 Y
DC006002001   ****                3 Y
DC007001001   ****                3 Y
DC007002001   ****                3 Y
DC007002002   ****                3 Y
DC007003001   ****                3 Y
DC007003002   ****                3 Y
DC007003003   ****                3 Y
Run Code Online (Sandbox Code Playgroud)

桌子: TB_DISASTER_HISTORY

SEQ DISASTER_TYPE DISASTER_CAUSE                                             
----- ------------- ------------------------------------------------------------
   32 DT001003002   DC001001004|DC002001002|DC003001001|DC007002001             
   33 DT002003007   DC001001004|DC007002001                                     
   34 DT002003009   DC001001003|DC003001002|DC007002001                         
   16 DT002003001   DC001001004|DC002001002|DC005001003|DC007002001             
   17 DT002003001   DC001001004|DC002001002|DC005001003|DC006001003|DC007002001 
   18 DT002003007   DC001001003|DC001002002|DC002001001|DC002002002|DC004001001|
                    DC007002001                                                 

   19 DT002003007   DC001001003|DC001001004|DC003001002|DC007002001             
   20 DT002003007   DC001001003|DC001001004|DC007002001                         
   21 DT002003007   DC001001003|DC003004001|DC007002001                         

  SEQ DISASTER_TYPE DISASTER_CAUSE                                             
----- ------------- ------------------------------------------------------------
   22 DT002003007   DC001001003|DC001001004|DC002001002|DC007002001  
Run Code Online (Sandbox Code Playgroud)

询问

select 
          x.category_id as category_id
          ,x.cnt as ccnt
          ,y.category_nm as category_nm
        from(

        select category_id , count(*) cnt from 
            TB_DISASTER_CAUSE_CATE  a , 
            tb_disaster_history b 

           where last_yn  = 'Y'
             and b.disaster_cause like '%'||a.category_id||'%'
             and b.disaster_type=#disaster_type#
         group by category_id  ) x

         inner join (
         select * from tb_disaster_cause_cate
         where last_yn='Y') y
         on x.category_id = y.category_id;
Run Code Online (Sandbox Code Playgroud)

Tod*_*ett 7

这个问题的答案与这个问题的答案相同。两者实际上都是关于第一范式(1NF)。链接的问题与由通过破折号“-”连接的不同数据元素组成的关键列的理由有关。这个问题与非键列的理由有关,该列由通过管道“|”满足的相同数据元素的不同组成。为了答案,我们必须先评估,如果这样的设计违反了意向1NF的,和第二,如果是的话,何时会是有理由作出这样违反1NF的反正。

背景

首先,快速回顾一下 1NF 的构成。一个表是一个关系(R-Table)表,因此如果在其设计中遵循一个规则来确保:

  • 不同的、无序的行
  • 唯一命名的无序列
  • 每列包含来自相应域的单个值

注意要求 3 意味着任何列都不能有缺失值(或 NULL 标记)。让我们暂时假设满足要求 1 和 2,以及要求 3 的“无缺失值”部分。我说“假设”是因为我们真的无法在不查看表中的所有行以验证没有缺失值、没有重复行以及在列或行的顺序中没有编码的隐式信息的情况下评估合规性。现在,如果模式包含完整性约束,这些约束实现了概念模型中的业务规则以保证没有重复行和缺失值的,我们将不必检查所有数据来验证是否符合这些要求,因为我们知道 DBMS 将强制执行与这些规则的一致性每个案例。然而,我们仍然需要检查所有数据,以确保行或列排序没有被用于隐式编码附加信息。

鉴于刚刚提出的假设,我们要确定将多个灾难原因代码放置到由管道分隔的单个列中是否违反了单值列的要求 3。在我对前一个问题的回答中,关于具有多个数据元素的键列由破折号分隔,我说这样的设计确实违反了该要求,并且在任何情况下都没有理由。我的答案是基于对Fabian PascalChris Date对 1NF 的最新和最明确的思考的研究(我将在答案末尾提供参考)。Fabian 本人通过更正我的回答为回答这个问题做出了贡献。他说我的断言太严格了,而且:

如果组合仅用于视觉目的,并且可以保证DBMS 始终将其作为单个值进行访问(这里强调的是我的)。问题是这种保证是不稳定的。

Fabian 的更正击中了单值列要求的微妙之处。如果该列的域是关系值域(RVD),并且该列中因此包含单个表,则不会违反 1NF。在我们为现在能够证明您的“高级”开发人员创建具有多个数据元素的列的实践而感到高兴之前,这些列由破折号或管道或其他任何东西分隔,我想首先指出,没有 SQL DBMS 实际上支持 RVD,从而使操作上列作为单一值困难。其次,Date 和 Pascal 都指出,虽然 RVD 不违反 1NF,但它确实引入了额外的复杂性和不对称性,而没有(除了少数极端情况)任何额外的好处。因此,虽然它可能是允许的,但它可能不是一个好主意。有关这方面的详细信息,请参阅参考资料。

评估设计

了解 1NF 的背景后,让我们深入了解这种情况。有问题的列,灾难原因,我要提交的不是单个值列,正是因为您给出了一个示例查询,其中访问了较大字符串中的各个值。因此该表未标准化。鉴于此,仅仅因为规范化设计需要两个表并因此被认为“不必要地复杂” ,以使其不是R 表的方式设计该表是否仍然合理?我会说绝对不会有两个原因。首先,爱因斯坦说事情应该尽可能简单,但不能更简单。这种未规范化的设计是“太简单”的一个例子,因为如果表不是 R 表,则无法获得 R 表的任何好处。参考文献详细介绍了这一点,但重点是对每个数据元素的保证逻辑访问丢失了。一个更微妙的问题是,保持灾难原因的完整性要困难得多。如果灾难原因是一个子表,键约束保证每个灾难原因代码只能分配一次。作为灾难​​历史表中的多值列,应用程序很容易意外地将相同的原因代码连接两次。甚至考虑如何编写在多值列上实现此约束所必需的过程触发器,这让我很头疼。最后,请注意我只提到了逻辑设计。我相信在各种 SQL DBMS 的物理实现方面有很多专家,他们会对将要运行的查询的结果优化感到畏缩,就像提供的那样,以访问这些数据。

最后,我认为即使是带有两个表的规范化设计“不必要地复杂”的理由也是错误的。相反,单表中的多值列在我看来“不必要地复杂”。您提供的查询试图计算给定灾难原因导致灾难的次数,是如果设计只有一个灾难表和一个子灾难原因表,那么编写起来会更复杂。嵌套将是不必要的,并且连接对于其目的来说将是简单明了的。第一次查看模式的人可以很容易地理解,甚至没有看到数据,我们有关于灾难的信息,每个灾难可能有很多原因。使用多值列,这个简单的结构变得模糊,第一次用户必须深入研究数据值才能发现实际上灾难可能有很多原因。

参考

Fabian Pascal 的实用数据库基础系列。论文 #5 解决了 1NF 和 RVD。如果您购买所有论文(您确实应该购买,因为它们是解决所有基础知识的补充集),可以获得另外两篇专门针对 1NF 的论文 - CJ Date 的“第一范式的真正含义”和“第一范式是什么”形式意味着不是”法比安帕斯卡。接下来,CJ Date 撰写了大量关于关系理论的文章。关于规范化的最新参考资料是Database Design and Relational Theory: Normal Forms and All That Jazz。最后,Fabian Pascal最近发表了关于 1NF的博客,这些博客非常适合阅读。