联接与集合操作三--集合操作
通常来说,将联接操作看成是表之间的水平操作,因为该操作生成的虚拟表包含两个表中的列。而集合操作一般将这些操作视为垂直操作。MySQL数据库支持两种集合操作:UNION ALL和UNION DISTINCT。
与联接操作一样,集合操作也是对两个输入进行操作,并生成一个虚拟表。在联接操作中,一般把输入表称为左输入和右输入,或都第一个输入和第二个输入。集合操作的两个输入必须拥有相同的列数,若数据类型不同,MySQL数据库会自动进行隐式转化。同时结果列的名称由第一个输入决定。
在进一步阐述集合操作之前,先根据下列语句创建测试表x、y:
mysql> create table x(a char(1));
Query OK, 0 rows affected (0.01 sec)
mysql> create table y(a char(1));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into x select 'a';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> insert into x select 'b';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> insert into x select 'c';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> insert into y select 'a';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> insert into y select 'b';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql>
接着来看对一个不同类型的数据进行集合操作:
mysql> select a as m from x
-> union
-> select 1 as n from dual
-> union
-> select 'abc' as o from dual
-> union
-> select now() as p from dual;
+---------------------+
| m |
+---------------------+
| a |
| b |
| c |
| 1 |
| abc |
| 2016-03-18 20:44:33 |
+---------------------+
6 rows in set (0.00 sec)
mysql>
这里对各种不同的类型进行了联接,先从x表中取出类型为CHAR(1)的字符,第二个进行UNION操作的是整型数1,第三个是字符串abc,第四个是日期类型。虽然类型各不相同,但是MySQL数据库会自动对其进行判断,选出一种类型进行隐式转换。
另一方面,在这个例子中对每个选取操作都进行了别名定义,从最后的结果可以看出,MySQL选择了m这个别保,也就是集合操作中第一个SELECT输入的别名。
除了以下两点,集合操作中的SELECT语句和一般的SELECT查询并无不同:
- 只有最后一个SELECT可以应用INOT OUTFILE,但是整个集合的操作将被输出到文件中。
- 不能在SELECT语句中使用HIGH_PRIORITY关键字。
注意:在集合操作中INOT OUTFILE只能存在于最后一个SELECT语句中,否则MySQL数据库会提示语法错误,例如:
mysql> select a into outfile 'ret.txt' from x
-> union
-> select a from y;
ERROR 1221 (HY000): Incorrect usage of UNION and INTO
mysql>
此外,虽然INTO OUTFILE只存在于最后一个SELECT语句中,但导出的结果是整个集合操作的结果,例如:
mysql> select a from x union select a from y into outfile 'ret.txt';
Query OK, 3 rows affected (0.01 sec)
mysql>
[mysql@mysql guo]$ cat ret.txt #默认保存在data目录下的数据库目录
a
b
c
mysql> select a from x union all select a from y into outfile '/home/mysql/ret.txt'; #指定保存目录
Query OK, 5 rows affected (0.00 sec)
mysql> system cat /home/mysql/ret.txt
a
b
c
a
b
mysql>
还有一点需要注意的是,若SELECT语句中包含LIMIT和ORDER BY子句,最好的做法是为参与集合操作的各SELECT语句添加括号,否则执行集合查询会得到错误提示,例如:
LIMIT 未添加括号
mysql> SELECT A FROM X ORDER BY A LIMIT 1
-> UNION
-> SELECT A FROM Y;
ERROR 1221 (HY000): Incorrect usage of UNION and ORDER BY
mysql>
只对第一个数据添加括号
mysql> (SELECT A FROM X ORDER BY A LIMIT 1) UNION SELECT A FROM Y;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
mysql>
这个集合操作的正确SQL语句应该为:
mysql> (SELECT A FROM X ORDER BY A LIMIT 1) UNION(SELECT A FROM Y);
+------+
| A |
+------+
| a |
| b |
+------+
2 rows in set (0.00 sec)
mysql>
UNION DISTINCT 和UNION ALL
UNION DISTINCT组合两个输入,并应用DISTINCT过滤重复项。一般省略DISTINCT关键字,直接用UNION,例如:
mysql> SELECT A FROM X
-> UNION
-> SELECT A FROM y;
+------+
| A |
+------+
| a |
| b |
| c |
+------+
3 rows in set (0.00 sec)
mysql> SELECT A FROM X UNION ALL SELECT A FROM y;
+------+
| A |
+------+
| a |
| b |
| c |
| a |
| b |
+------+
5 rows in set (0.00 sec)
mysql>
MySQL数据库目前对于UNION DISTINCT的实现方式如下:
- 创建一张临时表,即虚拟表。
- 对这张临时表的列添加唯一索引(Unique Index)。
- 将输入的数据插入临时表。
- 返回虚拟表。
因为添加了唯一索引,所以可以过滤掉集合中重复的项。可以通过观察服务器状态变量Created_tmp_tables来确认UNION DISTICT的实现方式,例如:
mysql> show status like 'Created_tmp_tables';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| Created_tmp_tables | 10 |
+--------------------+-------+
1 row in set (0.00 sec)
mysql> SELECT A FROM X UNION SELECT A FROM y;
+------+
| A |
+------+
| a |
| b |
| c |
+------+
3 rows in set (0.00 sec)
mysql> show status like 'Created_tmp_tables';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| Created_tmp_tables | 11 |
+--------------------+-------+
1 row in set (0.00 sec)
mysql>
由于向临时表添加了唯一索引,插入的速度显然会因此而受到影响。如果确认进行UNION操作的两个集合中没有重复的选项,最有效的办法应该是使用UNION ALL。
UNION ALL 组合两个输入中所有的结果集,并包含重复的选项,例如:
mysql> SELECT A FROM X UNION ALL SELECT A FROM y;
+------+
| A |
+------+
| a |
| b |
| c |
| a |
| b |
+------+
5 rows in set (0.00 sec)
正如前面据说,如果确认两个输入没有重复项,应该选择UNION ALL。如果两个输入中有重复项,也可以在数据库端使用UNION ALL,在应用程序端进行DISTINCT的去重。