热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

数据库再设计(DatabaseRedesign)

数据库设计有三个来源:(1)可以从现有数据开始设计数据库,例如从excel表格等,这种模式下需要考虑的问题是数据的normalization,最终通常将数据转化为BCNF范式;(2)设计新的数据库,这种模式需要从构建E-R图开始一步步构造完整的数据库;(3)数据库再

数据库设计有三个来源:(1)可以从现有数据开始设计数据库,例如从excel表格等,这种模式下需要考虑的问题是数据的normalization,最终通常将数据转化为BCNF范式;(2)设计新的数据库,这种模式需要从构建E-R图开始一步步构造完整的数据库;(3)数据库再

数据库设计有三个来源:(1)可以从现有数据开始设计数据库,例如从excel表格等,这种模式下需要考虑的问题是数据的normalization,最终通常将数据转化为BCNF范式;(2)设计新的数据库,这种模式需要从构建E-R图开始一步步构造完整的数据库;(3)数据库再设计,这种模式需要修改现有数据库,使其满足新的数据或者性能要求。本博客介绍第三种数据库设计模式。此外,为了介绍数据库再设计,我们需要有一个现成的数据库。我们将以一个艺术品拍卖会的数据库为例介绍,图一描述了其数据库。

\

1. 数据库再设计目的

一般情况下,如果我们按照标准的模式从前两种来源设计数据库,最终的结果都是满足相关要求的,为什么还需要重新设计数据库?该问题的解答包含两方面:首先,正确地设计数据库其实非常难,尤其是从无到有的全新设计。即使我们获得了完整的用户需求,并创建了一个正确的数据模型,将模型转化为实际数据库依旧非常难,尤其模型非常复杂时。更重要的一方面是,用户的需求一直在变化,这直接导致数据库的不停变更。因而数据库再设计是数据库设计和维护中很重要的一部分,再设计过程会存在于数据库的整个生命周期。

2. 准备工作

在修改数据库结构之前,我们需要做一些准备工作。首要的任务是熟悉当前数据库的结构和存储的数据,以了解数据库中各结构的相互依赖关系。其次,在修改实际数据库之前,我们需要在相当数据量的测试数据库上进行测试,只有在测试数据库上保证正确之后才能实际修改可操作数据库(operational database)。最后,一定要备份可操作数据库。

3. 依赖图

在修改数据库结构之前,我们还需要构造一个依赖图(dependency graph),用来描述不同数据库结构之间的依赖关系。当我们在修改数据库的不同结构时,会影响其他结构,通过依赖图就可以很清楚地看出修改一个结构会影响哪些结构。图二描述了图一数据库中不同结构的依赖图。

\

假设我们现在需要修改artist表,则依赖于它的表work,触发器Trans_checkSalesPrice和视图ArtistWorkNetView也需要修改。根据实际情况,修改表work可能还会递归导致依赖于它的结构也需要修改。可以看出数据库再设计是一个相当复杂的过程。不过,通常情况下的修改只会导致子节点的变动,不太可能修改子子节点或者后续子节点。

数据库再设计通常包含三个方面的修改:表或关系、列和基数,下面详细介绍不同的修改。此外,本博客涉及的数据库再设计仅限于数据库数据的范围没有大到不能复制的程度。

1. 数据库表的修改

4.1表名的修改

单纯的表名修改没有想象中那么简单,会导致一系列结构的修改。例如,假设我们现在要将work表名改为WORK_VERSION2,我们需要执行下列步骤:

1) 利用CREATE TABLEWORK_VERSION2语句创建新的表,在work中workID是一个surrogate key,在WORK_VERSION2中暂时不将该主键设为surrogate key。同时将work表中的constraint复制到新表中,注意修改constraint的名字,否则会提示冲突。

2) 将work表中的数据利用insert into语句添加到新表中,添加语句包含work所有的列。

3) 修改WORK_VERSION2表中workID为surrogatekey。修改方式在不同的数据库产品上不同。

4) 修改触发器Trans_checkSalesPrice和视图ArtistWorkNetView,主要是将旧表名work改为新表名WORK_VERSION2。

5) 删除旧表work。删除时需要先将表trans中的外键约束删除,然后再删除work。

6) 在trans表上添加新的外键约束使其指向新表WORK_VERSION2的workID。

4.2 添加新表或关系

添加新表或者关系通常没有什么难点,直接利用create table创建新表并附加各种constraint即可。唯一的一点例外是,如果创建的新表是某个现存表的父节点(新表和现存表有1:N的最大基数关系),我们需要在现存表中添加外键约束。

4.3 删除表或关系

删除表通常遵循这样一个原则:首先删除依赖该表主键的外键约束,然后删除该表。当然,在删除过程中,视图和触发器也应该根据依赖图进行相应的修改或删除。

2. 数据库表中列的修改

列的修改本质上还是属于数据库表的修改,但是由于其本身含有较多内容,单独拿出来介绍。

5.1 列名的修改

如果修改的是一个nonkey形式的列,通常不会带来什么影响,但是还是需要检查是否有触发器或者视图依赖于该列,如果有也需要修改相应的列名。如果修改的是主键,除了检查视图和触发器,我们还需要将对应的外键名字也修改掉。

5.2 添加列

添加可以null的列直接采用alter table add column语句即可。在数据库运行的过程中,我们可以随时修改该列的属性,比如设置default值等。但是设置的default值只会存在于后续添加的行中,先前添加的行值可能还是null。

初次添加一个not null的列也是直接采用altertable add column语句。但是我们不能将其设置为not null,因为表中已经存在很多行,直接设置not null会报错。然后更新所有的行使其值为not null,然后再利用SQL语句ALTER TABLE ALTER COLUMN设置列属性为not null。

5.3 删除列

nonkey的列直接删除一般不会存在问题,但是还是需要注意视图和触发器。如果删除的是外键,则需要先删除外键对应的constraint,然后再删除外键对应的列。

当删除主键时,情况会复杂很多。例如我们要删除work表的主键,并替换为新的主键,则需要执行下列步骤:

1) 首先删除表trans中的外键约束WorkFK;

2) 其次删除表work中的主键约束WorkPK;

3) 利用列(Title,Copy, ArtistID)创建新的主键约束WorkPK;

4) 在trans表中创建依赖于work中列(Title, Copy, ArtistID)的外键约束WorkFK;

5) 删除列workID。

5.4 修改列的数据类型或者约束

可以用命令ALTER TABLE ALTER COLUMN命令修改列的属性。如果将列由null变为not null,则必须确保所有的列都为not null。不同数据类型之间的转换可能导致数据丢失。但是通常情况下,将数字类型、时间、货币类型转换为char或者varchar都没有问题,反过来则可能不被DBMS允许。

添加删除constraint通过ALTER TABLEADD CONSTRAINT 和ALTER TABLE DROP CONSTRAINT语句完成。

3. 修改关系的基数(Cardinalities)

6.1 修改最小基数

parent端

修改parent端的最小基数,也就是让child表有或者没有parent,也即判断child中外键是否为null。例如,针对1:N的关系DEPARTMENT to EMPLOYEE,外键DepartmentNumber出现在employee中。修改employee是否需要指定department也即修改DepartmentNumber的null状态。

如果将最小基数从0改为1,就需要将外键设置为not null,这时需要确保目前存在的数据外键都为not null,否则我们需要先更新数据。根据不同的DBMS产品,修改外键的方式也不一样,但是可以遵循下面的步骤:

1) 删除旧的外键约束;

2) 设置外键列为not null;

3) 添加新的外键约束。

上面的例子修改最小基数的SQL语句如下:

ALTER TABLE EMPLOYEE
    DROP CONSTRAINT DepartmentFK;
ALTER TABLE EMPLOYEE
    ALTER COLUMN DepartmentNumber Int NOT NULL;
ALTER TABLE EMPLOYEE
    ADD CONSTRAINT DepartmentFK FOREIGN KEY (DepartmentNumber)
        REFERENCES DEPARTMENT (DepartmentNumber)    
	    ON UPDATE CASCADE;

级联属性需要根据具体的业务来确定。

child端

在child端设置最小基数非零需要利用触发器或者application code,因而将最小基数从零变为一也需要利用触发器。如果将最小基数从1变为0,直接删除触发器即可。

6.2 增大最大基数

1:1 to 1:N

假设在EMPLOYEE 和PARKING_PERMIT之间存在如图三所示的1:1关系,现在默认employee是parent,因为外键在parking_permit中。当将1:1的关系变为1:N的关系时,需要考虑哪端是parent端(为1)。假如employee还是parent端,则只需要将parking_permit中的外键unique属性去掉即可。如果parking_permit变为parent端,则我们需要将外键移到employee中,还需要数据导入employee表中。具体的操作如下:

1) 在employee中添加可以为null的外键PermitNumber;

2) 将parking_permit中的数据导入employee表中:
UPDATE EMPLOYEE
    SET EMPLOYEE.PermitNumber =
        (SELECT  PP.PermitNumber
         FROM  PARKING_PERMIT AS PP
         WHERE  PP.EmployeeNumber = EMPLOYEE.EmployeeNumber);

3) 将parking_permit中的外键EmployeeNumber删除;

4) 在employee中添加外键约束;

5) 修改相应的触发器和视图。

\

1:N to N:M

将1:N的关系变为N:M的关系比较容易,只需要:创建一个只包含两个表主键的新表(intersection table),然后将两个主键的组合作为新表的主键,每一个分别是依赖于另外每一个表的外键;从child表导入数据,然后删除child中的外键约束。最后修改触发器和视图使其应用新表。

6.3 减小最大基数(存在数据丢失)

将一个N:M的关系降为一个1:N的关系,我们需要在child表中添加一个外键,然后从intersection table中导入外键的数据。修改相应的触发器和视图等,最后将intersection table删除。将一个1:N的关系降为1:1的关系,我们需要将child表中的外键数据变为1,然后将外键约束变为unique。这两种情况都需要决定如何删除数据。


推荐阅读
  • 本文介绍了如何在MySQL中将零值替换为先前的非零值的方法,包括使用内联查询和更新查询。同时还提供了选择正确值的方法。 ... [详细]
  • 本文介绍了如何使用Power Design(PD)和SQL Server进行数据库反向工程的方法。通过创建数据源、选择要反向工程的数据表,PD可以生成物理模型,进而生成所需的概念模型。该方法适用于SQL Server数据库,对于其他数据库是否适用尚不确定。详细步骤和操作说明可参考本文内容。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • MyBatis错题分析解析及注意事项
    本文对MyBatis的错题进行了分析和解析,同时介绍了使用MyBatis时需要注意的一些事项,如resultMap的使用、SqlSession和SqlSessionFactory的获取方式、动态SQL中的else元素和when元素的使用、resource属性和url属性的配置方式、typeAliases的使用方法等。同时还指出了在属性名与查询字段名不一致时需要使用resultMap进行结果映射,而不能使用resultType。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文介绍了通过mysql命令查看mysql的安装路径的方法,提供了相应的sql语句,并希望对读者有参考价值。 ... [详细]
  • 本文讨论了在数据库打开和关闭状态下,重新命名或移动数据文件和日志文件的情况。针对性能和维护原因,需要将数据库文件移动到不同的磁盘上或重新分配到新的磁盘上的情况,以及在操作系统级别移动或重命名数据文件但未在数据库层进行重命名导致报错的情况。通过三个方面进行讨论。 ... [详细]
author-avatar
谁是我在寻找
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有