首页 > Linux操作系统 > Linux操作系统 > 数据库重构11之转换
转换是一种变更,它为数据库schema增加了新的特征,从而改变了数据库shcema的语义。
在已有表中插入数据。
动机
在对表设计进行结构变更之后,通常需要进行“插入数据”变更。
可能的折衷
在表中插入新数据可能比较麻烦,特别是如果打算插入查找数据,而这些数据又被一个或多个其他的表参照时。
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
}在已有的表中引入一个新列。
动机
引入新列的原因可能包括:
可能的折衷
需要确保该列没有在其他地方已经存在;否则,可能因为增加了数据冗余而导致参照完整性问题。
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"/>
在现有数据库中引入一个新表。
动机
引入新表的原因可能包括:
可能的折衷
主要是需要确认引入的表没有在其他地方已经存在。通常是存在与之近似的表,重构原有的表比加入新表并包含冗余信息更容易一些。
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)
);数据迁移的方法
虽然这类转换本身没有数据迁移,但一项重要的任务是向该表中填充数据值。需要执行以下的步骤:
访问程序更新的方法
理想情况下,只需要在应用中使用新的表就可以了。但如果新表是为了替代其他的几个表,需要重构访问程序使用新表。
基于数据库中已有的表创建一个视图。
动机
引入视图的原因可能包括:
可能的折衷
引入视图主要有两项挑战。首先,终端用户可能不能接受视图的连接性能,这就需要使用其他的方式,例如“引入只读表”重构。其次,增加新的视图将增加数据库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 ;
更新现有表中的数据。
动机
可能的原因包括:
可能的折衷
更新表中的数据可能比较麻烦,特别是更新参照数据。
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' ;
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/24945919/viewspace-745904/ ,如需转载,请注明出处,否则将追究法律责任。