深入浅出SQL(10)-多张表的数据库设计

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baby_hua/article/details/82497958

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

多张表的数据库设计:拓展你的表

到了某个时候,一张表不够了:

    我们在上一章中就已经体会到,对于四季饮品的表,查询饮品信息还是销售信息,都只能从这一张表查询;

    当数据变得越来越复杂,这唯一的一张表就显得不够用了;表里充满了多余的数据,浪费空间又会拖慢速度;

本章我们将学习用多张表来记录数据、控制数据;

新建示例数据库及表:

    新建我的饮品店中关于饮品的数据库及表:hq_drink_list、drink_list;

    并插入若干条数据;

不具有原子性的burdening(配料)列:

    如果我们查询包含banana的饮品,我们将不得不使用LIKE搭配通配符的方式:%banana%;

    如果继续限定一些其他的查询条件,这个查询的并不能算是简单;

    再如果,想查询配料包含apple的饮品信息呢。。。就要写好多类似的查询,当表变得庞大之后,查询会很麻烦;

我们的表应该是为了节省精力而设计的,别为了克服设计不良的表而执意改善查询;

创建多个列来存储各项配料:

    可见各个配料放在一列中存储是很不方便的查询;

    我们需要重新设计他的表;

添加更多配料列:

    只有一个配料列会使查询非常不精确;

    我们已经学会使用ALTER修改表,拆分文本字符串到多个列;

    我们可以把各个配料存入不同的列中,嗯,4个列应该足够了;

一切从头开始:

    我们使用burdening1、burdening2、burdening3、burdening4表示各个配料;

    但是,如此这般,我们的查询配料包含banana的条件很可能是这样的:

        burdening1 = ‘banana’ OR burdening2 = ‘banana’ ORburdening3 = ‘banana’ OR burdening4 = ‘banana’;

    当查询包含apple和banana的记录时,还需要用AND链接两个上边的子句,显然这是很糟的方式;

一切都失败了:

    我们想的拆分多个列,并没能让查询更加简单;而且修改之后的表也不符合原子性的需求;

跳出一张表的思考框框:

    在目前的这一张表中不会有什么好的方案了;

    我们需要更多能与现有内容合作的表,让每种饮品和他们的配料之间产生关联;

我们需要把不符合原子性的列移入新的表;

我们现在的表:

我们要把原本的一张表扩大成更有效果的一组相关的表:(图示:Final)

现在让我们一步一步的学习,这些都是如何实现的;

数据库模式:

    模式(schema)用于表达数据库内的结构,包括表和列,以及他们之间相互连接的方式;

    创建数据库的视觉解析图,在设计查询时有助于理解数据相连的方式;模式也能用文字表达;

模式:

    对数据库内的数据描述(列和表),以及任何相关对象和各种连接方式的描述就称为SCHEMA,即模式;

类似上边图所示的图表,我们需要知道的是:为表创建图表可以协助我们分别看待表的设计与其中的数据;

如何从一张表变成两张表:

    我们知道配料列的多个值,或者多个配料列都会使查询非常难写;

    我们先解决原子性的问题:使用另外一张表来存储配料;

步骤:

1)移出配料列并把它存储至专属表:

    每项配料为一行;

2)添加足以识别 drink_list表中每项饮品的配料 的列:

    在新的表中,我们需要识别各项配料分别属于哪项饮品;

    我们需要吧drink_list表中的某些列加入新表来建立连接;

图表中加入的连接:

    连接表中相符列使用箭头或线段;因为使用标准的模式schema图示符号,任何SQL开发者都可以理解;

    如上图中drink_info和burdening表之间的箭头连线;

现在我们的查询语句可以是:(模式参照上边图)

    先从drink_info中查询出符合条件的饮品的唯一记录标识,这里可以是id,并且id是主键,使用主键连接表是很好的方式;(如果建立连接的是原表中多个列的组合,那这里需要查出来的就是符合条件的多个列的值,要求的是这里的多个列可以唯一标识原表中的一条记录)

    使用查出来的id作为条件,查询出相应的配料;

这个虽然看起来复杂了,但是却呈现出如何以一张表的内容来提取另外一张表的内容;

这样burdenings表中的配料搭配唯一的drink_info的主键id,对应原drink_list表中的唯一一行;

连接你的表:

    我们需要使用独一无二的列来连接一切;

    还好我们已经开始规范化了,在drink_info中确实有独一无二的列——主键;

    把drink_info.id主键作为burdenings表的一列;

    利用这个外来的主键列 我们能知道某个配料属于哪个饮品——这种方式成为外键(foreign key);

外键:

    外键是表中的某一列,他引用到另一个表的主键;

    外键可能与它引用的主键名称不同;

    外键使用的主键也被称为父键(parent key);

    该主键所在的表又被称为父表(parent table);

    外键能用于确认一张表中的行与另外一张表中的行相对应;

    外键的值可以是NULL,即使主键值不可谓NULL;

    外键值不需要唯一——事实上,外键通常都没有唯一性;

外键为NULL,表示在父表中没有相符的主键;但我们可以确保外键包含有意义、已存储在父表中的值——通过约束(constraint)来实现;

外键约束:

    外键只能用CREATE或ALTER语句来指定;

    创建在结构内的键被称为约束(constraint);

引用完整性:

    referential integrity;

    插入外键列的值必须已经存在于父表的来源列中;

    你可以使用外键来引用父表中某个唯一的值;

外键不一定必须是父表的主键,但必须有唯一性;

创建/更新表时加入外键:

    CREATE和ALTER TABLE时都可以加入外键;

    我们尝试创建burdenings表,创建带有约束的外键drink_id,父表为drink_info;

相关SQL:

mysql> CREATE TABLE drink_info

    -> (

    -> id INT NOT NULL AUTO_INCREMENT,

    -> drink_name VARCHAR(30) NOT NULL,

    -> cost DEC(4,2) NOT NULL DEFAULT 20.00,

    -> meter INT NOT NULL DEFAULT 200,

    -> drink_des VARCHAR(100),

    -> PRIMARY KEY(id)

    -> );

Query OK, 0 rows affected (0.09 sec)



mysql> CREATE TABLE burdenings 

    -> (

    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,

    -> name VARCHAR(30),

    -> drink_id INT NOT NULL,

    -> CONSTRAINT drink_info_id_fk

    -> FOREIGN KEY(drink_id)

    -> REFERENCES drink_info(id)

    -> );

Query OK, 0 rows affected (0.09 sec)

分析:

1)创建外键和创建索引列一样:把外键列设定为INT与NOT NULL;

2)直接在主键后加入PRIMARY KEY是指定主键的另一种方式;

3)CONSTRAINT约束:指明来源表和键的名称,同时还说明它是一个外键(fk);

4)FOREIGN KEY指明外键列;

5)REFERENCES参考:指明外键来源,以及外键列在另外一张表中的名称;

MUL表示这一列可以存储多个相同的值,他也是追踪每个drink_id使用哪些配料的关键;

创建外键约束的优点:

    创建外键约束后,就只能插入存在于父表中的值,有助于加强两张表间的连接;

    外键约束能确保引用完整性,即约束能确保通过外键与另一张表中的某一行对应;

    如果我们试图删除主键表中的行或修改主键值,而这个主键是其他表的外键约束时,就会收到警告;

    要想删除的话,需要先移除外键行;

约束的种类:

    主键约束;我们现在看见的外键约束;创建列时使用关键字也被视为一种约束;

    还有一种检查约束CHECK,同于指定某个条件,列必须满足条件之后才能插入新值;(请自行查阅)

new day:

    我们来回顾下之前所做的,然后继续学习;


现在我们已经知道了可以将表拆分成多个表,并建立表之间连接,来优化我们的数据库结构和查询,这个过程有一定的模式可以遵循;

    我们上边的连接数据库表的模式并不一定是最好的;接下来让我们详细介绍下数据库的几种模式;

数据库模式(schema):

1.数据模式:一对一

    两个表之间的连接,对应两个表的记录都只有一条;

    如某位员工的信息表和员工的薪资单表就是一对一的关系,因为一个人只能有一个属于自己的工资表;

    在模式图中,一对一的连接线是单纯的实线,表示连接一件事物与另一件事物;

在“图示Final”中:

    drink_info和drink_des表之间的连接就是一对一的关系;

    一种饮品对应唯一的描述,一条描述信息对应唯一的饮品;

使用一对一的时机:

    我们并不会经常使用一对一的表;

    通常,把一对一的数据留在主表更合理,但有时候也适合把某些列拉出来;

    1)抽出数据或许能让你写出更快速的查询,比如你想知道你的工资单,到相应的表中查询是最好的方式;

    2)如果有列包含还不知道的值,可以单独存储这一列,以免主要表中出现NULL;

    3)希望某些数据不要太常被 访问;隔离这些数据即可管制访问次数;以员工表为例,他们的薪资信息最好存为另一张表;

    4)如果有一大块数据,例如BLOB类型,这段数据或许存为另一张表会更好;

一对一:父表只有一行与子表的某行相关;

2.数据模式:一对多

    表A中的某条记录在表B中可以对应多条记录;

    但表B中的每一条记录都只会对应到A表中的某一条记录;

在“图示Final”中:

    表drink_info的burdening_main列与burdenings表之间的连线就是一对多的关系;

    一条饮品只能有一种主配料,但是一种配料可以是多种饮品的主配料;

3.数据模式:多对多

    在“图示Final”中:

        drink_info和burdening_list 以及 drink_info和sale_list表之间都是多对多的关系,我们以前者为例进行说明;

        一个饮品对应多种配料,一种配料也可以用在多种饮品中;

一对一的连接线是线段;

一对多的连接线是带箭头的线段;

多对多的连接线是两侧都带箭头的线段;

4.数据模式:我们需要junction table

    对于多对多的关系,无论在哪个表中添加外键,都会造成表中出现重复的数据;

    我们一开始创建的带有外键的burdenings表,由于是多对多的关系,就会有这个问题,同一个配料会出现好多次;

连接表(junction table):

    直接连接两张表最后都会出现重复的数据;

    因此,我们需要在两个多对多的表之间架设一个中间桥梁,来存储所有的drink_id和burdening_id;

    这就是所谓的连接表junction table,用来存储两个相关表的主键;

    对应的“图示:Final”中的burdening_list表就是连接表;

我们单独再画一个图示如下:

分析这张图:

    可以看出,我们通过junction table将多对多的关系转化成来能个一对多的关系;

    即many-many -> one-many and one-many;

多对多:用junction table存储原始表的主键;

5.数据模式:再看多对多

    多对多的秘密:通常由两个一对多关系并利用junction table在中间连接构成;

    遇到多对多,一定要用中间表;避免造成重复组;

    改成此方式的优点会在下一章的联接(join)中有直观的体现;

Junction table有助于保持数据完整性:如必须删除某一饮品,这种连接会让我们无需分心注意配料表,只需管理中间表;

其次,由于避免了重复数据,在更新数据的时候,也会更加顺利;

数据模式:修正篇-规范化的更多级别:

1.第一范式,1NF:

    规则一:数据列只包含具有原子性的值;

    规则二:没有重复的数据组(含义交叉的数据列);

为了满足1NF,我们创建了带有外键的burdenings表(由于多对多的关系,会有重复的记录),如果不考虑主键id,其他两个列一起使用就能形成唯一的主键,唯一标识符这条记录;

1)组合键:

    由两列以上组合的键称为组合键;

    是由多个数据列构成的主键,组合个列后形成具有唯一性的键;

组合键体现出了一种关系,即表中的数据列本身对其他列也有关系;

2)函数依赖:

    functional dependency;

    当某列的数据必须随着另一列的数据的改变而改变的时候,表示第一列函数依赖于第二列;    

    比如,对于姓名以及姓名的缩写两列数据,姓名的缩写就 函数依赖于 姓名,因为缩写会随着姓名的变化而变化;

    这种依赖性在有些列之间比较常见;

3)速记符号:

    快速表示函数依赖的方式:T.x -> T.y,即在关系表T中,y列函数依赖于x列;(从右读到左就是解读依赖性的方式)

4)完全不依赖:

    比如人的信息表中,除了姓名和缩写外,还存储了其恋人所在的城市;

    恋人可以随时换一座城市,但这并不会影像表中其他的信息,因此表示恋人city的这一列是个完全不依赖的列;

5)部分函数依赖性:

    partial functional dependency;

    指非主键列依赖于组合主键的某一部分,但又不完全依赖于组合主键;

6)传递函数依赖性:

    这是对于非键列的相互关联的一种思考;

    如果改变任何非键列可能造成其他列的改变,即为传递依赖;

    传递函数依赖表现了任何非键列与另一个非键列有关联;

使用人造主键id可以有效避免部分函数依赖性的问题,这类id是新的专用于索引的字段,就表示没有字段依赖它;

在junction table中使用自然键(组合键)还是人造键(id)是个值得商榷的问题,本书主要采用单一人造主键,用于维持语法简单;

2.第二范式,2NF:

    在表中新增主键列有助于达成2NF;因为第二范式重点是 表的主键如何与其他数据产生关系;

    只要所有列都是主键的一部分或者表中有唯一主键列符合1NF的表 也会符合2NF;后者也是指定一个AUTO_INCREMENT的ID列的好理由;

    规则一:符合1NF;

    规则二:没有部分函数依赖性;

3.第三范式,3NF:

    由于加上了人工主键(artificial primary key)符合2NF并不困难,任何具有人工主键且没有主键的表都符合2NF;

    规则一:符合2NF;

    规则二:没有传递函数依赖性;

        传递函数依赖性表示的是任何非键列与相同表中其他非键列有联系;

        如果改变任何非键列可能造成其他任何非键列的改变,就是具有传递函数依赖性;

    类似之前的例子,一个人的信息中存储了 它恋人的一系列信息,这一系列信息之间就很大可能存在传递函数依赖性;

最终的修改:

    现在再来看我们的原始表:

    以及,我们修改之后的模式图:

    仔细思考下,看看我们新的数据库结构是否经得住:数据库模式、数据库规范化:1NF、2NF、3NF的考验;

总结:

    数据库模式是描述 表与表之间关系的;

    数据库规范是描述 列与列之间关系的;

    表之间通过外键建立连接,添加相应的外键约束;

1.Scheme:数据库模式,描述数据库中的数据、其他相关对象,以及这些对象相互连接的方式;

2.One-toOne relationship:一对一关系,父表中的一行记录至于子表中的一行记录相关联;

3.One-to_Many relationship:一对多关系,一张表中的一行记录可能与另一张表中的多行相关联,但最后一张表中的任一行记录只会与前一张表中的一行记录相关联;

4.Many-to-Many relationship:多对多关系,两个通过junction table连接的表,让一张表中的多行记录能与另一张表中的多行记录相关联,反之亦然;

5.First Normal Form(1NF):第一范式,列中只包含原子性数据,而且列内没有重复的数据组;

6.Transitive functional dependency:传递函数依赖,指任何非键列依赖于另一个非键列;

7.Second normal form(2NF):第二范式,先符合1NF,同时不能包含部分函数依赖性;

8.Third normal form(3NF):第三范式,先符合2NF,同时不可包含可传递函数依赖;

9.Foreign key:外键,引用其他表主键的列;

10.Composite key:组合键,由多个列构成的主键,这些列需形成唯一的键值;

值得注意的是,本章并没有介绍更多的SQL语句,在下一章,我们会看到,使用数据库模式和遵循数据库规范会给我们的查询带来哪些好处。

猜你喜欢

转载自blog.csdn.net/baby_hua/article/details/82497958
今日推荐