该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
联接与多张表的操作:不能单独存在吗?
数据库的多张表是好事,但我们也需要学习一些操控多张表的新技术与工具;
混乱状态与多张表一起出现,所以你需要别名来让表更加清楚;
联接则有助于联系表,取得分布在个张表里的内容;
我们仍然基于饮品的表来做测试,只不过我们需要重新填充下表中的数据,以满足我们的测试需求:
原始表:drink_list
该原始表中:
显示了饮品的种类及信息;
显示了饮品的销售记录;(看样子混合型的饮料销售的并不好)
表中每一列中都有很多重复的数据;
在表中检索saler的值,但要把值过滤成不重复的形式:
使用WHERE子句过滤掉NULL的值;使用GROUP BY把重复数据合成一个组,然后使用ORDER BY整理出按照字母排列的列表;
注意,ORDER BY永远都要出现在最后;
mysql> SELECT saler FROM drink_list
-> WHERE NOT saler IS NULL
-> GROUP BY saler
-> ORDER BY saler;
+-------+
| saler |
+-------+
| A.N |
| B.N |
| P.N |
+-------+
3 rows in set (0.00 sec)
类似的我们还可以检索出表中出现的饮料;
但是这并适合检索burdening列:
我们无法只靠单一的SELECT来取出所有兴趣,因为它是如下这个样子的;
mysql> SELECT burdening FROM drink_list
-> WHERE NOT burdening IS NULL
-> GROUP BY burdening
-> ORDER BY burdening;
+--------------------+
| burdening |
+--------------------+
| apple,banana,water |
| apple,peach,water |
| apple,plum,water |
| apple,water |
| banana,water |
| peach,water |
| plum,water |
+--------------------+
7 rows in set (0.00 sec)
这部分内容,我们在规范化一章中已经做了阐述,如果你还不明白,请移步回顾下;
下面的模式图表现了我们最终的多张表结构:
把配料放在表burdenings中单独的一列:
很明显我们不应该一条一条的查询和录入;
我们可以交给SQL代劳;
在这之前,我们先来看看如果将原始表drink_list的burdening列 直接拆分成当前表的多个列,该如何实现:
这并不是符合规范化要求的方式,但却值得我们一试;
我们新增3列burdening1、burdening2、burdening3,来看看SQL如何来写;
UPDATE所有配料列:
1)新增列:(顺便复习下删除列)
mysql> ALTER TABLE drink_list
-> DROP COLUMN burdening1,
-> DROP COLUMN burdening2,
-> DROP COLUMN burdening3;
mysql> ALTER TABLE drink_list
-> ADD COLUMN burdening1 VARCHAR(50) NOT NULL AFTER burdening_main,
-> ADD COLUMN burdening2 VARCHAR(50) NOT NULL AFTER burdening1,
-> ADD COLUMN burdening3 VARCHAR(50) NOT NULL AFTER burdening2;
2)更新列:
mysql> UPDATE drink_list
-> SET
-> burdening1 = SUBSTRING_INDEX(burdening,',',1),
-> burdening = SUBSTRING(burdening,LENGTH(burdening1) + 2),
-> burdening2 = SUBSTRING_INDEX(burdening,',',1),
-> burdening = SUBSTRING(burdening,LENGTH(burdening2) + 2),
-> burdening3 = burdening;
如果忘记了SUBSTRING如何使用,可以使用HELP命令,进行查看;
把配料放在表burdenings中单独的一列:
现在,配料存储在了多个列,想要查询它们并不容易,需要写3个SELECT查询;
我们还需要将配料存储在新表的一列中,所以,我们需要能够接受SELECT语句并把内容直接填入新表的工具;
上述示例作为一个练习,同时也作为引出接受SELECT语句并把内容直接填入新表的工具的例子,我们仍然需要使用最原始的表,即没有添加过新列的表;
SELECT语句并把内容直接填入新表:
接下来,我们尝试创建模式图中一对一关系的表drink_des,并填满内容,来实践上边提到的工具;
我们有三种方式;
结合CREATE、SELECT、INSERT等语句实现的方式;
1.CREATE TABLE,然后利用SELECT进行INSERT;
mysql> CREATE TABLE drink_des
-> (
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> drink_des VARCHAR(100)
-> );
mysql> INSERT INTO drink_des
-> (drink_des)
-> SELECT drink_des FROM drink_list;
多列数据举例:
mysql> CREATE TABLE drink_des
-> (
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> drink_des VARCHAR(100),
-> meter INT
-> );
mysql> INSERT INTO drink_des
-> (drink_des,meter)
-> SELECT drink_des,meter FROM drink_list;
2.利用SELECT进行CREATE TABLE,然后ALTER以添加主键;
mysql> CREATE TABLE drink_des AS
-> SELECT drink_des FROM drink_list;
mysql> ALTER TABLE drink_des
-> ADD COLUMN id INT NOT NULL AUTO_INCREMENT FIRST,
-> ADD PRIMARY KEY(id);
多列数据举例:
mysql> CREATE TABLE drink_des AS
-> SELECT drink_des,meter FROM drink_list;
mysql> ALTER TABLE drink_des
-> ADD COLUMN id INT NOT NULL AUTO_INCREMENT FIRST,
-> ADD PRIMARY KEY(id);
3.CREATE TABLE的同时,设置主键并利用SELECT 填入数据;
mysql> CREATE TABLE drink_des
-> (
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> drink_des VARCHAR(100)
-> )AS
-> SELECT drink_des FROM drink_list;
多列数据举例:
mysql> CREATE TABLE drink_des
-> (
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> drink_des VARCHAR(100),
-> meter INT
-> )AS
-> SELECT drink_des,meter FROM drink_list;
这种情况SQL具有AUTO_INCREMENT功能,所以剩余的其他字段,就是SELECT出来的字段需要填入的地方;
此时的表drink_des内容如下:
单列:(部分行)
| id | drink_des |
+----+------------------------------------+
| 1 | apple peach mixture also is good! |
多列:(部分行)
| id | drink_des | meter |
+----+------------------------------------+-------+
| 1 | apple peach mixture also is good! | 200 |
如果对填入的数据有没有重复、按字母排序的要求,可以对SELECT语句追加GROUP BY 和 ORDER BY子句来实现;
注意,分组的SELECT字段,除了分组使用的以外,其他必须是基于分组多条数据的操作;
值得注意的是:
虽然我们已经知道对于burdening这样的非原子性的列需要进行规范化,但很遗憾到目前为止,我们还没有学习到如何使用SQL进行最为便捷的操作,实现将burdening的各个配料,无重复、排好序的内容填入新的表;随着继续深入学习,相信很快会有答案;
一个新的关键字AS:
AS能把SELECT的查询结果填入新表中;
上述示例中是把原表的SELECT查询结果,存入新建的drink_des表中;
如果新表指定的列名与AS后的SELECT的查询列不一致/不指定,则会新创建相应的查询列名的列;
除此之外,AS在设置别名时也有用;
好多好多drink_des——引入别名:
一个SQL中如此多的drink_des,很容易让人迷惑;
SQL提供了别名功能,以免各种名称混淆;
别名(alias):
SQL允许我们暂时对表和列赋予新名称;
列的别名:
在查询中首次使用原始列名的地方后接AS并设定要采用的别名;
告诉软件,从现在开始以另一个名称引用查询表的列,这样让查询更容易被理解;
我们尝试将原表的饮品销售信息,存储到新表sale_info中:
表结构参照模式图;
1)不使用别名:
mysql> CREATE TABLE sale_info
-> (
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> saler VARCHAR(20),
-> sale_date DATE
-> )AS
-> SELECT saler, sale_date FROM drink_list
-> WHERE NOT saler IS NULL
-> ORDER BY sale_date;
| id | saler | sale_date |
+----+-------+------------+
| 1 | A.N | 2018-09-01 |
2)应用别名:
mysql> CREATE TABLE sale_info
-> (
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> si_er VARCHAR(20),
-> si_date DATE
-> )AS
-> SELECT saler AS si_er, sale_date AS si_date FROM drink_list
-> WHERE NOT saler IS NULL
-> ORDER BY si_date;
| id | si_er | si_date |
+----+-------+------------+
| 1 | A.N | 2018-09-01 |
值得注意的是:
这里的WHERE子句并不能直接使用列的别名,这是由于语句的执行顺序决定的,WHERE执行时,别名尚未生效;
可以简单的理解为,WHERE子句中的列名只能是FROM后的表中存在的列名,无论这张表是稳定的表还是临时的表;
表的别名:
创建表的别名的方式与创建列别名的方式几乎一致,在查询首次出现表名的地方后接AS并设定别名;
另外,也可以省略AS,只要别名紧跟在原始表名或列名后,就能直接设定别名;
即将介绍联接的内容,在那里我们会使用到表的别名;
我们暂时将规范化的部分搁置一下,先来看看联接的使用;
联接(join):
联接都会生成一张临时的中间表;
研究一下使用联接的时机,以及具体使用哪种联接;
我们举两张简单的表来说明问题:
mysql> SELECT * FROM toys;
+--------+----------+
| toy_id | toy |
+--------+----------+
| 1 | wan ju 1 |
| 2 | wan ju 2 |
| 3 | wan ju 3 |
| 4 | wan ju 4 |
| 5 | wan ju 5 |
+--------+----------+
mysql> SELECT * FROM boys;
+--------+-------+
| boy_id | boy |
+--------+-------+
| 1 | boy 1 |
| 2 | boy 2 |
| 3 | boy 3 |
| 4 | boy 4 |
| 5 | boy 5 |
+--------+-------+
交叉连接(cross join):
最简单的联接,他有很多不同的名称,如笛卡尔积、交叉积、“没有联接”,我们称他为交叉连接;
下面是同时查询玩具列和男孩列,他的查询结果就是交叉联接的;
上一章我们提过速记符号’.’,点前是表名,点后是表内的列名;
这里我们使用了别名代替了表的全名;
mysql> SELECT t.toy , b.boy
-> FROM
-> toys AS t
-> CROSS JOIN
-> boys AS b;
+----------+-------+
| toy | boy |
+----------+-------+
| wan ju 1 | boy 1 |
| wan ju 2 | boy 1 |
| wan ju 3 | boy 1 |
| wan ju 4 | boy 1 |
| wan ju 5 | boy 1 |
| wan ju 1 | boy 2 |
| wan ju 2 | boy 2 |
| wan ju 3 | boy 2 |
| wan ju 4 | boy 2 |
| wan ju 5 | boy 2 |
| wan ju 1 | boy 3 |
| wan ju 2 | boy 3 |
| wan ju 3 | boy 3 |
| wan ju 4 | boy 3 |
| wan ju 5 | boy 3 |
| wan ju 1 | boy 4 |
| wan ju 2 | boy 4 |
| wan ju 3 | boy 4 |
| wan ju 4 | boy 4 |
| wan ju 5 | boy 4 |
| wan ju 1 | boy 5 |
| wan ju 2 | boy 5 |
| wan ju 3 | boy 5 |
| wan ju 4 | boy 5 |
| wan ju 5 | boy 5 |
+----------+-------+
25 rows in set (0.00 sec)
我们看到,查询结果被联接成一张新的结果表;
交叉联接把第一张表的每个值都与第二张表的每个值配成对;
CROSS JOIN返回两张表的每一行相乘的结果;
交叉联接的作用:
知道交叉联接的存在有助于找到修正联接的方式;
交叉联接有时可用于检测RDBMS软件及其配置的运行速度;
运行交叉联接所需的时间可以轻松地检测与比较出速度慢的查询;
执行如下SQL还是会返回25条数据,但会返回4列数据:
mysql> SELECT *
-> FROM
-> toys AS t
-> CROSS JOIN
-> boys AS b;
+--------+----------+--------+-------+
| toy_id | toy | boy_id | boy |
+--------+----------+--------+-------+
| 1 | wan ju 1 | 1 | boy 1 |
值得注意的是:
CROSS JOIN可以省略不写,只用逗号代替;
交叉联接是内连接的一种;内连接基本上就是通过查询中的条件移除了某些结果数据后的交叉联接;
我们先基于boys和toys表,建立两个表之间的连接,添加外键;
mysql> ALTER TABLE boys
-> ADD COLUMN toy_id INT;
mysql> UPDATE TABLE boys
-> SET toy_id = 2 WHERE boy_id = 1;
……
mysql> ALTER TABLE boys
-> MODIFY COLUMN toy_id INT NOT NULL,
-> ADD CONSTRAINT toys_toy_id_fk
-> FOREIGN KEY(toy_id)
-> REFERENCES toys(toy_id);
产看现在的表boys:
mysql> DESC boys;
+--------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+----------------+
| boy_id | int(11) | NO | PRI | NULL | auto_increment |
| boy | varchar(30) | YES | | NULL | |
| toy_id | int(11) | NO | MUL | NULL | |
+--------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
mysql> SELECT * FROM boys;
+--------+-------+--------+
| boy_id | boy | toy_id |
+--------+-------+--------+
| 1 | boy 1 | 2 |
| 2 | boy 2 | 4 |
| 3 | boy 3 | 5 |
| 4 | boy 4 | 1 |
| 5 | boy 5 | 3 |
+--------+-------+--------+
5 rows in set (0.00 sec)
内连接(INNER JOIN):
利用条件判断中的比较运算符结合两张表的记录;
只有联接记录符合条件时才会返回列;
mysql> SELECT b.boy,t.toy
-> FROM
-> boys AS b
-> INNER JOIN
-> toys AS t
-> ON b.toy_id = t.toy_id;
查询我们需要的列,ON关键字后设置相应的条件:
使用别名是为了让查询更加简洁;
ON也可以改用关键字WHERE;
条件式里可采用任何一个比较运算符;
+-------+----------+
| boy | toy |
+-------+----------+
| boy 1 | wan ju 2 |
| boy 2 | wan ju 4 |
| boy 3 | wan ju 5 |
| boy 4 | wan ju 1 |
| boy 5 | wan ju 3 |
+-------+----------+
5 rows in set (0.00 sec)
值得注意的是:
ON和WHERE并不等价,尤其在LEFT JOIN和RIGHT JOIN中区别明显(这两个本章不会介绍,后续章节会详细讲解);
以左联接为例,ON是在生成临时表时使用条件,但不管ON中条件是否为真,都会返回左边表中的记录;
WHERE则是在临时表生成之后,在对临时表进行条件过滤,此时已经没有了联接的概念,条件为假的全部过滤掉;
简单来说,INNER JOIN利用条件式里的比较运算符结合两张表;
相等联接(equijoin):
测试相等性的内联接,使用内联接加上相等运算符(=);
上边演示内连接的SQL就是一个相等联接;
不等联接(non-equijoin):
测试不相等性的内联接,使用内联接加上不相等运算符(<>);
最后一种内联接:自然联接(natural join)
自然连接只有在联接的列在两张表中的名称都相同时才会有用;
它是利用相同列名的内联接:
boys表的外键名 和 toys表的主键名,一致的时候,才能使用自然联接;
mysql> SELECT b.boy,t.toy
-> FROM
-> boys AS b
-> NATURAL JOIN
-> toys AS t;
+-------+----------+
| boy | toy |
+-------+----------+
| boy 1 | wan ju 2 |
| boy 2 | wan ju 4 |
| boy 3 | wan ju 5 |
| boy 4 | wan ju 1 |
| boy 5 | wan ju 3 |
有内联接,相应的还有外联接(outer join),这部分会在后续章节继续讲解;
值得注意的是:
我们可以联接多张表;
设计内联接的方式也还有很多;
当然最关键的还是,要理解这些最基本的联接;
联合查询——另一个查询里的查询,我们会在下一章介绍;
总结:
本章我们学习了,AS以及JOIN让我们回顾下;
1.INNER JOIN:内联接,任何使用条件结合来自两张表的记录的链接;
2.NATUREAL JOIN:自然联接是不使用ON子句的内联接,只有在联接的两张表中有同名列时才能顺利运作;
3.CROSS JOIN:交叉联接,返回一张表的每一行与另外一张表的每一行所有可能的搭配结果;
其他常见名称还包括笛卡尔联接(CARTESIAN JOIN)与NO JOIN;
4.EQUIJOIN与NON-EQUIJOIN:相等联接和不等联接,两者都是内联接的一种,相等联接返回相等的行,不等联接则返回不相等的行;
5.COMMA JOIN:与CROSS JOIN相同,只不过以逗号取代关键字CROSS JOIN;