7

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

我继承了一个数据库,其中包含 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,虽然看起来不优雅!

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

4

20 回答 20

8

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

于 2008-09-16T20:58:26.033 回答
8

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

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

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

于 2008-09-16T21:00:37.480 回答
3

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

于 2008-09-16T21:01:38.380 回答
2

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

尽管 access 中有一个旋转命令,但没有反向语句。

看起来可以通过复杂的连接来完成。查看这篇有趣的文章,了解如何在选择命令中取消透视。

于 2008-09-16T21:24:02.760 回答
1

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

于 2008-09-16T20:59:46.233 回答
1

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

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

于 2008-09-16T21:01:43.747 回答
1

在 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'
于 2008-09-16T21:03:02.793 回答
1

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

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

于 2008-09-16T21:03:36.210 回答
1

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

于 2008-09-16T21:03:46.740 回答
1

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

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

于 2008-09-16T21:10:32.353 回答
1

我会通过对您的 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

于 2008-09-16T21:55:51.560 回答
1

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

于 2008-09-16T21:57:48.903 回答
1

@跺脚:

在您键入答案的框上方,有几个按钮。101010 是代码示例。您选择所有作为代码的文本,然后单击该按钮。然后它不会被弄乱。

cout>>"I don't know C"
cout>>"Hello World"
于 2008-09-16T22:51:27.510 回答
1

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

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

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

于 2008-09-16T23:13:02.497 回答
1

当我读到标题“糟糕的 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 (...)

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

于 2008-09-16T23:30:41.097 回答
1

抱歉,我几乎没有做过 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 是表别名。

希望这有帮助

于 2008-09-16T23:54:20.307 回答
1

OBTW,

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

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

于 2008-09-17T00:02:16.327 回答
1

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

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

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

于 2008-09-23T03:59:26.960 回答
1

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

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

可能的 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 进行硬编码(因为你只会这样做一次,对吗?你可以在创建国家表,所以你知道所有的 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 
于 2008-10-13T14:07:44.683 回答
1

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

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

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

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

阿富汗阿尔巴尼亚。, .

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

Finally, you need to manually change the last "UNION" back to a semicolon.

If you can get Access to perform this giant union, you can get the data you want in the form you want, and insert it into your new table.

于 2008-10-22T11:59:56.013 回答