是否可以违反第一次正常形式?

时间:2012-10-01 19:23:09

标签: sql database ms-access normalization

我有一个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 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中执行此类查询的有效方法。

3 个答案:

答案 0 :(得分:4)

你总是可以构建一个违反任何规则的场景,所以你标题中的问题答案是肯定的。

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

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

答案 1 :(得分:0)

我仍然可以正常地将其正常化 - 然后担心演示文稿。

在Oracle中

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

答案 2 :(得分:0)

为什么不构建一个连接表,这样就可以维持与字段引用完整性的1:1关系。否则你将需要解析1:many字段值然后得到一个参考关联,所以一切都神奇地工作(呵呵呵;))

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

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

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

表1

gid (Number) <- Table2.id
cid (Number) <- Table3.id
price (Number)
  • gid MANY&lt; - ONE Table2.id
  • cid MANY&lt; - ONE Table3.id

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