【问题标题】:Is it ever okay to violate first normal form?违反第一范式可以吗?
【发布时间】:2012-09-22 16:17:13
【问题描述】:

我有一个 Access 数据库,需要在一行上显示具有多个一对多关系的数据(例如,它将项目列为“a、b、e、f”,并且我将有多个列像那样)。我知道以这种方式存储数据也是一个坏主意,但考虑到我允许用户过滤其中的几个列,我想不出比违反第一个范式更好的处理数据的方法.

举个例子:假设我有几篇期刊文章,每篇文章都可能报道多种动物和多种蔬菜。用户可以过滤源名称,或者他们可以过滤一种或多种动物和一种或多种蔬菜。输出应该看起来像

Source name....animals...............vegetables
Source 1.......dogs, cats, birds.....carrots, tomatoes
Source 2.......leopards, birds.......tomatoes, zucchini, apples
Source 3.......cats, goldfish........carrots, cucumbers

通常您会有一个单独的表格,其中包含源名称 + 动物:

Source name......animal
Source 1.........dog
Source 1.........cats
Source 1.........birds
Source 2.........leopards

还有一张类似的蔬菜表。但考虑到数据需要如何呈现给用户(逗号分隔的列表),以及用户如何过滤数据(他可能会过滤以仅查看包括狗和猫的源,以及带有胡萝卜和西红柿的源),我认为将数据存储为动物和蔬菜的逗号分隔列表是有意义的。使用逗号分隔的列表,当用户选择多种蔬菜和多种动物时,我可以说

WHERE (Vegetables like "*carrots*" and Vegetables like "*tomatoes*") AND (Animals like *dogs*" and Animals like "*cats*")

如果不使用大量 VBA 和多个查询,我想不出一种在 Access 中执行相同类型查询的有效方法。

【问题讨论】:

  • 通常至少有一个很好的理由来打破任何规则。我觉得 Access 有时很难使用,有时它在最初和考虑长期没有完全关系数据库模式时要容易得多。不过,这真的取决于。我曾经不得不对 Access DB 进行反规范化,因为由于 3NF 设计,一些报告变得非常复杂。但是,我认为这可以在没有任何 VBA 和规范化设计的情况下完成。
  • 当然希望您不必在数据库中跟踪草原土拨鼠。

标签: sql database ms-access database-normalization


【解决方案1】:

您始终可以构建一个场景,在其中违反任何规则都是有意义的,因此您标题中问题的答案是肯定的。

但是,这不是其中一种情况。您提出的搜索和表示问题对于大多数一对多关系(或者,至少对于 many 一对多关系)来说是常见的,如果这是违反第一范式的原因那么你不会看到很多规范化的数据库。

正确构建数据库,您不必担心逗号、搜索词相互嵌入以及由于缺少索引而导致搜索缓慢。编写一段可重用的代码来为您执行逗号分隔的汇总,这样您就不会不断重复发明轮子。

【讨论】:

  • 我建议以正确的规范化格式存储并将显示转换为任何合适的格式,包括逗号分隔的格式。显示问题可以通过一段通用代码来解决,该代码可以在任何需要该功能的地方重复使用(我在 Access VBA 和使用 JET 作为引擎的其他客户端语言中都这样做了)。
  • 感谢您的澄清。如果您将这些详细信息包含在其中,我认为您的答案会更有用。我想您会建议不要在逗号分隔字段等中进行编辑。我认为如果您能稍微说明一下查询的外观也会很好。您会在 Where 子句中使用子查询,还是使用适当的 Where 子句/条件对同一个表使用多个连接?还是其他更好的主意?
  • 嘿 HK1,以防万一这可能对您有所帮助,我听取了大家的建议并正确归一化(正如我的直觉告诉我的那样)。稍后连接行真的并没有很麻烦。我对所有数据进行了适当的规范化,然后修改了 Allen Browne 的函数 ConcatRelated (allenbrowne.com/func-concat.html) 以对我需要显示的数据进行分组。
【解决方案2】:

我仍然会正确地对此进行规范化 - 然后担心演示文稿。

在 Oracle 中 - 这将通过用户定义的聚合函数来完成。

【讨论】:

    【解决方案3】:

    为什么不构建一个连接表,这样您就可以维持与字段引用完整性的 1:1 关系。否则,您将不得不解析出 1:many 字段值,然后才能获得引用关联,因此一切都可以神奇地工作(呵呵;))

    当我发现自己需要违反第一范式时,9:10 的答案是创建一个 Join Table 并构造一些方法来产生预期的效果。

    编辑:2012-10-09 9:06AM

    • 此设计是为了响应在未知数量的列/字段中显示的未知数量的信息。虽然我的方向是数值,但您可以简单地开发一种 vba 方法来连接信息字段以生成单个数据字段。

    表1

    gid (Number) <- Table2.id
    cid (Number) <- Table3.id
    price (Number)
    
    • gid 很多
    • cid 很多

    crt_CategoryPG

    TRANSFORM Sum(Table1.price) AS SumOfprice
    SELECT View1.tid, Table1.cid, View1.category
    FROM Table2 INNER JOIN (View1 INNER JOIN Table1 ON View1.cid = Table1.cid) ON Table2.gid = Table1.gid
    WHERE (((Table2.active)=True) AND ((View1.active)=True))
    GROUP BY View1.tid, Table1.cid, View1.category, Table2.active, View1.active
    ORDER BY View1.tid, Table1.cid
    PIVOT [Table2].[gid] & " - " & [Table2].[nm];
    

    刷新 CT 表

    Public Function RefreshCategoryPricing(Optional sql As String = "")
        If HasValue(lstType) Then
    Application.Echo False      'Turn off Screen Updating
    
            Dim cttbl As String: cttbl = CreateCTTable("crt_CategoryPG") 'Create Table to store the Cross-Tab information
            If IsNullOrEmpty(sql) Then
                sql = SQLSelect(cttbl)
            End If
    
            Dim flds As DAO.Recordset: Set flds = CurrentDb.OpenRecordset(sql)
            Dim fldwd As String, fldhd As String      'Store the Field Width pattern and Field Header Row
            fldhd = "-1;-1;Category"
            fldwd = "0"";0"";2.5"""   'Handles `tid`, `cid`, and `category` columns in the ListBox
            'Assign the number of columns based on the number of fields in CTtable
            lstCategoryPG.ColumnCount = flds.Fields.Count
    
            Dim fld As Long
            For fld = 3 To (flds.Fields.Count - 1)
                fldwd = fldwd & ";.75"""
                fldhd = fldhd & ";" & flds.Fields(fld).Name
            Next
    
            GC flds
    
            lstCategoryPG.ColumnHeads = True
            lstCategoryPG.ColumnWidths = fldwd
    
            sql = SQLSelect(cttbl, , ("tid = " & lstType.Value))
    
            lstCategoryPG.Enabled = True
            RefreshControl CurrentDb, lstCategoryPG, sql, , False
            lstCategoryPG.AddItem fldhd, 0
    
    Application.Echo True       'Turn on Screen Updating
        End If
    End Function
    

    创建交叉表

    '@ct - String value, Source Cross-Tab to base Table design off of
    Public Function CreateCTTable(ct As String) As String
        Dim tbl As String: tbl = "tbl_" & ct
        Dim sql As String
    
        If TableExists(tbl) Then
        'Table exists and needs to be dropped
            sql = SQLDrop(tbl)
            CurrentDb.Execute sql
        End If
    
        'Create Table
        sql = SQLSelect(ct, "* INTO " & tbl)
        CurrentDb.Execute sql
    
        CreateCTTable = tbl
    End Function
    

    【讨论】:

    • 使用您的方法,您将如何在单行中显示此数据,例如查询或数据表,而不为“连接”中的每一行列出“源表”中的每一行一次表”?
    • 好吧,根据他们的情况,您可以构建一个表来包含信息所需的字段,并在完成规范化后临时创建表。我已经为当前的客户完成了它,它是一个 PITA,但可行,如果做得正确,您可以构建一个可更新到源表的临时交叉表。
    • 我想我知道你的意思,但我以前从未听说过“临时交叉表”。
    • @HK1 好吧,由于项目的性质,我建造了一个。在我构建表格内容之前删除它,然后在我获得交叉表字段名称后重建表格。考虑到 VBA,它很笨重,但是当我构建数据仓库以报告到 SharePoint 衍生产品时,我在 MS-SQL 中做了很多。主要是因为交叉表不是不可格式化的(很容易),所以我必须将所有参考完整性值加载到不可见的字段中。
    • 我会发布我在我的一个项目中使用的方法。它是 VBA 和 DB-Queries 的混合体,主要在 VBA 中运行。
    猜你喜欢
    • 2011-06-02
    • 2015-06-14
    • 2011-05-21
    • 2012-08-17
    • 2011-11-29
    • 1970-01-01
    • 2020-08-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多