当我遇到这些时,我编写了一个脚本来进行转换,而不是尝试在SQL中执行。对我来说,它通常更快更容易。选择您喜欢的任何语言。
当我读到“糟糕的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 (…)
</code>
显然这是伪代码,不会像现在这样工作。您还可以通过在此处添加插入语句来动态填充国家/地区和物种表。
在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’
</code>
</pre>
当我遇到类似问题时,我发现生成生成SQL脚本的脚本很方便。这是你提供的样本,抽象使用%PAR1%代替阿富汗。
SELECT Species.ID, Country.CountryID
FROM Country, Species
WHERE (((Species.%PAR1%)=1)) AND (((Country.Country)=”%PAR1%”))
UNION
</code>
此外,关键词union已被添加为组合所有选择的一种方式。
接下来,您需要一个根据现有数据生成的国家/地区列表:
阿富汗
阿尔巴尼亚
。
,
。
接下来,您需要一个可以遍历国家/地区列表的脚本,并且对于每次迭代,
产生一个输出,在第一次迭代时替代阿富汗%PAR1%,在第二次迭代时替代阿尔巴尼亚,依此类推。该算法就像文字处理器中的邮件合并一样。编写这个脚本有点工作。但是,一旦你拥有它,你可以在像这样的几十个一次性项目中使用它。
最后,您需要手动将最后一个“UNION”更改回分号。
如果您可以使用Access来执行此巨型联合,则可以以所需的形式获取所需的数据,并将其插入新表中。
您可能会聪明地查询系统表中的列名,然后构建一个动态查询字符串来执行,但老实说,这可能比快速脚本更难以为您生成SQL语句。
希望您没有太多动态SQL代码来访问隐藏在代码库中的旧表。那可能是
真
困难的部分。
OBTW,
如果您有针对旧表运行的查询,则需要创建一个使用新表复制旧表的视图。您需要执行一个组来对表进行非规范化。
告诉您的用户将来不支持旧表/视图,所有新查询或旧查询的更新都必须使用新表。
我会通过对您的SpeciesFoundInCountry表进行轻微的临时修改来实现它的三步过程。我会在该表中添加一列来存储国家/地区名称。然后步骤如下。
1)创建/运行一个脚本,该脚本遍历源表中的列,并在SpeciesFoundInCountry中为每个具有true值的列创建记录。此记录将包含国家/地区名称。
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
</code>
在此之后,您可以运行一个简单的SQL语句来更新SpeciesFoundInCountry表中的CountryID值。
UPDATE SpeciesFoundInCountry INNER JOIN Country ON SpeciesFoundInCountry.CountryName = Country.CountryName SET SpeciesFoundInCountry.CountryID = Country.CountryID;
最后,您所要做的就是通过删除CountryName列来清理SpeciesFoundInCountry表。
** SIDE注意:我发现让国家/地区表格也包含ISO缩写(国家/地区代码)非常有用。有时,它们在其他表中用作外键,因此不必在查询中包含到Country表的连接。
欲了解更多信息:
http://en.wikipedia.org/wiki/Iso_country_codes
这是(希望)一次性的练习,所以一个不优雅的解决方案可能不会听起来那么糟糕。
问题(因为,我确定你只是太清楚了!)是在你的查询的某个时刻,你必须列出所有这些列。 :(问题是,最优雅的方法是什么?下面是我的尝试。它看起来很笨,因为有很多列,但它可能是你所追求的,或者至少它可能会指向你正确的方向。
/ 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
</code>
我的建议
就个人而言,我不会这样做。我会做一个快速而肮脏的解决方案,就像你提到的解决方案一样,除了我会硬编码国家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
</code>
我会使用Union查询,非常粗略:
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
</code>
然后,您将拥有一个可以附加到新设计的视图。