如何使用SQL系列 之 如何在SQL中使用联合(UNION)

引言

许多数据库传播信息在不同的表基于它们的含义和上下文。在检索数据库中保存的数据信息时,经常需要同时引用多个表。

结构化查询语言(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的数据库:

CREATE DATABASE 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数据类型,这一列将保存每次购买的日期。

使用下面的命令创建示例表:

CREATE TABLE book_purchases (
    purchase_id int,
    customer_name varchar(30),
    book_title varchar(40),
    date date,
    PRIMARY KEY (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数据类型,这一列将保存租约的结束日期。

使用下面的命令创建第二个表:

CREATE TABLE book_leases (
    lease_id int,
    customer_name varchar(30),
    book_title varchar(40),
    date_from date,
    date_to date,
    PRIMARY KEY (lease_id)
);

下面的输出确认了第二个表的创建:

OutputQuery OK, 0 rows affected (0.00 sec)

接下来,通过运行以下INSERT INTO操作,加载包含一些示例数据:

INSERT INTO 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 表中插入一些示例数据:

INSERT INTO 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查询检索到的两个单独的结果集合并为一个结果集,其中包含两个查询返回的行。

注意:数据库不会限制UNIONSELECT查询的复杂性。数据检索查询可以包括JOIN语句、聚合或子查询。通常,UNION用于合并复杂语句的结果。出于教学目的,本指南中的示例将使用SELECT查询来关注UNION操作符的行为。

下面的例子展示了包含UNION操作符的SQL语句的通用语法:

SELECT column1, column2 FROM table1
UNION
SELECT column1, column2 FROM table2;

这个SQL片段以一条从table1返回两列的SELECT语句开始,接着是UNION操作符和第二条SELECT语句。第二个SELECT查询也返回了两列,但是来自table2UNION关键字告诉数据库获取前面和后面的查询,分别执行它们,然后将它们的结果集连接成一个。整个代码片段,包括SELECT查询和它们之间的UNION关键字,是一条单独的SQL语句。因此,第一个SELECT查询不会以分号结束,它只出现在整个语句之后。

举个例子,假设你想列出购买或租用图书的所有客户。购买记录保存在book_purchases表中,而租赁记录保存在book_leases表中。运行以下查询:

SELECT customer_name FROM book_purchases
UNION
SELECT customer_name FROM book_leases;

下面是这个查询的结果集:

Output+---------------+
| customer_name |
+---------------+
| sammy         |
| bill          |
| walt          |
| kim           |
+---------------+
4 rows in set (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 in set (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 in set (0.000 sec)

租赁表指Sammy, Bill和Kim,但Walt从未借的书。通过组合两个答案,您可以得到租赁和购买的数据。

使用UNION和单独执行两个查询之间的重要区别是,UNION除了合并结果之外,还删除了重复的值:在结果中没有重复的客户名称。

为了使用UNION正确地合并两个独立查询的结果,两个查询都应该返回相同格式的结果。有些不一致会导致数据库引擎错误,而有些则会给出与查询意图不匹配的结果。

考虑下面两个例子:

列计数不匹配的UNION

试着在返回单列的SELECT语句和返回两列的UNION语句之间执行:

SELECT purchase_id, customer_name FROM book_purchases
UNION
SELECT 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
UNION
SELECT 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 in set (0.000 sec)

在这个例子中,UNION操作将第一个查询的第一列与第二个查询的第一列合并,并对第二个列执行相同的操作,将客户姓名和图书名称混合在一起。

使用WHERE子句并与UNION一起排序

在前面的例子中,合并了表示两个对应表中所有行的结果集。通常,你需要在合并结果之前过滤行。用UNION操作符合并的SELECT语句可以使用WHERE子句来完成。

假设你想知道Sammy在你的书店的帮助下读了哪些书,无论是通过购买还是租赁。运行以下查询:

SELECT book_title FROM book_purchases
WHERE customer_name = 'Sammy'
UNION
SELECT 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 in set (0.000 sec)

再一次,UNION确保结果列表中没有重复的结果。你可以使用WHERE子句来限制在SELECT查询中返回哪些行,或者只返回其中的一行。此外,WHERE子句可以引用两个语句中的不同列和条件。

通过UNION操作返回的结果没有任何特定的顺序。要改变这一点,你可以利用ORDER BY子句。排序是在最终合并的结果上执行的,而不是在单个查询上执行的。

在检索到Sammy购买或租赁的所有图书后,要对书名按字母顺序排序,执行以下查询:

SELECT book_title FROM book_purchases
WHERE customer_name = 'Sammy'
UNION
SELECT book_title FROM book_leases
WHERE customer_name = 'Sammy'
ORDER BY book_title;

下面的输出将打印到屏幕上:

Output+------------------------------------+
| book_title                         |
+------------------------------------+
| Frankenstein                       |
| Pride and Prejudice                |
| The Adventures of Huckleberry Finn |
| The Picture of Dorian Gray         |
| The Time Machine                   |
+------------------------------------+
5 rows in set (0.001 sec)

这一次,返回的结果是基于包含两个SELECT查询合并结果的book_title列的顺序。

使用UNION ALL来保留副本

如前面的例子所示,UNION操作符会自动从结果中删除重复的行。然而,有时这种行为并不是您期望或打算通过查询实现的。例如,假设您感兴趣的书籍,购买或租赁10月1日,2022年。要取得这些标题,可以像之前一样使用类似的例子:

SELECT book_title FROM book_purchases
WHERE date = '2022-10-01'
UNION
SELECT book_title FROM book_leases
WHERE date_from = '2022-10-01'
ORDER BY 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 in set (0.001 sec)

归还的书名是正确的,但结果不会告诉你这些书是只买的、只租的,还是两者都有。如果有些书同时购买和租赁,它们的书名将同时出现在book_purchasebook_lease表中。然而,由于UNION删除了重复的行,这些信息在结果中丢失了。

幸运的是,SQL有办法改变这种行为,保留重复的行。你可以使用UNION ALL操作符来合并两个查询的结果,而不会删除重复的行。UNION ALL的工作原理与UNION类似,但当相同的值出现多次时,ALL将出现在结果中。

运行相同的查询,但将UNION更改为UNION ALL

SELECT book_title FROM book_purchases
WHERE date = '2022-10-01'
UNION ALL
SELECT book_title FROM book_leases
WHERE date_from = '2022-10-01'
ORDER BY 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 in set (0.000 sec)

《The Adventures of Huckleberry Finn》和《The Picture of Dorian Gray》这两本书在结果集中出现了两次。这意味着这些标题同时出现在book_purchase book_lease 表中。对于重复的条目,您可以假设它们在当天已经被租赁和购买。

根据你是想删除还是保留重复项,你可以在UNIONUNION ALL操作符之间进行选择,它们可以互换使用。

注意:执行工会所有的速度比执行“联盟”,作为数据库不需要对重复扫描结果集。如果你正在合并两个SELECT查询的结果,并且你知道它们不会包含任何重复的行,那么在更大的数据集上使用UNION ALL可以带来明显的性能提升。

总结

按照本指南,你可以使用UNIONUNION ALL操作从多个表中检索数据。你还使用了WHERE子句来过滤结果,并使用ORDER BY子句来对结果进行排序。最后,你了解了如果SELECT语句产生不同的数据格式,可能出现的错误和意外行为。

虽然这里包含的命令应该适用于大多数关系数据库,但请注意,每个SQL数据库都使用自己独特的语言实现。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CHQIUU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值