转换是一种变更,它为数据库schema增加了新的特征,从而改变了数据库shcema的语义。
1、插入数据
在已有表中插入数据。
动机
在对表设计进行结构变更之后,通常需要进行“插入数据”变更。
- 重新组织表。如果进行了“表改名”、“合并表”、“拆分表”、“删除表”等重构,可能必须进行“”,在原有的表中重新组织数据。
- 提供静态查找数据。
- 创建测试数据。
可能的折衷
在表中插入新数据可能比较麻烦,特别是如果打算插入查找数据,而这些数据又被一个或多个其他的表参照时。
shcema更新的方法
为了更新数据库schema,必须执行以下步骤:
- 确定要插入的数据。包括确定所有的依赖关系。
- 确定数据的目的地。
- 确定数据源。来自其他表还是手工插入。
- 确定转换需求。插入目标表之前,是否需要转换。
数据迁移的方法
如果需要插入少量的数据,可能只需要一个简单的SQL脚本就足够了。对于大量的数据,需要更复杂的方式,如Oracle的SQLLDR或批量数据加载工具。
图1展示了如何向AccountType表中插入一条新记录,代表经纪(brokerage)账户。

图1 插入一条新AccountType
下面的代码展示了向AccountType表插入数据的DML:
INSERT INTO AccountType(AccountTypeID,Name,EffectiveDate)
VALUES (6,'Brokerage','Feb 1 2007');
访问程序更新的方法
如果“插入数据”是由另一类数据库重构引起的,应该按照那种重构更新应用。
如果“插入数据”是为了支持额外的数据,有可能外部应用代码需要进行修改。例如,在图1中,我们将“Brokerage”插入到AccountType表中。可能需要在WHERE子句中不需要这个值的情况下去掉它,如下面的代码所示。
// Before code
stmt.prepare(
"SELECT * FROM AccountType " +
"WHERE AccountTypeId NOT IN (?,?)");
stmt.setLong(1,PRIVATEACCOUNT.getId);
stmt.setLong(2,MONEYMARKETACCOUNT.getId);
stmt.execute();
ResultSet standardAccountTypes = stmt.executeQuery();
//After code
stmt.prepare(
"SELECT * FROM AccountType " +
"WHERE AccountTypeId NOT IN (?,?,?)");
stmt.setLong(1,PRIVATEACCOUNT.getId);
stmt.setLong(2,MONEYMARKETACCOUNT.getId);
stmt.setLong(3,BROKERAGE.getId);
stmt.execute();
ResultSet standardAccountTypes = stmt.executeQuery();也可以通过“引入视图”重构来创建一个特定的视图,返回数据的子集。参见后文。
同样,可能需要更新检验数据有效性的源代码。例如,存在一些代码,规定溢价账户(premium account)可以是私人账户(private)或者货币市场账户(money market)。现在也可以是经纪账户(Brokerage)。
//Before code
public enum PremiumAccountType {
PRIVATEACCOUNT(new Long(3)),
MONEYMARKET(new Long(4));
private Long id;
public Long getId() {
return id;
}
PremiumAccountType(Long value) {
this.id = value;
}
public static Boolean
isPremiumAccountType(Long idToFind) {
for (PremiumAccountType premiumAccountType :
PremiumAccountType.values()) {
if (premiumAccountType.id.equals(idToFind))
return Boolean.TRUE;
}
Return Boolean.FALSE
}
//After code
public enum PremiumAccountType {
PRIVATEACCOUNT(new Long(3)),
MONEYMARKET(new Long(4)),
BROKERAGE(new Long(6));
private Long id;
public Long getId() {
return id;
}
PremiumAccountType(Long value) {
this.id = value;
}
public static Boolean
isPremiumAccountType(Long idToFind) {
for (PremiumAccountType premiumAccountType :
PremiumAccountType.values()) {
if (premiumAccountType.id.equals(idToFind))
return Boolean.TRUE;
}
Return Boolean.FALSE
}2、引入新列
在已有的表中引入一个新列。
动机
引入新列的原因可能包括:
- 持久化新的属性。
- 一次重构的中间步骤。许多重构,如“移动列”和“列改名”都包含一次引入新列的步骤。
可能的折衷
需要确保该列没有在其他地方已经存在;否则,可能因为增加了数据冗余而导致参照完整性问题。
schema更新的方法
如图2所示,只需要通过SQL命令ALTER TABLE的ADD子句引入CountryCode列。
ALTER TABLE State ADD Country Code VARCHAR2(3) NULL;

图2 引入State.CountryCode列
State.CountryCode具有一个外键,参照到Country表。为了创建参照约束,需要进行“加入外键约束”重构。
数据迁移的方法
虽然这类转换本身没有数据迁移,但一项重要的任务是向该列中填充数据值。需要执行以下的步骤:
- 与项目涉众一起确定合适的值。
- 手工或者通过脚本自动填充该列。
- 考虑对该列进行“引入缺省值”、“删除不可空”或者“使列不可空”等重构。
下面的代码演示了用初始值“USA”填充State.CountryCode列的DML。
UPDATE State SET CountryCode = 'USA' WHERE CountryCode IS NULL;
访问程序更新的方法
需要更新的就是在应用中使用新的列。下面的代码展示了hibernate的OR映射元数据的更新。
//Before mapping
//After mapping
column="COUNTRYCODE"/>
3、引入新表
在现有数据库中引入一个新表。
动机
引入新表的原因可能包括:
- 持久化新的属性。
- 一次重构的中间步骤。许多重构,如“拆分表”和“表改名”,都包含一个引入新表的步骤。
- 为了引入新的正式数据源。常常存在类似信息存储在多个表中,彼此之间不同步或者相互矛盾。这种情况下必须进行“使用正式数据源”重构,然后对那些非正式数据源进行“删除表”重构。
- 需要进行数据备份。一些重构,如“删除表”和“合并表”,可能需要创建一个表保存中间数据或者作为备份。
可能的折衷
主要是需要确认引入的表没有在其他地方已经存在。通常是存在与之近似的表,重构原有的表比加入新表并包含冗余信息更容易一些。
schema更新的方法
如图3所示,只需要通过SQL命令CREATE TABLE引入CustomerIdentification。

图3 引入CustomerIdentification表
下面的代码展示了引入CustomerIdentification表的DDL。CustomerID列具有一个外键,参照到Customer表。
CREATE TABLE CustomerIdentification(
CustomerID NUMBER NOT NULL,
Photo BLOB,
PassportID NUMBER,
CONSTRAINT PK_CustomerIdentification
PRIMARY KEY (CustomerID)
);数据迁移的方法
虽然这类转换本身没有数据迁移,但一项重要的任务是向该表中填充数据值。需要执行以下的步骤:
- 与项目涉众一起确定合适的值。
- 手工或者编写脚本自动地填充该表。
- 考虑进行“插入数据”转换。
访问程序更新的方法
理想情况下,只需要在应用中使用新的表就可以了。但如果新表是为了替代其他的几个表,需要重构访问程序使用新表。
4、引入视图
基于数据库中已有的表创建一个视图。
动机
引入视图的原因可能包括:
- 汇总报表数据。
- 取代重复的读取。一些外部程序或者存储过程常常实现了相同的检索查询,可以被一个通用的只读表或者视图取代。
- 数据安全。视图可以向终端用户提供读取数据的权限,但不提供更新的权限。
- 封装对表的访问。通过定义可更新的视图来封装对表的访问。这样可以容易地进行类似“列改名”或“表改名”的重构,因为视图在表和应用之间增加了一个封装层。
- 减少SQL重复。如果应用存在复杂的SQL查询,通常SQL的部分内容在许多地方是重复的。
可能的折衷
引入视图主要有两项挑战。首先,终端用户可能不能接受视图的连接性能,这就需要使用其他的方式,例如“引入只读表”重构。其次,增加新的视图将增加数据库schema的内部耦合性,如图4所示,视图定义依赖于表的定义。

图4 引入CustomerPortfolio视图
schema更新的方法
为了更新数据库schema,只需要通过SQL命令CREATE VIEW。下面的代码展示了引入CustomerPortfolio视图的DDL。
CREATE VIEW CustomerPortfolio (
CustomerID
Name
PhoneNumber
AccountsTotalBalance
InsuranceTotalPayment
InsuranceTotalValue
) AS SELECT
Customer.CustomerID,
Customer.Name,
Customer.PhoneNumber,
SUM(Account.Balance),
SUM(Insurance.Payment),
SUM(Insurance.Value)
FROM
Customer,Account,Insurance
WHERE
Customer.CustomerID=Account.CustomerID
AND Customer.CustomerID=Insurance.CustomerID
;数据迁移的方法
这类转换没有数据迁移的要求。
访问程序更新的方法
如果引入视图是为了取代访问程序中的通用SQL读取代码,那么必须更新相应的代码,使用新的视图;如果是为了得到报表,应该编写新的报表,利用视图带来的便利性。
如果应用中存在重复的SQL代码,很可能存在缺陷,因为改动一处的SQL时,必须对所有重复的SQL代码进行类似的改动。例如,考虑下面这个例子:
SELECT Customer.CustomerID, SUM(Insurance.Payment), SUM(Insurance.Value) FROM Customer, Insurance WHERE Customer.CustomerID=Insurance.CustomerID AND Customer.Status = 'ACTIVE' AND Customer.InBusinessSince <= TodaysDateLastYear GROUP BY Customer.CustomerID ; SELECT Customer.CustomerID, SUM(Account.Balance) FROM Customer, Account WHERE Customer.CustomerID=Account.CustomerID AND Customer.Status = 'ACTIVE' AND Customer.InBusinessSince <= TodaysDateLastYear GROUP BY Customer.CustomerID;
可以看到两段代码对于活动(ACTIVE)顾客的选择是重复的。可以创建一个视图,将重复的SQL提取到视图中,这样当SQL选择活动顾客的方式发生变化时,不需要多次修改。
CREATE OR REPLACE VIEW ActiveCustomer SELECT Customer.CustomerID FROM Customer WHERE Customer.Status = 'ACTIVE' AND Customer.InBusinessSince <= TodaysDateLastYear ; SELECT ActiveCustomer.CustomerID, SUM(Insurance.Payment), SUM(Insurance.Value) FROM ActiveCustomer, Insurance WHERE ActiveCustomer.CustomerID=Insurance.CustomerID GROUP BY ActiveCustomer.CustomerID ; SELECT ActiveCustomer.CustomerID, SUM(Account.Balance) FROM ActiveCustomer, Account WHERE ActiveCustomer.CustomerID=Account.CustomerID GROUP BY ActiveCustomer.CustomerID ;
5、更新数据
更新现有表中的数据。
动机
可能的原因包括:
- 表重组。如果进行了“表改名”、“列改名”、“移动列”、“拆分表”、“拆分列”、“合并表”或者“合并列”等重构,可能需要进行“更新数据”,重新组织原表中的数据。
- 提供原来没有的数据。如果进行了“引入新列”这样的转换,需要在生产环境中为新增的列提供数据。
- 修改参照数据。
- 支持列变更。类似“采用标准代码”和“采用标准类型”的重构,常常需要更新该列中保存的数据。
- 修复事务性数据。由于部署的应用或数据库的缺陷,可能得到了一些无效的结果,需要进行修复变更。
可能的折衷
更新表中的数据可能比较麻烦,特别是更新参照数据。
schema更新的方法
这类转换没有更新schema的要求。
数据迁移的方法
如果更新少量数据,可能只需要一个简单的SQL脚本就足够了。对于大量数据,需要诸如ETL工具的更复杂的方式。要考虑的问题包括:
- 源数据是由原来的应用表提供,还是由业务用户提供?
- 项目涉众接受这些值吗?
- 哪些行需要更新?
- 这些行中的哪些列需要更新?
- 存在怎样的依赖关系?
图5展示了如何更新AccountType表。这个AccountType支持新的命名惯例,需要先测试,然后再部署到生产环境中去。

图5 更新AccountType表
下面的代码展示了更新表的DDL。
UPDATE AccountType SET Name = 'Chequing' WHERE AccountTypeID=1; UPDATE AccountType SET Name = 'Private Banking' WHERE AccountTypeID=3;
访问程序更新的方法
如果“更新数据”是由另一类数据库重构引起的,外部程序应该按照那种重构进行更新。
如果是为了支持在原有的schema上改动数据,可能外部程序需要进行改动。首先,可能需要更新SELECT语句,使WHERE子句包含更新后的值。类似,可能需要更新检验数据有效性的代码。下面的视图定义展示了当“Private”账户类型改为“Private Banking”时,应用如何修改。
//Before view CREATE OR REPLACE VIEW PrivateAccounts AS SELECT Account.AccountId, Account.CustomerId, Account.StartDate, Account.Balance, Account.isPrimary FROM Account, AccountType WHERE Account.AccountTypeId = AccountType.AccountTypeId AND AccountType.Name = 'Private'; ; //After view CREATE OR REPLACE VIEW PrivateAccounts AS SELECT Account.AccountId, Account.CustomerId, Account.StartDate, Account.Balance, Account.isPrimary FROM Account, AccountType WHERE Account.AccountTypeId = AccountType.AccountTypeId AND AccountType.Name = 'Private Banking' ;