深入浅出SQL(6)-聪明的表设计

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

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

聪明的表设计:为什么要规范化

本章多是理论,请注意理解;

    我们到目前为止创建的表,都没有经过仔细考虑;随着数据的越来越多,我们需要考虑的更多,好让现在的WHERE子句简单一点;我们需要让表更正常、更规范;

两张鱼的表:

表一:

鱼的学名——鱼的俗名——重量——捕获地点

common          species       weight    location

草甘                草鱼            500g        查干湖,吉林

扫描二维码关注公众号,回复: 3049417 查看本文章

表二:

捕获者的姓名——鱼的俗名——重量——捕获水域——捕获地区——捕获日期

fitst_name   last_name   common  weight   location    state                date

hua              Q                 草甘        500g      查干湖        吉林              6/2/2018

表一缺乏捕获者的信息:

    但是一名鱼类研究者,在查找表的时候,上述4个字段才是他关注的;

表二的信息相对更细致:

    作为一名杂志撰稿人,了解鱼的这些信息还是很有必要的;

对于上述两张表针对吉林捕捞的鱼的记录编写SQL:

    SELECT * FROM biao1 WHERE location LIKE '%吉林';

    SELECT * FROM biao2 WHERE state = '吉林’;

我们看到:

对于表一:我们必须使用LIKE关键字取得查询结果;

对于表二:我们直接通过一个地区字段作为查询条件;

两种方式对于不同的使用表的人,都可以满足各自的需求,但是:

    如果你的表中包含复杂信息的话,LIKE搜索精确数据的能力就显得不足了;

    查询越简单越好;

是不是可以使用多个列存储地址呢,一列存储详细的地址,一列存储简单的地址:

    这看似是个不错的方法,但是有两个问题:

    首先,这回占用更多的空间;

    其次,由于两列的数据有重叠的地方,这代表每次修改数据的时候都要在UPDATE语句中多加一个子句;

使用数据的方式 和 表的设计方式 是相互影响的;

    良好的表结构设计可以让数据的处理方式更简单;

SQL是一种用于关系数据库的语言,那‘关系’是什么?

表都是基于关系的:

    SQL因关系数据库管理系统RDBMS而出名;

    对于设计表的人而言,就是要设计一个杀手级的表,必须考虑列彼此之间的关系,多个列是如何一起描述某项事物的;

难点在于使用列描述事物,好让取得数据更加方便;

设计方向取决于我们对表的需求;

创建表步骤:

1.挑出事物,挑出你希望表描述的某样事物;

2.列出一份关于那样事物的信息列表,这些信息都是使用表时的必要信息;    

    如何使用这张表;

3.使用信息列表,把关于那样事物的综合信息拆分成小块信息,以便用于组织表;

    轻松查询这张表;

这里的疑问:

    鱼类研究者的需求:通过查找鱼的学名或俗名得知雨的重量和地点;

    杂志撰稿人的需求:通说俗名,得知捕获者的姓名、重量、捕获水域、捕获地区、捕获日期;

对于不同的使用者来讲,上述的表设计都有各自的合理性;

    我们把‘关于那样事物的综合信息拆分成小块信息’的细致程度,用原子性来描述;

    个人认为,关于数据原子性的界定,取决于我们实际的需求;

    

原子性数据:

    原子(atom)是一块无法或不应该分割的信息;

    对于数据而言,数据的原子性,就表示它已经被分割为最小块;已经不能或不应该再被分割;

比如对一个送货员来说,一列存储街道名门牌号的列就已经具有了原子性;如果将数据拆分成街道名称+门牌号两列,查询反而会更长;

原子性和你的表:

如何理解表中内容:

1.表在描述什么事物;

2.以何种方式使用表能更好取得描述的事物;

3.列是否包含原子性数据,让查询更简短准确;

让数据具有原子性:

    并不是将数据尽可能的分割的非常小,而是要分割成创建有效率的表所需的最小片段

    别把数据切割的超出必要;如果不需要额外增加列,就别因为可以增加而增加;

    原子性有助于确保表内容的准确性,也能使查询更有效率,因为查询会因原子性而更容易设计;

原子性数据的正式规则:

规则一:

    具有原子性数据的列中不会有多个类型相同的值;

    比如,喜欢的运动字段sport下,值为足球、篮球……    

    保持数据的简洁性,否则如果查询具体某个兴趣将是一场噩梦;

first_name——last_name——intrests

hua                    Q                    football,backetball

规则二:

    具有原子性数据的表中不会有多个存储同类数据的列;

    列的划分不应该有涵义上的交叉;

teacher——student1——student2——student3

Yuan            hua1            hua2                hua3

规范化的原因:

    让表规范化(normalization)表示表遵循某些标准,即使是刚接触的新设计师也能理解;

让数据具有原子性是创建一个规范化表的第一步;

规范化表的优点:

1.规范化表中没有重复的数据,可以减少数据库的大小;

2.因为查找的数据较少,你的查询会更为快捷;

达成1NF的半路上:

    具有原子性的数据只让我们的表现规范了一半;

    完全的规范化表示我们处于第一范式(First Normal Form)的状态,简称1NF;

1NF:

1)每行数据行必须包含具有原子性的值;

2)每个数据行必须有独一无二的识别项,称为“主键”(Primary Key);

所以要让表完整的规范,需要为每条记录加上主键;

主键规则:

    成为主键的列必须在创建表的时候一并设置;

    

我们先来看看主键是什么;

    主键是表中的某个列,他可以让每一条记录成为唯一的;

    主键用于独一无二地识别出每条记录;

    即,主键中的数据不能重复;

比如对于个人信息,可以使用身份证号进行标示,但是你可以保证数据库绝对安全吗?否则如果数据被偷走,你的客户资料也就一并曝光了;

主键的特征:

1)主键不可以为NULL;因为NULL值不唯一

2)插入新纪录时必须指定主键值;否则主键就有可能是NULL;

3)主键必须简洁;只是一个独一无二的数据,不该有其他内容;

4)主键值不可以被修改;意外的输入值可能破坏主键的唯一性;

最佳主键可能是新的主键:

    最佳方式是另外创建一个包含唯一性主键的列;可以称之为ID;

如下表结构:

id——last_name——first_name

1        hua                    Q1

2        hua                    Q2

ID列让表中记录具有了唯一性;这张表也因此处于第一范式的状态;

人造(synthetic)主键:使用虚构的ID列作为主键;

自然(natural)主键:使用人的身份证信息作为主键;

我们后续会继续讨论:

    第二范式、第三范式,每一级范式都会增加更严格、更精确的规则;

    在表设计的过程中,有些字段可能没有原子性,是因为我们并不需要那样做,比如使用关键词进行评论,评论字段中的数据无原子性,但是它也并不需要原子化;

朝规范化前进:

    让数据具有原子性;

    加上主键:主键的创建通常会在编写CREATE TABLE时进行;

既然这样,我们是不是可以这样修改我们之前不完美的表:

1)查出所有数据;

2)创建一张规范化的表;

3)把旧数据INSERT INTO到新表,并改变每一行以符合新表的结构;

4)DROP TABLE掉就表;

但是,已经装满数据的表,我们并不希望DROP TABLE之后,再重新输入所有数据……

所以,我们需要对一张不具原子性,没有主键的表进行更新,我们介绍一个新的命令:

但在修理已有表的方式前,我们来看看如何重新设计一张更加标准的表;

我们设计的CREATE TABLE:

    我们回顾下第一章创建的表my_contacts:

profession、location和seeking这些列能不能设计的更加具有原子性呢?

同时我们也发现,这张表没有主键;

我们来看下表结构:

mysql> DESC my_contacts;

+------------+--------------+------+-----+---------+-------+

| Field      | Type         | Null | Key | Default | Extra |

+------------+--------------+------+-----+---------+-------+

| last_name  | varchar(30)  | YES  |     | NULL    |       |

| first_name | varchar(20)  | YES  |     | NULL    |       |

| emal       | varchar(50)  | YES  |     | NULL    |       |

| gender     | char(1)      | YES  |     | NULL    |       |

| birthdy    | date         | YES  |     | NULL    |       |

| profession | varchar(50)  | YES  |     | NULL    |       |

| location   | varchar(20)  | YES  |     | NULL    |       |

| status     | varchar(100) | YES  |     | NULL    |       |

| seeking    | varchar(100) | YES  |     | NULL    |       |

+------------+--------------+------+-----+---------+-------+

9 rows in set (0.03 sec)

设置表时所用的SQL:

    我们想看的不是表中的字段,而是想看CREATE的代码,这样才能知道表一开始是如何设计的;

    使用命令SHOW CREATE TABLE table; 将返回可以重建表但没有数据的CREATE TABLE语句;

    这样一来,就可以随时查看表的创建方式;

mysql> SHOW CREATE TABLE my_contacts;

+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

| Table       | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                    |

+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

| my_contacts | CREATE TABLE `my_contacts` (

  `last_name` varchar(30) DEFAULT NULL,

  `first_name` varchar(20) DEFAULT NULL,

  `emal` varchar(50) DEFAULT NULL,

  `gender` char(1) DEFAULT NULL,

  `birthdy` date DEFAULT NULL,

  `profession` varchar(50) DEFAULT NULL,

  `location` varchar(20) DEFAULT NULL,

  `status` varchar(100) DEFAULT NULL,

  `seeking` varchar(100) DEFAULT NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |

+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

1 row in set (0.00 sec)

可以看到:

    列名和表明前后的反撇号会在运行该命令的时候出现;(有关反撇号的作用我们一会会有说明)

    除非我们指定,否则数据列都会假设所有数据的默认值都是NULL;

    结束括号之后的文字,说明了数据如何存储,以及使用的字符集(现在是默认即可);

我们要基于之前的创建表的命令,创建一张规范化的新表:

    除非已经删除了旧表,否则我们需要一个新的表名;

    只靠复制粘贴是不够的,我们还需要做更多的事;

加上主键的CREATE TABLE:

    删除反撇号和最后一行;

    添加contact_id列并设置为NOT NULL;

    在列列表的最下方添加一行PRIMARY KEY,把新添加的contact_id设定为主键;(不为NULL,独一无二)

    设置主键的语法:PRIMARY KEY(contact_id),即把主键列的名称放在括号中;

我们新建一张表:flower_contacts

mysql> CREATE TABLE flower_contacts (

    ->     contact_id INT NOT NULL,

    ->     last_name varchar(30) DEFAULT NULL,

    ->     first_name varchar(20) DEFAULT NULL,

    ->     emal varchar(50) DEFAULT NULL,

    ->     gender char(1) DEFAULT NULL,

    ->     birthdy date DEFAULT NULL,

    ->     profession varchar(50) DEFAULT NULL,

    ->     location varchar(20) DEFAULT NULL,

    ->     status varchar(100) DEFAULT NULL,

    ->     seeking varchar(100) DEFAULT NULL,

    ->     PRIMARY KEY (contact_id)

    -> );

Query OK, 0 rows affected (0.11 sec)

相应的:

mysql> DESC flower_contacts;

+------------+--------------+------+-----+---------+-------+

| Field      | Type         | Null | Key | Default | Extra |

+------------+--------------+------+-----+---------+-------+

| contact_id | int(11)      | NO   | PRI | NULL    |       |

| last_name  | varchar(30)  | YES  |     | NULL    |       |

| first_name | varchar(20)  | YES  |     | NULL    |       |

| emal       | varchar(50)  | YES  |     | NULL    |       |

| gender     | char(1)      | YES  |     | NULL    |       |

| birthdy    | date         | YES  |     | NULL    |       |

| profession | varchar(50)  | YES  |     | NULL    |       |

| location   | varchar(20)  | YES  |     | NULL    |       |

| status     | varchar(100) | YES  |     | NULL    |       |

| seeking    | varchar(100) | YES  |     | NULL    |       |

+------------+--------------+------+-----+---------+-------+

10 rows in set (0.00 sec)

这里没有蠢问题:

1.我们已经知道每次插入新纪录都必须为主键列赋新值,有没有更好的办法:

    有两种方式;一种是直接利用原来就有唯一性的数据作为主键;这种可能不太容易实现;

    比较简单的一种是使用全新的主键列,用它来存储独一无二的值;

    每当使用主键时,我们可以要求SQL软件自动为主键填入新的值;

2.SHOW命令的使用场景:

    显示表中列及其数据类型:SHOW COLUMNS FROM table;

    提供重建数据库的语句:SHOW CREATE DATABASE database;        

    显示任何编了索引的列以及索引类型:SHOW INDEX FROM table;

    目前为止,我们唯一看到的索引就是主键;

    还有一个很有用的命令:SHOW WARNINGS;

        如果你从控制台收到SQL命令造成的错误信息,键入这个命令就可取得确切的警告内容;

3.反撇号的作用:

    反撇号的存在是因为RDBMS有时无法分辨列名;

    如果在列名前后加上该字符就能以SQL保留字作为列名;`select`就是一个有效的列名;

4.用保留字作为列名的问题:

    这是一个很糟糕的主意,这回让你的查询很混乱,也会引入每次都输入反撇号的麻烦;

    而且select也不是一个好列名,无法说明该列包含的数据;

1、2、3……自动递增“关键字AUTO_INCREMENT

    为contact_id列加上关键字AUTO_INCREMENT,就可以让SQL软件自动为该列填入数据,第一行填入1,后续递增;

    有些RDBMS类似功能的关键字可能是INDEX;

我们看下文档对该AUTO_INCREMENT的说明:

mysql> help auto_increment

Name: 'AUTO_INCREMENT'

Description:

The AUTO_INCREMENT attribute can be used to generate a unique identity

for new rows:

URL: http://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html

Examples:

CREATE TABLE animals (

     id MEDIUMINT NOT NULL AUTO_INCREMENT,

     name CHAR(30) NOT NULL,

     PRIMARY KEY (id)

);

INSERT INTO animals (name) VALUES

    ('dog'),('cat'),('penguin'),

    ('lax'),('whale'),('ostrich');

SELECT * FROM animals;

我们运行了这个示例:

mysql> SELECT * FROM animals;

+----+---------+

| id | name    |

+----+---------+

|  1 | dog     |

|  2 | cat     |

|  3 | penguin |

|  4 | lax     |

|  5 | whale   |

|  6 | ostrich |

+----+---------+

6 rows in set (0.00 sec)

现在,我们修改创建flower_contacts表的SQL:

DROP TABLE flower_contacts;

CREATE TABLE flower_contacts (

    contact_id INT NOT NULL AUTO_INCREMENT,

    last_name varchar(30) DEFAULT NULL,

    first_name varchar(20) DEFAULT NULL,

    emal varchar(50) DEFAULT NULL,

    gender char(1) DEFAULT NULL,

    birthdy date DEFAULT NULL,

    profession varchar(50) DEFAULT NULL,

    location varchar(20) DEFAULT NULL,

    status varchar(100) DEFAULT NULL,

    seeking varchar(100) DEFAULT NULL,

    PRIMARY KEY (contact_id)

);

现在的表是这样的:

mysql> DESC flower_contacts;

+------------+--------------+------+-----+---------+----------------+

| Field      | Type         | Null | Key | Default | Extra          |

+------------+--------------+------+-----+---------+----------------+

| contact_id | int(11)      | NO   | PRI | NULL    | auto_increment |

| last_name  | varchar(30)  | YES  |     | NULL    |                |

| first_name | varchar(20)  | YES  |     | NULL    |                |

| emal       | varchar(50)  | YES  |     | NULL    |                |

| gender     | char(1)      | YES  |     | NULL    |                |

| birthdy    | date         | YES  |     | NULL    |                |

| profession | varchar(50)  | YES  |     | NULL    |                |

| location   | varchar(20)  | YES  |     | NULL    |                |

| status     | varchar(100) | YES  |     | NULL    |                |

| seeking    | varchar(100) | YES  |     | NULL    |                |

+------------+--------------+------+-----+---------+----------------+

10 rows in set (0.00 sec)

我们尝试操作这张表,来看下AUTO_INCREMENT是如何运作的;

我们先来看几条SQL:

INSERT INTO flower_contacts

(last_name,seeking)

VALUES

('hua1','working,girlfriend'),

('hua2','working,girlfriend'),

('hua3','working,girlfriend’);



INSERT INTO flower_contacts

(contact_id,last_name,seeking)

VALUES

(5,'hua4','working,girlfriend'),

(7,'hua5','working,girlfriend');



INSERT INTO flower_contacts

(last_name,seeking)

VALUES

('hua6','working,girlfriend'),

('hua7','working,girlfriend'),

('hua8','working,girlfriend’);



INSERT INTO flower_contacts

(contact_id,last_name,seeking)

VALUES

(10,'hua9','working,girlfriend');



INSERT INTO flower_contacts

(contact_id,last_name,seeking)

VALUES

(NULL,'hua10','working,girlfriend');



DELETE FROM flower_contacts WHERE contact_id = 11;



INSERT INTO flower_contacts

(contact_id,last_name,seeking)

VALUES

(NULL,'hua10','working,girlfriend');



SELECT contact_id,last_name,seeking FROM flower_contacts;

最终查看表flower_contacts的内容如下:

mysql> SELECT contact_id,last_name,seeking FROM flower_contacts;

+------------+-----------+--------------------+

| contact_id | last_name | seeking            |

+------------+-----------+--------------------+

|          1 | hua1      | working,girlfriend |

|          2 | hua2      | working,girlfriend |

|          3 | hua3      | working,girlfriend |

|          5 | hua4      | working,girlfriend |

|          7 | hua5      | working,girlfriend |

|          8 | hua6      | working,girlfriend |

|          9 | hua7      | working,girlfriend |

|         10 | hua8      | working,girlfriend |

|         12 | hua10     | working,girlfriend |

+------------+-----------+--------------------+

9 rows in set (0.00 sec)

分析:

hua1、2、3 我们是按照文档例子的方式插入的,运行正常;

hua4、5两条则是我们指定的contact_id进行的插入,运行正常:

hua6、7、8虽然也正常插入了,但是我们看到,自增的数值是基于当前最后一条主键的值进行的自增,也就是7之后的8;

hua9的插入我们再一次指定了contact_id为10,但这一次失败了:

mysql> INSERT INTO flower_contacts 

    -> (contact_id,last_name,seeking)

    -> VALUES

    -> (10,'hua6','working,girlfriend');

ERROR 1062 (23000): Duplicate entry '10' for key 'PRIMARY'

mysql> SHOW WARNINGS;

+-------+------+----------------------------------------+

| Level | Code | Message                                |

+-------+------+----------------------------------------+

| Error | 1062 | Duplicate entry '10' for key 'PRIMARY' |

+-------+------+----------------------------------------+

1 row in set (0.00 sec)

因为我们插入的主键10重复了;

hua10的插入很有意思,之所以成功,是因为AUTO_INCREMENT会忽略NULL;(没有它的话就报错了)

    很明显hua10的contact_id=11;

紧接着,我们删除了contact_id = 11的这条记录,我们继续将hua10这条记录插入,此时hua10的contact_id则是基于11的增加,值为12,虽然11这条记录已经删除了;

可以得出的结论是:

    自增的数值是基于当前最后一条主键的值进行的自增,即便他已经被删除了;

    只要插入过的记录就会引起主键的AUTO_INCREMENT;

当然,contact_id=11,我们还可以继续使用:

INSERT INTO flower_contacts

(contact_id,last_name,seeking)

VALUES

(11,'hua10','working,girlfriend');

最终,表的内容如下:

mysql> SELECT contact_id,last_name,seeking FROM flower_contacts;

+------------+-----------+--------------------+

| contact_id | last_name | seeking            |

+------------+-----------+--------------------+

|          1 | hua1      | working,girlfriend |

|          2 | hua2      | working,girlfriend |

|          3 | hua3      | working,girlfriend |

|          5 | hua4      | working,girlfriend |

|          7 | hua5      | working,girlfriend |

|          8 | hua6      | working,girlfriend |

|          9 | hua7      | working,girlfriend |

|         10 | hua8      | working,girlfriend |

|         11 | hua10     | working,girlfriend |

|         12 | hua10     | working,girlfriend |

+------------+-----------+--------------------+

10 rows in set (0.00 sec)

当然,这是我们重新,开始建表实现了规范化的设计;现在我们要针对my_cotacts表,介绍我们的新命令了;

ALTER语句:

    改用ALERT语句,我们不用再重新开始了;(注意!!!!!不是alert,而是alter)

    带有数据的表不应该经历被丢弃、卸除、重建的步骤;我们需要的是改变现有的表;

    ALTER还会在下一章进一步介绍,我们先看看他的用法;

   

首先请回看一下前边提到的my_contacts表及其内容;

为现有的表添加主键:

    ALTER TABLE 并添加PRIMARY KEY;

ALTER TABLE my_contacts

ADD COLUMN contact_id INT NOT NULL AUTO_INCREMENT FIRST,

ADD PRIMARY KEY (contact_id);

FIRST:

    是要求软件吧新列放在最前面,这是一个可选的关键字;

    把主键放在最前面是一个很好的习惯;

ADD COLUMN:

    添加一个新列;

我们看到,有4条记录添加了新列;同时我们也看到,主键的值已经全部都填上了;

总结:

ATOMIC DATA:数据原子性,列中数据已拆成查询所需的最小单位;

ATOMIC DATA规则一:

    具有原子性表示在同一列中不会存在多个类型相同的数据;

ATOMIC DATA规则二:

    具有原子性表示不会用多个列来存储类型相同的数据;

SHOW CREATE TABLE:呈现创建现有表的正确语法;

PRIMARY KEY:主键,一个或一组能识别出唯一数据行的列;

FIRST NORMAL FORM(1NF):

    第一范式,每个数据行均需包含原子性数据值,而且每个数据行均需有唯一的识别方法;

AUTO_INCREMENT:

    列声明时若使用该关键字,则每次执行INSERT命令插入数据时,都会自动给列赋予唯一的递增整数值;

猜你喜欢

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