引言
许多数据库传播信息在不同的表基于它们的含义和上下文。在检索数据库中保存的数据信息时,经常需要同时引用多个表。
结构化查询语言(SQL)提供了多种从不同表中检索数据的方法,例如集合操作。更具体地说,集合运算符
UNION
在大多数关系数据库系统中都得到了广泛支持。
UNION
操作将两个具有匹配列的查询结果合并为一个。
在本指南中,您将使用“联盟”操作同时从多个表检索数据,然后合并结果。你还将结合使用
UNION
操作符和过滤来对结果进行排序。
前期准备
为了学习本指南,你需要一台运行某种使用SQL的关系数据库管理系统(RDBMS)的计算机。
注意:请注意,许多RDBMS使用它们自己独特的SQL实现。虽然本教程中概述的命令适用于大多数RDBMS,但如果你在MySQL以外的系统上测试它们,确切的语法或输出可能会有所不同。
你还需要一个装载了一些示例数据的数据库和表,可以在其中练习使用相关命令。
连接到MySQL并设置一个示例数据库
如果SQL数据库系统运行在远程服务器上,请从本地设备SSH到服务器:
ssh sammy@your_server_ip
然后打开MySQL服务器提示符,将
==sammy==
替换为你的MySQL用户账户的名称:
mysql -u sammy -p
创建一个名为
bookstore
的数据库:
CREATEDATABASE bookstore;
如果数据库成功创建,您将收到这样的输出:
OutputQuery OK, 1 row affected (0.01 sec)
要选择
bookstore
数据库,运行以下
USE
语句:
USE bookstore;
OutputDatabase changed
选择数据库后,您可以在其中创建示例表。在本指南中,你将使用一个虚构的书店,它既提供图书购买,也提供图书租赁。两个服务都是分开管理的;因此,有关购买和租赁的数据存储在不同的表中。
注意:为了便于学习,本例中的数据库模式被简化了。在实际场景中,表结构将更加复杂,并涉及主键和外键。有关数据库如何组织数据的更多信息,请参阅我们关于理解关系数据库的教程。
第一个表
book_purchases
将包含购买的书籍和购买的顾客的数据。它将包含4列:
purchase_id
:这一列包含购买标识符,用int
数据类型表示。这一列将成为表的主键,每个值将成为对应行的唯一标识符。customer_name
:这一列将保存客户的名称,使用varchar
数据类型表示,最多30个字符。book_title
:这一列将保存购买的图书的标题,使用varchar
数据类型表示,最多200个字符。date
:使用date
数据类型,这一列将保存每次购买的日期。
使用下面的命令创建示例表:
CREATETABLE book_purchases (
purchase_id int,
customer_name varchar(30),
book_title varchar(40),datedate,PRIMARYKEY(purchase_id));
如果输出如下,说明已经创建了第一张表:
OutputQuery OK, 0 rows affected (0.00 sec)
第二个表名为
book_leases
,它将存储有关借阅书籍的信息。它的结构与前一个类似,但租约的特征是两个不同的日期:租约日期和租约持续时间。为了表示这一点,表将包含5列:
lease_id
:这一列包含租赁标识符,用int
数据类型表示。这一列将成为表的主键,每个值将成为对应行的唯一标识符。customer_name
:这一列将保存客户的名称,使用varchar
数据类型表示,最多30个字符。book_title
:这一列将保存借阅书籍的标题,使用varchar
数据类型表示,最多200个字符。date_from
:使用date
数据类型,这一列将保存租约的开始日期。date_to
:使用date
数据类型,这一列将保存租约的结束日期。
使用下面的命令创建第二个表:
CREATETABLE book_leases (
lease_id int,
customer_name varchar(30),
book_title varchar(40),
date_from date,
date_to date,PRIMARYKEY(lease_id));
下面的输出确认了第二个表的创建:
OutputQuery OK, 0 rows affected (0.00 sec)
接下来,通过运行以下
INSERT INTO
操作,加载包含一些示例数据:
INSERTINTO book_purchases
VALUES(1,'sammy','The Picture of Dorian Gray','2022-10-01'),(2,'sammy','Pride and Prejudice','2022-10-04'),(3,'sammy','The Time Machine','2022-09-23'),(4,'bill','Frankenstein','2022-07-23'),(5,'bill','The Adventures of Huckleberry Finn','2022-10-01'),(6,'walt','The Picture of Dorian Gray','2022-04-15'),(7,'walt','Frankenstein','2022-10-13'),(8,'walt','Pride and Prejudice','2022-10-19');
INSERT INTO
操作将向
book_purchases
表中添加8个指定值的购买记录。下面的输出表明已经添加了8行数据:
OutputQuery OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
然后在
book_lease
表中插入一些示例数据:
INSERTINTO book_leases
VALUES(1,'sammy','Frankenstein','2022-09-14','2022-11-14'),(2,'sammy','Pride and Prejudice','2022-10-01','2022-12-31'),(3,'sammy','The Adventures of Huckleberry Finn','2022-10-01','2022-12-01'),(4,'bill','The Picture of Dorian Gray','2022-09-03','2022-09-18'),(5,'bill','Crime and Punishment','2022-09-27','2022-12-05'),(6,'kim','The Picture of Dorian Gray','2022-10-01','2022-11-15'),(7,'kim','Pride and Prejudice','2022-09-08','2022-11-17'),(8,'kim','The Time Machine','2022-09-04','2022-10-23');
你将收到以下输出,它确认示例数据已被添加:
OutputQuery OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
租赁和购买涉及相似的客户和书籍,这将有助于演示
UNION
操作符的行为。
理解
UNION
操作符的语法
SQL中的
UNION
操作符告诉数据库将通过单独的
SELECT
查询检索到的两个单独的结果集合并为一个结果集,其中包含两个查询返回的行。
注意:数据库不会限制
UNION
中
SELECT
查询的复杂性。数据检索查询可以包括
JOIN
语句、聚合或子查询。通常,
UNION
用于合并复杂语句的结果。出于教学目的,本指南中的示例将使用
SELECT
查询来关注
UNION
操作符的行为。
下面的例子展示了包含
UNION
操作符的SQL语句的通用语法:
SELECT column1, column2 FROM table1
UNIONSELECT column1, column2 FROM table2;
这个SQL片段以一条从
table1
返回两列的
SELECT
语句开始,接着是
UNION
操作符和第二条
SELECT
语句。第二个
SELECT
查询也返回了两列,但是来自
table2
。
UNION
关键字告诉数据库获取前面和后面的查询,分别执行它们,然后将它们的结果集连接成一个。整个代码片段,包括
SELECT
查询和它们之间的
UNION
关键字,是一条单独的SQL语句。因此,第一个
SELECT
查询不会以分号结束,它只出现在整个语句之后。
举个例子,假设你想列出购买或租用图书的所有客户。购买记录保存在
book_purchases
表中,而租赁记录保存在
book_leases
表中。运行以下查询:
SELECT customer_name FROM book_purchases
UNIONSELECT customer_name FROM book_leases;
下面是这个查询的结果集:
Output+---------------+
| customer_name |
+---------------+
| sammy || bill || walt || kim |
+---------------+
4 rows inset(0.000 sec)
这个输出表明Sammy、Bill、Walt和Kim在某个时间点购买或租了书。要理解这个结果集是如何生成的,请尝试分别执行两个
SELECT
语句:一次针对purchase,一次针对lease。
运行下面的查询来返回购买图书的顾客:
SELECT customer_name FROM book_purchases;
下面的输出将打印到屏幕上:
Output+---------------+
| customer_name |
+---------------+
| sammy || sammy || sammy || bill || bill || walt || walt || walt |
+---------------+
8 rows inset(0.000 sec)
Sammy, Bill和 Walt 购买书籍,但是Kim 没有。
接下来,运行查询来返回租书的顾客:
SELECT customer_name FROM book_leases;
下面的输出将打印到屏幕上:
Output+---------------+
| customer_name |
+---------------+
| sammy || sammy || sammy || bill || bill || kim || kim || kim |
+---------------+
8 rows inset(0.000 sec)
租赁表指Sammy, Bill和Kim,但Walt从未借的书。通过组合两个答案,您可以得到租赁和购买的数据。
使用
UNION
和单独执行两个查询之间的重要区别是,
UNION
除了合并结果之外,还删除了重复的值:在结果中没有重复的客户名称。
为了使用
UNION
正确地合并两个独立查询的结果,两个查询都应该返回相同格式的结果。有些不一致会导致数据库引擎错误,而有些则会给出与查询意图不匹配的结果。
考虑下面两个例子:
列计数不匹配的UNION
试着在返回单列的
SELECT
语句和返回两列的
UNION
语句之间执行:
SELECT purchase_id, customer_name FROM book_purchases
UNIONSELECT customer_name FROM book_leases;
数据库服务器将返回一个错误:
OutputThe used SELECT statements have a different number of columns
无法对具有不同列数的结果集执行
UNION
操作。
列顺序不匹配的UNION
试着在两个返回相同值但顺序不同的
SELECT
语句之间执行
UNION
:
SELECT customer_name, book_title FROM book_purchases
UNIONSELECT book_title, customer_name FROM book_leases;
数据库服务器不会返回错误,但结果集不会是正确的:
Output+------------------------------------+------------------------------------+
| customer_name | book_title |
+------------------------------------+------------------------------------+
| sammy | The Picture of Dorian Gray || sammy | Pride and Prejudice || sammy | The Time Machine || bill | Frankenstein || bill | The Adventures of Huckleberry Finn || walt | The Picture of Dorian Gray || walt | Frankenstein || walt | Pride and Prejudice || Frankenstein | sammy || Pride and Prejudice | sammy || The Adventures of Huckleberry Finn | sammy || The Picture of Dorian Gray | bill || Crime and Punishment | bill || The Picture of Dorian Gray | kim || Pride and Prejudice | kim || The Time Machine | kim |
+------------------------------------+------------------------------------+
16 rows inset(0.000 sec)
在这个例子中,
UNION
操作将第一个查询的第一列与第二个查询的第一列合并,并对第二个列执行相同的操作,将客户姓名和图书名称混合在一起。
使用
WHERE
子句并与
UNION
一起排序
在前面的例子中,合并了表示两个对应表中所有行的结果集。通常,你需要在合并结果之前过滤行。用
UNION
操作符合并的
SELECT
语句可以使用
WHERE
子句来完成。
假设你想知道Sammy在你的书店的帮助下读了哪些书,无论是通过购买还是租赁。运行以下查询:
SELECT book_title FROM book_purchases
WHERE customer_name ='Sammy'UNIONSELECT book_title FROM book_leases
WHERE customer_name ='Sammy';
两个
SELECT
查询都包含
WHERE
子句,它从两个不同的表中过滤数据,只包含
Sammy
的purchase和lease。此查询的结果集将打印如下:
Output+------------------------------------+
| book_title |
+------------------------------------+
| The Picture of Dorian Gray || Pride and Prejudice || The Time Machine || Frankenstein || The Adventures of Huckleberry Finn |
+------------------------------------+
5 rows inset(0.000 sec)
再一次,
UNION
确保结果列表中没有重复的结果。你可以使用
WHERE
子句来限制在
SELECT
查询中返回哪些行,或者只返回其中的一行。此外,
WHERE
子句可以引用两个语句中的不同列和条件。
通过
UNION
操作返回的结果没有任何特定的顺序。要改变这一点,你可以利用
ORDER BY
子句。排序是在最终合并的结果上执行的,而不是在单个查询上执行的。
在检索到Sammy购买或租赁的所有图书后,要对书名按字母顺序排序,执行以下查询:
SELECT book_title FROM book_purchases
WHERE customer_name ='Sammy'UNIONSELECT book_title FROM book_leases
WHERE customer_name ='Sammy'ORDERBY book_title;
下面的输出将打印到屏幕上:
Output+------------------------------------+
| book_title |
+------------------------------------+
| Frankenstein || Pride and Prejudice || The Adventures of Huckleberry Finn || The Picture of Dorian Gray || The Time Machine |
+------------------------------------+
5 rows inset(0.001 sec)
这一次,返回的结果是基于包含两个
SELECT
查询合并结果的
book_title
列的顺序。
使用
UNION ALL
来保留副本
如前面的例子所示,
UNION
操作符会自动从结果中删除重复的行。然而,有时这种行为并不是您期望或打算通过查询实现的。例如,假设您感兴趣的书籍,购买或租赁10月1日,2022年。要取得这些标题,可以像之前一样使用类似的例子:
SELECT book_title FROM book_purchases
WHEREdate='2022-10-01'UNIONSELECT book_title FROM book_leases
WHERE date_from ='2022-10-01'ORDERBY book_title;
You will get the following results:
Output+------------------------------------+
| book_title |
+------------------------------------+
| Pride and Prejudice || The Adventures of Huckleberry Finn || The Picture of Dorian Gray |
+------------------------------------+
3 rows inset(0.001 sec)
归还的书名是正确的,但结果不会告诉你这些书是只买的、只租的,还是两者都有。如果有些书同时购买和租赁,它们的书名将同时出现在
book_purchase
和
book_lease
表中。然而,由于
UNION
删除了重复的行,这些信息在结果中丢失了。
幸运的是,SQL有办法改变这种行为,保留重复的行。你可以使用
UNION ALL
操作符来合并两个查询的结果,而不会删除重复的行。
UNION ALL
的工作原理与
UNION
类似,但当相同的值出现多次时,ALL将出现在结果中。
运行相同的查询,但将
UNION
更改为
UNION ALL
:
SELECT book_title FROM book_purchases
WHEREdate='2022-10-01'UNIONALLSELECT book_title FROM book_leases
WHERE date_from ='2022-10-01'ORDERBY book_title;
这一次,得到的列表变长了:
Output+------------------------------------+
| book_title |
+------------------------------------+
| Pride and Prejudice || The Adventures of Huckleberry Finn || The Adventures of Huckleberry Finn || The Picture of Dorian Gray || The Picture of Dorian Gray |
+------------------------------------+
5 rows inset(0.000 sec)
《The Adventures of Huckleberry Finn》和《The Picture of Dorian Gray》这两本书在结果集中出现了两次。这意味着这些标题同时出现在
book_purchase
和
book_lease
表中。对于重复的条目,您可以假设它们在当天已经被租赁和购买。
根据你是想删除还是保留重复项,你可以在
UNION
和
UNION ALL
操作符之间进行选择,它们可以互换使用。
注意:执行工会所有的速度比执行“联盟”,作为数据库不需要对重复扫描结果集。如果你正在合并两个
SELECT
查询的结果,并且你知道它们不会包含任何重复的行,那么在更大的数据集上使用
UNION ALL
可以带来明显的性能提升。
总结
按照本指南,你可以使用
UNION
和
UNION ALL
操作从多个表中检索数据。你还使用了
WHERE
子句来过滤结果,并使用
ORDER BY
子句来对结果进行排序。最后,你了解了如果
SELECT
语句产生不同的数据格式,可能出现的错误和意外行为。
虽然这里包含的命令应该适用于大多数关系数据库,但请注意,每个SQL数据库都使用自己独特的语言实现。
版权归原作者 CHQIUU 所有, 如有侵权,请联系我们删除。