我有一个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中执行此类查询的有效方法。
答案 0 :(得分:4)
你总是可以构建一个违反任何规则的场景,所以你标题中的问题答案是肯定的。
然而,这不是其中一种情况。您提出的搜索和演示问题对于大多数一对多关系(或者至少是许多一对多关系)是常见的,如果这是违反第一范式的原因那么你就不会看到很多规范化的数据库。
正确构建数据库,您不必担心逗号,相互嵌入的搜索术语以及由于缺少索引而导致搜索速度变慢。编写一段可重复使用的代码,为您执行逗号分隔的汇总,这样您就不会继续重新发明轮子。
答案 1 :(得分:0)
我仍然可以正常地将其正常化 - 然后担心演示文稿。
在Oracle中- 这将通过用户定义的聚合函数来完成。
答案 2 :(得分:0)
为什么不构建一个连接表,这样就可以维持与字段引用完整性的1:1关系。否则你将需要解析1:many字段值然后得到一个参考关联,所以一切都神奇地工作(呵呵呵;))
当我发现自己需要违反第一范式时,答案9:10是创建一个连接表并构建一些方法来产生预期效果。
gid (Number) <- Table2.id
cid (Number) <- Table3.id
price (Number)
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];
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