【问题标题】:Mending bad BAD database design once data is in the system一旦数据在系统中,就修复糟糕的 BAD 数据库设计
【发布时间】:2010-09-09 18:29:00
【问题描述】:

我知道这不是一个问题......无论如何,这是问题所在。

我继承了一个数据库,其中包含 1(一个)表,看起来很像这样。其目的是记录在各个(200 多个)国家发现的物种。

ID 
Species
Afghanistan
Albania
Algeria
American Samoa
Andorra
Angola
....
Western Sahara
Yemen
Zambia
Zimbabwe

数据样本是这样的

id Species Afghanistan Albania American Samoa
1  SP1         null     null        null
2  SP2          1         1         null
3  SP3         null      null         1

在我看来,这是典型的多对多情况,我想要 3 张桌子。 物种、国家和物种FoundInCountry

链接表 (SpeciesFoundInCountry) 在物种和国家表中都有外键。

(图好难画!)

Species
SpeciesID  SpeciesName

Country
CountryID CountryName

SpeciesFoundInCountry
CountryID SpeciesID

是否有一种神奇的方法可以生成一个插入语句,该语句将根据列名和原始巨型表中为 1 的 SpeciesID 从新的 Country 表中获取 CountryID?

我可以为一个国家做这件事(这是一个展示我想要的东西的选择)

SELECT Species.ID, Country.CountryID
FROM Country, Species
WHERE (((Species.Afghanistan)=1)) AND (((Country.Country)="Afghanistan"));

(巨型表称为物种)

但是使用这种策略,我需要对原始表中的每一列进行查询。

有没有办法在sql中做到这一点?

我想我可以 OR 一堆 where 子句,然后编写一个脚本来生成 sql,虽然看起来不优雅!

有什么想法(或需要澄清)吗?

【问题讨论】:

    标签: sql ms-access database-design


    【解决方案1】:

    我会使用脚本来生成所有单独的查询,因为这是一次性导入过程。

    Excel 等一些程序擅长混合不同维度的数据(将列名与行内的数据进行比较),但关系数据库很少这样做。

    但是,您可能会发现某些系统(例如 Microsoft Access,令人惊讶)具有方便的工具,您可以使用这些工具来规范化数据。就我个人而言,我会发现编写脚本更快,但您在 Access 和脚本方面的相关技能可能与我不同。

    【讨论】:

      【解决方案2】:

      为什么要在 SQL 中执行此操作?只需编写一个执行转换的小脚本即可。

      【讨论】:

        【解决方案3】:

        当我遇到这些时,我会编写一个脚本来进行转换,而不是尝试在 SQL 中进行。对我来说,它通常更快更容易。选择您喜欢的任何语言。

        【讨论】:

          【解决方案4】:

          如果这是 SQL Server,您将使用 Unpivot 命令,但查看您为其分配的访问标签 - 我说的对吗?

          虽然有pivoting command in access,但没有反向语句。

          看起来可以通过复杂的连接来完成。查看此interesting article,了解如何在选择命令中取消透视。

          【讨论】:

            【解决方案5】:

            您可能想要在适当的位置创建替换表。脚本种类取决于您可以使用的脚本语言,但您应该能够通过列出您现在拥有的表的列来创建国家/地区 ID 表。完成此操作后,您可以进行一些字符串替换以遍历所有唯一的国家/地区名称并插入到给定国家/地区列不为空的 speciesFoundInCountry 表中。

            【讨论】:

              【解决方案6】:

              您可能会变得聪明并在系统表中查询列名,然后构建一个动态查询字符串来执行,但老实说,这可能比为您生成 SQL 语句的快速脚本更难看。

              希望您没有太多动态 SQL 代码来访问隐藏在代码库中的旧表。这可能是真正困难的部分。

              【讨论】:

                【解决方案7】:

                在 SQL Server 中,这将生成您演示的自定义选择。您可以外推到插入

                select 
                  'SELECT Species.ID, Country.CountryID FROM Country, Species WHERE (((Species.' + 
                 c.name + 
                 ')=1)) AND (((Country.Country)="' +
                 c.name + 
                 '"))'
                from syscolumns c
                inner join sysobjects o
                on o.id = c.id
                where o.name = 'old_table_name'
                

                【讨论】:

                  【解决方案8】:

                  与其他人一样,我很可能只是以任何适合您的方式一次性快速修复。

                  使用这些类型的转换,它们是一次性的、快速修复的,并且代码不必很优雅,它只需要工作即可。对于这些类型的事情,我做了很多方法。

                  【讨论】:

                    【解决方案9】:

                    如果这是 SQL Server,您可以使用 sys.columns 表来查找原始表的所有列。然后你可以使用动态 SQL 和 pivot 命令来做你想做的事。在线查找语法。

                    【讨论】:

                      【解决方案10】:

                      我绝对同意您编写一个小脚本来生成 SQL 并为每一列查询的建议。

                      事实上,你的脚本可能在你花时间思考这个神奇的查询时就已经完成了(你只会使用一次然后扔掉,那么让它变得神奇和完美有什么用)

                      【讨论】:

                        【解决方案11】:

                        抱歉,该死的帖子解析器删除了我帖子中的空格和格式。它使日志更难阅读。

                        【讨论】:

                          【解决方案12】:

                          @stomp:

                          在您输入答案的框上方,有几个按钮。 101010 是代码示例。您选择所有作为代码的文本,然后单击该按钮。这样就不会乱七八糟了。

                          cout>>"I don't know C"
                          cout>>"Hello World"
                          

                          【讨论】:

                            【解决方案13】:

                            我会使用联合查询,非常粗略:

                            Dim db As Database
                            Dim tdf As TableDef
                            
                            Set db = CurrentDb
                            
                            Set tdf = db.TableDefs("SO")
                            
                            strSQL = "SELECT ID, Species, """ & tdf.Fields(2).Name _
                                & """ AS Country, [" & tdf.Fields(2).Name & "] AS CountryValue FROM SO "
                            
                            For i = 3 To tdf.Fields.Count - 1
                                strSQL = strSQL & vbCrLf & "UNION SELECT ID, Species, """ & tdf.Fields(i).Name _
                                & """ AS Country, [" & tdf.Fields(i).Name & "] AS CountryValue FROM SO "
                            Next
                            
                            db.CreateQueryDef "UnionSO", strSQL
                            

                            然后您将拥有一个可以附加到您的新设计的视图。

                            【讨论】:

                              【解决方案14】:

                              当我读到标题“糟糕的 BAD 数据库设计”时,我很想知道它有多糟糕。你没有让我失望:)

                              正如其他人提到的,脚本是最简单的方法。这可以通过用 PHP 编写大约 15 行代码来完成。

                              SELECT * FROM ugly_table;
                              while(row)
                              foreach(row as field => value)
                              if(value == 1)
                              SELECT country_id from country_table WHERE country_name = field;
                              
                              if(field == 'Species')
                              SELECT species_id from species_table WHERE species_name = value;
                              
                              INSERT INTO better_table (...)
                              

                              显然这是伪代码,不会按原样工作。您还可以通过在此处添加插入语句来动态填充国家和物种表。

                              【讨论】:

                              • 当我看到它时,我真的哭了!我有什么进一步的荣耀发现这是真正的问题:)
                              【解决方案15】:

                              抱歉,我很少进行 Access 编程,但我可以提供一些指导,应该会有所帮助。

                              首先让我们来解决这个问题。 假设您通常需要在 SpeciesFoundInCountry 中为原始表中的每一行生成多行。换句话说,物种往往存在于不止一个国家​​。这实际上很容易用笛卡尔积实现,即没有连接条件的连接。

                              要进行笛卡尔积,您需要创建 Country 表。该表应具有从 1 到 N 的 country_id(N 是唯一国家/地区的数量,200 左右)和国家/地区名称。为了让生活更轻松,只需按列顺序使用数字 1 到 N。这将使阿富汗 1 和阿尔巴尼亚 2 ...津巴布韦 N。您应该能够使用系统表来执行此操作。

                              接下来从包含物种和每个国家/地区的 0 或 1 刺痛的原始表创建一个表或视图。您需要将 null 而不是 null 转换为文本 0 或 1,并将所有值连接成一个字符串。表格的描述和带有正则表达式的文本编辑器应该使这很容易。首先对单个列进行试验,一旦成功,编辑创建视图/插入所有列。

                              接下来将两个表连接在一起,没有连接条件。这将为您提供每个国家/地区每个物种的记录,您就快到了。

                              现在您所要做的就是过滤掉无效的记录,它们将在字符串中的相应位置有一个零。由于 country 表的 country_code 列具有子字符串位置,您需要做的就是过滤掉它为 0 的记录。

                              where substring(new_column,country_code) = '1'
                              

                              您仍然需要创建物种表并加入该表

                              where a.species_name = b.species_name
                              

                              a 和 b 是表别名。

                              希望有帮助

                              【讨论】:

                                【解决方案16】:

                                OBTW,

                                如果您已经对旧表运行了查询,则需要创建一个视图,该视图使用新表复制旧表。您将需要对表进行非规范化分组。

                                告诉您的用户,旧表/视图将来将不再受支持,所有新查询或对旧查询的更新都必须使用新表。

                                【讨论】:

                                  【解决方案17】:

                                  如果我必须创建一大堆类似的 SQL 语句并执行所有这些语句,我经常会发现 Excel 非常方便。接受您的原始查询。如果 A 列中有国家列表,B 列中有 SQL 语句,格式为文本(带引号),并在国家出现在 sql 中的位置插入单元格引用

                                  例如="INSERT INTO new_table SELECT ... (species." & A1 & ")= ... ));"

                                  然后只需将公式复制下来以创建 200 条不同的 SQL 语句,将列复制/粘贴到您的编辑器并按 F5。当然,您可以使用任意数量的变量来执行此操作。

                                  【讨论】:

                                    【解决方案18】:

                                    当我遇到类似的问题时,我发现生成一个生成 SQL 脚本的脚本很方便。这是您提供的示例,抽象为使用 %PAR1% 代替阿富汗。

                                    SELECT Species.ID, Country.CountryID
                                    FROM Country, Species
                                    WHERE (((Species.%PAR1%)=1)) AND (((Country.Country)="%PAR1%"))
                                    UNION
                                    

                                    还添加了关键字 union 作为组合所有选择的一种方式。

                                    接下来,您需要从现有数据生成的国家/地区列表:

                                    阿富汗 阿尔巴尼亚 . , .

                                    接下来,您需要一个可以遍历国家/地区列表的脚本,并且对于每次迭代, 产生一个输出,在第一次迭代中用阿富汗代替 %PAR1%,在第二次迭代中用阿尔巴尼亚代替,依此类推。该算法就像文字处理器中的邮件合并。编写这个脚本是一项小工作。但是,一旦拥有它,您就可以在数十个像这样的一次性项目中使用它。

                                    最后,您需要手动将最后一个“UNION”改回分号。

                                    如果你可以让Access来执行这个巨大的联合,你就可以得到你想要的形式的数据,并把它插入到你的新表中。

                                    【讨论】:

                                      【解决方案19】:

                                      我会通过对您的 SpeciesFoundInCountry 表稍作临时修改将其分为三步。我会在该表中添加一列来存储国家/地区名称。那么步骤如下。

                                      1) 创建/运行一个脚本,该脚本遍历源表中的列,并在 SpeciesFoundInCountry 中为每个具有真值的列创建一条记录。该记录将包含国家名称。 2) 运行一条 SQL 语句,通过加入 Country Name 上的 Country 表来更新 SpeciesFoundInCountry.CountryID 字段。 3) 通过删除 CountryName 列来清理 SpeciesFoundInCountry 表。

                                      这里有一点 MS Access VB/VBA 伪代码来给你要点

                                      Public Sub CreateRelationshipRecords()
                                      
                                        Dim rstSource as DAO.Recordset
                                        Dim rstDestination as DAO.Recordset
                                        Dim fld as DAO.Field
                                        dim strSQL as String
                                        Dim lngSpeciesID as Long
                                      
                                        strSQL = "SELECT * FROM [ORIGINALTABLE]"
                                        Set rstSource = CurrentDB.OpenRecordset(strSQL)
                                        set rstDestination = CurrentDB.OpenRecordset("SpeciesFoundInCountry")
                                      
                                        rstSource.MoveFirst
                                      
                                        ' Step through each record in the original table
                                        Do Until rstSource.EOF
                                          lngSpeciesID = rstSource.ID
                                          ' Now step through the fields(columns). If the field
                                          ' value is one (1), then create a relationship record
                                          ' using the field name as the Country Name
                                          For Each fld in rstSource.Fields
                                            If fld.Value = 1 then
                                              with rstDestination
                                                .AddNew
                                                .Fields("CountryID").Value = Null
                                                .Fields("CountryName").Value = fld.Name
                                                .Fields("SpeciesID").Value = lngSpeciesID
                                                .Update
                                              End With
                                            End IF
                                          Next fld  
                                          rstSource.MoveNext
                                        Loop
                                      
                                        ' Clean up
                                        rstSource.Close
                                        Set rstSource = nothing
                                        ....
                                      
                                      End Sub
                                      

                                      之后,您可以运行一个简单的 SQL 语句来更新 SpeciesFoundInCountry 表中的 CountryID 值。

                                      UPDATE SpeciesFoundInCountry INNER JOIN Country ON SpeciesFoundInCountry.CountryName = Country.CountryName SET SpeciesFoundInCountry.CountryID = Country.CountryID;

                                      最后,您要做的就是通过删除 CountryName 列来清理 SpeciesFoundInCountry 表。

                                      ****旁注:我发现拥有包含 ISO 缩写(国家代码)的国家/地区表很有用。有时它们被用作其他表中的外键,因此查询中不必包含对 Country 表的连接。

                                      欲了解更多信息:http://en.wikipedia.org/wiki/Iso_country_codes

                                      【讨论】:

                                      • 谢谢。如果您返回并编辑您的帖子,然后选择代码位并单击代码按钮(零和一!),它将用四个空格缩进您的代码,从而为您提供常规代码着色等。
                                      【解决方案20】:

                                      这是(希望)一次性练习,所以一个不优雅的解决方案可能没有听起来那么糟糕。

                                      问题(因为,我相信您只是太清楚了!)是在您的查询中的某个时刻,您必须列出所有这些列。 :( 问题是,最优雅的方法是什么?下面是我的尝试。它看起来很笨拙,因为有很多列,但它可能是你所追求的,或者至少它可能会指向你正确的方向。

                                      可能的 SQL 解决方案:

                                      /* if you have N countries */
                                      CREATE TABLE Country
                                      (id    int, 
                                       name  varchar(50)) 
                                      
                                      INSERT Country
                                            SELECT 1, 'Afghanistan'
                                      UNION SELECT 2, 'Albania', 
                                      UNION SELECT 3, 'Algeria' ,
                                      UNION SELECT 4, 'American Samoa' ,
                                      UNION SELECT 5, 'Andorra' ,
                                      UNION SELECT 6, 'Angola' ,
                                      ...
                                      UNION SELECT N-3, 'Western Sahara', 
                                      UNION SELECT N-2, 'Yemen', 
                                      UNION SELECT N-1, 'Zambia', 
                                      UNION SELECT N, 'Zimbabwe', 
                                      
                                      
                                      
                                      CREATE TABLE #tmp
                                      (key        varchar(N),  
                                       country_id int) 
                                      /* "key" field needs to be as long as N */  
                                      
                                      
                                      INSERT #tmp 
                                      SELECT '1________ ... _', 'Afghanistan' 
                                      /* '1' followed by underscores to make the length = N */
                                      
                                      UNION SELECT '_1_______ ... ___', 'Albania'
                                      UNION SELECT '__1______ ... ___', 'Algeria'
                                      ...
                                      UNION SELECT '________ ... _1_', 'Zambia'
                                      UNION SELECT '________ ... __1', 'Zimbabwe'
                                      
                                      CREATE TABLE new_table
                                      (country_id int, 
                                      species_id int) 
                                      
                                      INSERT new_table
                                      SELECT species.id, country_id
                                      FROM   species s , 
                                             #tmp    t
                                      WHERE  isnull( s.Afghanistan, ' ' ) +  
                                             isnull( s.Albania, ' ' ) +  
                                             ... +  
                                             isnull( s.Zambia, ' ' ) +  
                                             isnull( s.Zimbabwe, ' ' ) like t.key 
                                      

                                      我的建议

                                      就个人而言,我不会这样做。我会像你提到的那样做一个快速而肮脏的解决方案,除了我会对国家 ID 进行硬编码(因为你只会这样做一次,对吗?你可以在创建country 表,所以你知道所有的 ID 是什么):

                                      INSERT new_table SELECT Species.ID, 1 FROM Species WHERE Species.Afghanistan = 1 
                                      INSERT new_table SELECT Species.ID, 2 FROM Species WHERE Species.Albania= 1 
                                      ...
                                      INSERT new_table SELECT Species.ID, 999 FROM Species WHERE Species.Zambia= 1 
                                      INSERT new_table SELECT Species.ID, 1000 FROM Species WHERE Species.Zimbabwe= 1 
                                      

                                      【讨论】:

                                        猜你喜欢
                                        • 2021-10-02
                                        • 2010-12-13
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2010-09-15
                                        • 2015-04-22
                                        • 2012-02-12
                                        • 1970-01-01
                                        • 1970-01-01
                                        相关资源
                                        最近更新 更多