SQLite学习笔记(四)

版权声明:本文为博主原创文章,转载请注明出处: https://blog.csdn.net/qq_38182125/article/details/89429225

概述

本篇文章将继续介绍关于 SELECT 中的一些用法。文章结构如下:

  • 子查询
  • 多表连接/联结表
  • 名称和别名
  • 组合/复合查询
  • 条件结果

一、子查询

子查询是指 SELECT 语句中又嵌套 SELECT 语句。子查询最常应用的地方是 WHERE 子句,特别是在 IN 操作符中。下面通过一个例子来了解下子查询的用法和好处。

例子

列出订购商品 RGAN01 的所有顾客。这个需求的检索思路如下:

  1. 检索包含物品 RGAN01 的所有订单的编号。
  2. 检索具有前一步步骤列出的订单编号的所有顾客的 ID。
  3. 检索前一步骤返回的所有顾客 ID 的顾客信息。

可以看到每一步查询的条件都是上一步查询得到的结果,我们首先进行普通的查询:

输入:

SELECT order_num 
FROM OrderItems 
WHERE prod_id = 'RGAN01';

输出:

order_num
----------
20007
20008

可以看到包含物品 RGAN01 的订单编号为 20007 和 20008,接下来进行第二步:

输入:

SELECT cust_id 
FROM Orders  
WHERE order_num IN(20007, 20008);

输出:

cust_id
----------
1000000004
1000000005

可以得知我们需要的顾客 ID 为 1000000004 和 100000005,接下来进行第三步:

输入:

SELECT cust_name, cust_contact 
FROM Customers 
WHERE cust_id IN('1000000004', '1000000005');

输出:

cust_name   cust_contact
----------  ------------------
Fun4All     Denise L. Stephens
The Toy St  Kim Howard

经过这三步查询之后,我们最终得到了我们想要的顾客信息。

接下来我们用子查询实现同样的效果:

输入:

SELECT cust_name, cust_contact 
FROM Customers 
WHERE cust_id IN (SELECT cust_id 
		FROM Orders 
		WHERE order_num IN (SELECT order_num 
				FROM OrderItems 
				WHERE prod_id = 'RGAN01'));

输出:

cust_name   cust_contact
----------  ------------------
Fun4All     Denise L. Stephens
The Toy St  Kim Howard

可以看到使用子查询我们达到了同样的效果。实际上 DBMS 仍然执行了三条 SELECT 语句。对于子查询来说,有以下三点需要注意:

作为子查询的 SELECT 语句只能查询单个列。企图检索多个列将返回错误。

SQL 语句对于能嵌套的子查询数目没有限制,不过在使用时由于性能的限制,不能嵌套太多子查询。

子查询可以用在关系表达式的任何地方,并非只能用于 WHERE 子句中。


二、多表连接/联结表

为了方便描述下面对于该名词统称为联结表。下面先来了解一下什么是联结。

1. 联结

SQL 最强大的功能之一就是能在数据查询的执行中联结(join)表,联结的这些表我们称之为关系表。什么是关系表,我们可以通过下面的一个例子来理解。

2. 关系表

例如,有一个包含产品目录的数据库表,其中每类物品占一行,对于每一种物品,要存储的信息包括产品描述、价格,以及生产该产品的供应商。

而供应商可能不止生产这一类产品,它还有可能生产多种产品,那么供应商名、地址、联系方法等信息又该如何存储?

答案是最好将供应商信息和产品信息分开存储,理由如下:

  • 同一供应商生产的每个产品,其供应商信息都是相同的,对每个产品重复此信息既浪费时间又浪费存储空间;
  • 如果供应商信息发生变化,例如供应商迁址或者电话号码变动,只需修改一次即可;
  • 如果有重复数据(即每种产品都存储供应商信息),很难保证每次输入该数据的方式都相同。不一致的数据在报表中很难利用。

关系表的设计就是要把信息分解成多个表,一类数据一个表。各表通过某些共同的值相互关联(所以才叫关系数据库)。

例如在上述例子中,我们可以使用一个 Products 表只存储产品信息,除了存储供应商 ID(Vnedors 表主键)外,它不存储其他有关供应商的信息。Vendors 表的主键将 Vendors 表和 Products 表相关联。接下来我们看看如何利用它们之间的联结来查询相关信息。

3. 内联结

内联结就是通过表中的两个字段进行联结,它使用关系代数的一种集合操作,叫做交叉,即找出同时存在于两个集合的元素,如下图所示:

在这里插入图片描述

现在我们将 Vendors、Products 这两个表通过 vend_id 字段进行内联结,我们可以使用 WHERE 子句:

输入:

SELECT vend_name, prod_name, prod_price 
FROM Vendors, Products 
WHERE Vendors.vend_id = Products.vend_id;

输出:

vend_name   prod_name          prod_price
----------  -----------------  ----------
Bears R Us  8 inch teddy bear  5.99
Bears R Us  12 inch teddy bea  8.99
Bears R Us  18 inch teddy bea  11.99
Doll House  Fish bean bag toy  3.49
Doll House  Bird bean bag toy  3.49
Doll House  Rabbit bean bag t  3.49
Doll House  Raggedy Ann        4.99
Fun and Ga  King doll          9.49
Fun and Ga  Queen doll         9.49

注意:在本例中我们使用到了完全限定列名,这是因为 vend_id 在两个表中都存在,为了避免引用的列可能出现歧义,必须使用完全限定列名。

WHERE 的作用:
在联结两个表时,实际上要做的就是将第一表中的每一行与第二个表中的每一行配对。WHERE 子句作为过滤条件,只包含那些匹配给定条件的行。没有 WHERE 子句,第一个表中的每一行将于第二个表中的每一行配对,而不管它们逻辑上能否配在一起,也就是所谓的笛卡儿积,这在后面会做介绍。

根据前面关于内联结的解释图,我们也可以画出 Vendors 和 Products 的内联结图,如下所示:
在这里插入图片描述
两个表的内联结除了使用 WHERE 子句外,还可以使用 INNER JOIN,如下所示:

输入:

SELECT vend_name, prod_name, prod_price 
FROM Vendors INNER JOIN Products 
ON Vendors.vend_id = Products.vend_id;

输出:

vend_name   prod_name          prod_price
----------  -----------------  ----------
Bears R Us  8 inch teddy bear  5.99
Bears R Us  12 inch teddy bea  8.99
Bears R Us  18 inch teddy bea  11.99
Doll House  Fish bean bag toy  3.49
Doll House  Bird bean bag toy  3.49
Doll House  Rabbit bean bag t  3.49
Doll House  Raggedy Ann        4.99
Fun and Ga  King doll          9.49
Fun and Ga  Queen doll         9.49

结果与使用 WHERE 子句的方式一致。

此语句中的 SELECT 与前面的 SELECT 语句相同,但 FROM 子句不同。这里,两个表之间的关系是以 INNER JOIN 指定的部分 FROM 语句。在使用这种语法时,联结条件用特定的 ON 子句而不是 WHERE 子句给出。传递给 ON 的实际条件与传递给 WHERE 的相同。

至于选用哪种语法,答案是都可以。ANSI SQL规范首选 INNER JOIN 语法。

如果我们需要联结2张以上的表格时,使用 WHERE 子句的方式会比使用 INNER JOIN 的方式简便很多。我们以子查询的例子为例:列出订购商品 RGAN01 的所有顾客。使用子查询的语句如下:

SELECT cust_name, cust_contact 
FROM Customers 
WHERE cust_id IN (SELECT cust_id 
		FROM Orders
		WHERE  order_num IN (SELECT order_num 
				FROM OrderItems 
				WHERE prod_id = 'RGAN01'));

对于这个问题,我们使用联结表的方式同样可以解决:

输入:

SELECT cust_name, cust_contact 
FROM Customers, Orders, OrderItems 
WHERE Customers.cust_id = Orders.cust_id 
AND Orders.order_num = OrderItems.order_num 
AND prod_id = 'RGAN01';

输出:

cust_name   cust_contact
----------  ------------------
Fun4All     Denise L. Stephens
The Toy St  Kim Howard

可以看到输出和使用子查询的结果是一样的。当然这道题也可以使用 INNER JOIN 的方式解决,不过由于 INNER JOIN 每次只能联结一个表的关系,所以并不是很方便,命令如下所示:

SELECT cust_name, cust_contact 
FROM Customers INNER JOIN (
		Orders INNER JOIN OrderItems 
		ON Orders.order_num = OrderItems.order_num 
		AND prod_id = 'RGAN01')   
ON Customers.cust_id = Orders.cust_id;

最终得到的输出结果也是一样的。

4. 交叉联结

如果两个表之间没有通过任何方式相连,那么 SELECT 会产生一种更为基础的联结——交叉联结,也成为笛卡儿积联结。它是强制的几乎毫无意义的联结,即使第一个表中的所有行与第二个表中的所有行联合起来。例子如下:

输入:

SELECT vend_name, prod_name, prod_price 
FROM Vendors, Products;

输出:

vend_name   prod_name          prod_price
----------  -----------------  ----------
Bears R Us  8 inch teddy bear  5.99
Bears R Us  12 inch teddy bea  8.99
Bears R Us  18 inch teddy bea  11.99
Bears R Us  Fish bean bag toy  3.49
Bears R Us  Bird bean bag toy  3.49
......
Jouets et   8 inch teddy bear  5.99
Jouets et   12 inch teddy bea  8.99
Jouets et   18 inch teddy bea  11.99
Jouets et   Fish bean bag toy  3.49
Jouets et   Bird bean bag toy  3.49
Jouets et   Rabbit bean bag t  3.49
Jouets et   Raggedy Ann        4.99
Jouets et   King doll          9.49
Jouets et   Queen doll         9.49

从结果可以看出,FROM 在缺省的条件下产生交叉联结,Products 中每一行都与 Vendors 的所有行组合在一起,更为关键的是,行之间不存在关系,也没有联结条件,它们仅仅是简单地组合在一起。

在实际使用时,我们应当避免交叉联结。因为我们往往不需要从一个表的行跨到另一表的每一行。

5. 外联结

一般情况下,内联结已经足以满足我们对联结的需求了。但是在某些情况下,我们需要在结果中包含那些没有关联行的那些行,这时候,内联结就无能为力了,此时我们就需要用到外联结。外联结可以用于解决以下问题:

  • 对每个顾客下的订单进行计数,包括那些至今尚未下订单的顾客;
  • 列出所有产品以及订购数量,包括没有人订购的产品;
  • 计算平均销售规模,包括那些至今尚未下订单的顾客。

上述例子的联结中均包含了相关表中没有关联行的行。这种联结称为外联结。

注意:
在SQL中,联结可分为左外联结、右外联结和全外联结。目前 SQLite 不支持右外联结和全外联结。

下面我们使用一个内联结,检索所有顾客及其订单:

输入:

SELECT Customers.cust_id, Orders.order_num 
FROM Customers INNER JOIN Orders 
ON Customers.cust_id = Orders.cust_id;

输出:

cust_id     order_num
----------  ----------
1000000001  20005
1000000003  20006
1000000004  20007
1000000005  20008
1000000001  20009

接下来使用外联结,检索包括没有订单顾客在内的所有顾客,如下所示:

输入:

SELECT Customers.cust_id, Orders.order_num 
FROM Customers LEFT OUTER JOIN Orders 
ON Customers.cust_id = Orders.cust_id;

输出:

cust_id     order_num
----------  ----------
1000000001  20005
1000000001  20009
1000000002  NULL
1000000003  20006
1000000004  20007
1000000005  20008

可以看到外联结和内联结的使用是非常相似的,只是在关键字上左外联结使用到的是 LEFT OUTER JOIN 而内联结使用到的是 INNER JOIN。从结果中我们也可以看出,外联结的输出包括了没有订单的顾客 1000000002。

虽然 SQLite 不支持右外联结和全外联结,但是这两种外联结的方式都可以有替代的方法。右外联结其实可以直接使用左外联结进行替换,毕竟只需要将两个表的位置互换即可;而全外联结则可以使用左外联结+右外联结+复合查询的形式替换。

6. 自然联结

自然联结实际上是内联结的另一种形式。它会通过表中共有的字段名称将两个表联结起来。自然联结派出多次出现,使每个列只返回一次。

完成这项工作的思路是通过对一个表使用通配符而对其它表的列使用明确的子集完成的。例子如下:

输入:

SELECT C.*, O.order_num, O.order_date,
	OI.prod_id, OI.quantity, OI.item_price 
FROM Customers C, Orders O, OrderItems OI 
WHERE C.cust_id = O.cust_id 
AND OI.order_num = O.order_num 
AND prod_id = 'RGAN01';

输出:

cust_id     cust_name   cust_address         cust_city   cust_state  cust_zip    cust_country  cust_contact        cust_email             order_num   order_date  prod_id     quantity    item_price
----------  ----------  -------------------  ----------  ----------  ----------  ------------  ------------------  ---------------------  ----------  ----------  ----------  ----------  ----------
1000000004  Fun4All     829 Riverside Drive  Phoenix     AZ          88888       USA           Denise L. Stephens  dstephens@fun4all.com  20007       2012-01-30  RGAN01      50          4.49
1000000005  The Toy St  4545 53rd Street     Chicago     IL          54545       USA           Kim Howard          NULL                   20008       2012-02-03  RGAN01      5           4.99

在 SELECT 语句中我们使用到了别名,在后面会对它进行介绍。


三、名称和别名

SQL 允许对表名起别名。这样做有两个主要理由:

  • 缩短 SQL 语句。
  • 允许在一条 SELECT 语句中多次使用相同的表。

假设一种场景:在使用联结时,我们需要联结两个表中相同名称的字段,为了不产生歧义,我们需要使用完全限定列名。如果表名比较长的话,这会相当痛苦,此时我们可以考虑使用别名。

看一个例子:

SELECT cust_name, cust_contact 
FROM Customers, Orders, OrderItems 
WHERE Customers.cust_id = Orders.cust_id 
AND OrderItems.order_num = Orders.order_num 
AND prod_id = 'RGAN01';

如果使用别名,它可以缩短一些:

SELECT cust_name, cust_contact 
FROM Customers C, Orders O, OrderItems OI 
WHERE C.cust_id = O.cust_id 
AND OI.order_num = O.order_num 
AND prod_id = 'RGAN01';

在这里我们将 Customers、Orders 和 OrderItems 分别起别名为 C、O 和 OI,这样在使用完全限定列名的时候会缩短一些语句长度。

使用别名的另一个动机是自联结的时候。我们考虑下面一个问题:要给与 Jim Jones 同一公司的所有顾客发送一封邮件。这个问题需要两步:

  1. 找出 Jim Jones 工作的公司
  2. 找出在该公司的顾客

我们可以使用子查询来解决:

输入:

SELECT cust_id, cust_name, cust_contact 
FROM Customers 
WHERE cust_name = (SELECT cust_name 
		FROM Customers 
			WHERE cust_contact = 'Jim Jones');

输出:

cust_id     cust_name   cust_contact
----------  ----------  ------------
1000000003  Fun4All     Jim Jones
1000000004  Fun4All     Denise L. St

根据前面的经验,能由子查询解决的问题往往也能用联结的方式解决,由于要使用自联结,为了避免列名混淆,我们必须使用别名:

输入:

SELECT c1.cust_id, c1.cust_name, c1.cust_contact 
FROM Customers c1, Customers c2 
WHERE c1.cust_name = c2.cust_name 
AND c2.cust_contact = 'Jim Jones';

输出:

cust_id     cust_name   cust_contact
----------  ----------  ------------
1000000003  Fun4All     Jim Jones
1000000004  Fun4All     Denise L. St

可以看到,输出结果与使用子查询是一致的。


四、组合/复合查询

为了统一名称下面同一称为组合查询。它使用到了 UNION、INTERSECT 和 EXCEPT 操作符。

1. UNION

UNION 用于将多条 SELECT 语句组合成一个结果集。有三种情况会用到 UNION:

  • 在一个查询中从不同的表返回结构数据。
  • 对一个表执行多个查询,按一个查询返回数据。
  • 对于 SQLite 中,组合查询还会用在全外联结上。

例子:假如需要 Illinois、Indiana 和 Michigan 等美国几个州的所有顾客的报表,还想包括不管位于哪个州的所有的 Fun4All。我们可以使用 UNION 来完成:

输入:

SELECT cust_name, cust_contact, cust_email 
FROM Customers 
WHERE cust_state IN('IL', 'IN', 'MI') 
UNION 
SELECT cust_name, cust_contact, cust_email 
FROM Customers 
WHERE cust_name = 'Fun4All';

输出:

cust_name   cust_contact        cust_email
----------  ------------------  ---------------------
Fun4All     Denise L. Stephens  dstephens@fun4all.com
Fun4All     Jim Jones           jjones@fun4all.com
The Toy St  Kim Howard          NULL
Village To  John Smith          sales@villagetoys.com

当然这个要求我们也可以只使用 WHERE 来完成:

SELECT cust_name, cust_contact, cust_email 
FROM Customers 
WHERE cust_state IN('IL', 'IN', 'MI') 
OR cust_name = 'Fun4All';

得到的输出是相同的。在这个例子中 UNION 没有体现出它的优势,但是对于较复杂的过滤条件或者从多个表中检索数据的情形,使用 UNION 处理可能会更为简单。

最后我们示范下用 UNION 实现全外联结。我们使用之前左外联结使用的例子即可,下面是左外联结的输入:

输入:

SELECT C.cust_id, O.order_num 
FROM Customers C LEFT OUTER JOIN Orders O 
ON C.cust_id = O.cust_id;

输出:

cust_id     order_num
----------  ----------
1000000001  20005
1000000001  20009
1000000002  NULL
1000000003  20006
1000000004  20007
1000000005  20008

右外联结的输入:

输入:

SELECT C.cust_id, O.order_num 
FROM Orders O LEFT OUTER JOIN Customers C 
ON C.cust_id = O.cust_id;

输出:

cust_id     order_num
----------  ----------
1000000001  20005
1000000003  20006
1000000004  20007
1000000005  20008
1000000001  20009

全外联结的输入:

输入:

SELECT C.cust_id, O.order_num 
FROM Customers C LEFT OUTER JOIN Orders O 
ON C.cust_id = O.cust_id 
UNION 
SELECT C.cust_id, O.order_num 
FROM Orders O LEFT OUTER JOIN Customers C 
ON C.cust_id = O.cust_id;

输出:

cust_id     order_num
----------  ----------
1000000001  20005
1000000001  20009
1000000002  NULL
1000000003  20006
1000000004  20007
1000000005  20008

UNION 的使用需要遵循以下规则:

  • UNION 必须由两条或两条以上的 SELECT 语句组成,语句之间用关键字 UNION 分隔。
  • UNION 中的每个查询必须包含相同的列、表达式或聚集函数,但各个列不需要以相同的次序出现。
  • 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含转换的类型。

UNION 使用的一些注意事项:

  • UNION 从查询结果集中自动去除了重复的行,这是 UNION 的默认行为,如果不想取消默认行的话,可以使用 UNION ALL。
  • 在使用 UNION 组合查询时,只能使用一条 ORDER BY 子句,它必须位于最后一条 SELECT 语句之后。

2. INTERSECT

INTERSECT 操作输入两个关系 A 和 B,选择那些既在结果 A 也在 B 的行,例子如下所示:

输入:

SELECT cust_name, cust_contact, cust_email 
FROM Customers 
WHERE cust_state IN('IL', 'IN', 'MI') 
INTERSECT 
SELECT cust_name, cust_contact, cust_email 
FROM Customers 
WHERE cust_name = 'Fun4All';

输出:

cust_name   cust_contact  cust_email
----------  ------------  ------------------
Fun4All     Jim Jones     jjones@fun4all.com

可以看出客户既满足位于 Illinois、Indiana 和 Michigan 这三个州又是是 Fun4All 的结果就只有一条了。INTERSECT 的使用限制和 UNION 基本类似。

3. EXCEPT

EXCEPT 操作输入两个关系 A 和 B,找出所有在 A 但不在 B 的行。这个操作符使用比较少,因为通过联结也同样可以得到相同的结果。例子如下:

输入:

SELECT cust_name, cust_contact, cust_email 
FROM Customers 
WHERE cust_state IN('IL', 'IN', 'MI') 
EXCEPT 
SELECT cust_name, cust_contact, cust_email 
FROM Customers 
WHERE cust_name = 'Fun4All';

输出:

cust_name      cust_contact  cust_email
-------------  ------------  ----------
The Toy Store  Kim Howard    NULL
Village Toys   John Smith    sales@vill

可以看到结果输出符合我们的描述。而 EXCEPT 操作符的限制也与 UNION 的限制基本一致。


五、条件结果

CASE 表达式允许在 SELECT 语句中处理各种情况,它有两种形式:接收静态值和表达式中有 WHEN 的情况。

1. 接收静态值

这是 CASE 表达式中最简单的一种情况,它的形式如下所示:

CASE value
	WHEN x THEN value_x
	WHEN y THEN value_y
	WHEN z THEN value_z
	ELSE default_value
END

我们通过一个简单示例来了解它的使用:

输入:

SELECT prod_name || CASE vend_id 
					WHEN 'BRS01' THEN '--BRS01'
					WHEN 'DLL01' THEN '--DLL01'
					WHEN 'FNG01' THEN '--FNG01'
					ELSE NULL 
					END AS CONCAT 
FROM Products;

输出:

CONCAT
------------------------
8 inch teddy bear--BRS01
12 inch teddy bear--BRS0
18 inch teddy bear--BRS0
Fish bean bag toy--DLL01
Bird bean bag toy--DLL01
Rabbit bean bag toy--DLL
Raggedy Ann--DLL01
King doll--FNG01
Queen doll--FNG01

这个 SELECT 语句将 prod_name 和 vend_id 进行拼接后输出,通过 CASE 语句对 vend_id 的值进行判断然后拼接上不同的字符串,这就是静态 CASE 的大致用法。

2. 表达式中有WHEN的情形

这种情形下的形式如下所示:

CASE 
	WHEN condition1 THEN value1
	WHEN condition2 THEN value2
	WHEN condition3 THEN value3
	ELSE default_value
END

同样通过一个简单的例子展示它的使用:

输入:

SELECT vend_id, (SELECT 
				CASE 
					WHEN COUNT(*) > 3 THEN 'High' 
					WHEN COUNT(*) = 3 THEN 'Moderate' 
					WHEN COUNT(*) < 3 THEN 'Low' 
					ELSE NULL 
				END  
				FROM Products 
				WHERE P.vend_id = vend_id) AS Frequency 
FROM Products  P 
GROUP BY vend_id; 

输出:

vend_id     Frequency
----------  ----------
BRS01       Moderate
DLL01       High
FNG01       Low

在这个例子中,我们统计出 vend_id 在表格中的出现频率,然后根据大于、等于、小于 3 将频率等级划分为 High、Moderate 和 Low。


参考

《SQLite权威指南》

  • 第3章 sqlite中的sql

《SQL必知必会》

  • 第11课 使用子查询
  • 第12课 联结表
  • 第13课 使用高级联结
  • 第14课 组合查询

本文中的例子均来自《SQLite必知必会》,图片来源《SQLite权威指南》,转载请注明出处!


希望这篇文章对你有所帮助~

猜你喜欢

转载自blog.csdn.net/qq_38182125/article/details/89429225