1.如何使用子查询
1.1 什么是子查询
SELECT语句是SQL的查询。所有SELECT语句都是简单查询,即从单个数据库表中检索数据的单条语句。SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。
1.2 利用子查询进行过滤—WHERE子句的IN操作符
本书所有章中使用的数据库表都是关系表。订单存储在两个表中。对于包含订单号、客户ID、订单日期的每个订单,orders表
存储一行。各订单的物品存储在相关的orderitems表
中。orders表不存储客户信息。它只存储客户的ID。实际的客户信息存储在customers表中。假如需要列出订购物品TNT2的所有客户的客户信息,应该怎样检索?
-
检索包含物品TNT2的所有订单的编号。
SELECT order_num FROM orderitems WHERE prod_id = 'TNT2';
-
检索具有前一步骤列出的订单编号的所有客户的ID
SELECT cust_id FROM orders WHERE order_num IN (20005,20007);
-
检索前一步骤返回的所有客户ID的客户信息。
SELECT cust_name,cust_contact FROM customers WHERE cust_id IN (10001,10004);
可以使用子查询来把3个查询组合成一条语句
在SELECT语句中,子查询总是从内向外处理。
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 ='TNT2 '));
为了执行上述SELECT语句,MySQL实际上必须执行3条SELECT语句。最里边的子查询返回订单号列表,此列表用于其外面的子查询的WHERE子句。外面的子查询返回客户ID列表,此客户ID列表用于最外层查询的WHERE子句。最外层查询确实返回所需的数据。
虽然子查询一般与IN操作符结合使用,但也可以用于测试等于(=)、不等于(<>)等。
1.3 作为计算字段使用子查询
使用子查询的另一方法是创建计算字段。假如需要显示customers表中每个客户的订单总数,订单与相应的客户ID存储在orders表中。需要如下步骤:
- 从customers表中检索客户列表
- 对于检索出的每个客户,统计其在orders表中的订单数目
为了对每个客户执行COUNT(*)
计算,应该将COUNT (*)
作为一个子查询。
SELECT cust_name,cust _state,
(SELECT COUNT(*) FROM orders
WHERE orders.cust_id = customers.cust_id) AS orders
FROM customers
ORDER BY cust_name;
这条SELECT语句对customers表中每个客户返回3列:cust name、cust state和orders。orders是一个计算字段,它是由圆括号中的子查询建立的。该子查询对检索出的每个客户执行一次。在此例子中,该子查询执行了5次,因为检索出了5个客户。
1.4 相关子查询—限制有歧义的列名
相关子查询(correlated subquery):涉及外部查询的子查询
WHERE orders.cust_id = customers.cust_id
使用了完全限定列名,如果不使用限定列名会发生什么情况呢
SELECT cust_name,cust_state,
(SELECT COUNT(*) FROM orders
WHERE cust_id = cust_id) AS orders
FROM customers
ORDER BY cust_name;
显然,返回的结果不正确(请比较前面的结果),那么,为什么会这样呢?有两个cust_id列,一个在customers中,另一个在orders中,需要比较这两个列以正确地把订单与它们相应的顾客匹配。如果不完全限定列名,MySQL将假定你是对orders表中的cust_id进行自身比较。而SELECT COUNT(* )FROM orders WHERE cust_id= cust_id
;总是返回orders表中的订单总数(因为MySQL查看每个订单的cust id是否与本身匹配,当然,它们总是匹配的)。
2.如何编写使用联结的SELECT语句
2.1 联结
SOL最强大的功能之一就是能在数据检索查询的执行中联结(join)表。联结是利用SQL的SELECT能执行的最重要的操作
2.1.1 关系表
关系表的设计就是要保证把信息分解成多个表,一类数据一个表。各表通过某些常用的值(即关系设计中的关系 (relational))互相关联。
假如有一个包含产品目录的数据库表,其中每种类别的物品占一行。对于每种物品要存储的信息包括产品描述和价格,以及生产该产品的供应商信息。假如有由同一供应商生产的多种物品,那么在何处存储供应商信息(如供应商名、地址、联系方法等)呢?
答:在这个例子中,可建立两个表,一个存储供应商信息,另一个存储产品信息。vendors表包含所有供应商信息,每个供应商占一行,每个供应商具有唯一的标识。此标识称为主键( primary key)可以是供应商ID或任何其他唯一值。products表只存储产品信息,它除了存储供应商ID (vendors表的主键)外不存储其他供应商信息。vendors表的主键又叫作products的外键,它将vendors表与>products表关联,利用供应商ID能从vendors表中找出相应供应商的详细信息。
这样做的好处如下:
- 供应商信息不重复,从而不浪费时间和空间;
- 如果供应商信息变动,可以只更新vendors表中的单个记录,相关表中的数据不用改动;
- 由于数据无重复,显然数据是一致的,这使得处理数据更简单。
总之,关系数据可以有效地存储和方便地处理。因此,关系数据库的可伸缩性远比非关系数据库要好。
- 外键(foreign key):外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
- 可伸缩性(scale):能够适应不断增加的工作量而不失败
2.1.2 为什么使用联接
如果数据存储在多个表中,怎样用单条SELECT语句检索出数据?答案是使用联结。联结是用来在一条SELECT语句中关联表。使用特殊的语法,可以联结多个表返回一组输出
维护引用完整性
- 联结在实际的数据库表中不存在。联结由MySQL根据需要建立,它存在于查询的执行当中。
- 在使用关系表时,仅在关系列中插入合法的数据非常重要。如果在products表中插入拥有非法供应商ID(即没有在vendors表中出现)的供应商生产的产品,则这些产品是不可访问的,因为它们没有关联到某个供应商。为防止这种情况发生,可指示MySQL只允许在products表的供应商ID列中出现合法值(即出现在vendors表中的供应商)。这就是维护引用完整性,它是通过在表的定义中指定主键和外键来实现的。
2.2 创建联结—WHERE
SELECT vend_name,prod_name,prod_price
FROM vendors,products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend name, prod_name;
FROM子句列出了两个表,分别是vendors和products。它们就是这条SELECT吾句联结的两个表的名字。这两个表用WHERE子句正确联结,WHERE子句旨示MySQL匹配vendors表中的vend_id和products表中的vend_id。这里需要这种完全限定列名vendors.vend_id = products.vend_id
2.2.1 笛卡尔积
笛卡儿积:(cartesian product)由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
SELECT vend_name,prod_name,prod_price
FROM vendors,products
ORDER BY vend_name,prod_name;
这里返回的数据用每个供应商匹配了每个产品,它包括了供应商不正确的产品。
2.2.2 内部联结—INNER JOIN与ON
内部联结:基于两个表之间的相等测试。下面使用不同的语法来明确指定联结的类型
SELECT vend_name,prod_name,prod_price
FROM vendors INNER J0IN products
ON vendors.vend_id = products.vend_id;
这里,两个表之间的关系是FROM子句的组成部分,以INNER JOIN
指定。在使用这种语法时,联结条件用特定的ON子句
而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。
2.2.3 联结多个表
SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则:首先列出所有表,然后定义表之间的关系。
SELECT prod_name,vend_name,prod_price,quantity
FROM orderitems,products, vendors
WHERE products.vend_id = vendors.vend_id AND orderitems.prod_id = products.prod_id AND order_num = 20005;
此例子显示编号为20005的订单中的物品。订单物品存储在orderitems表中。每个产品按其产品ID存储,它引用products表中的产品。这些产品通过供应商ID联结到vendors表中相应的供应商,供应商ID存储在每个产品的记录中。这里的FROM子句列出了3个表,而WHERE子句定义了这两个联结条件,而第三个联结条件用来过滤出订单20005中的物品。
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= 'TNT2';
这里有3个WHERE子句条件。前两个关联联结中的表,后一个过滤产品TNT2的数据。实现之前需要使用子查询才能实现的操作
3.如何对被联结的表使用表别名和聚集函数以及创建高级联结
3.1 使用表别名—AS
别名除了用于列名和计算字段外,SQL还允许给表名起别名。这样做有两个主要理由:
- 缩短SQL语句
- 允许在单条SELECT语句中多次使用相同的表
SELECT cust_name, cust_contact
FROM customers AS c,orders AS o,orderitems AS oi
WHERE c.cust_id = o.cust_id AND oi .order_num = o.order_num AND prod_id = 'TNT2';
表别名不仅能用于WHERE子句,它还可以用于SELECT的列表、ORDER BY子句以及语句的其他部分。应该注意,表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机。
3.2 使用不同类型的联结
下面介绍三种联结:自联结、自然联结和外部联结
3.2.1 自联结
假如你发现某物品(其ID为DTNTR)存在问题,因此想知道生产该物品的供应商生产的其他物品是否也存在这些问题。此查询要求首先找到生产D为DTNTR的物品的供应商,然后找出这个供应商生产的其他物品。
SELECT p1. prod_id,p1.prod_name
FROM products AS p1,products AS p2
WHERE p1.vend_id = p2. vend_id AND p2.prod_id = 'DTNTR';
WHERE(通过匹配p1中的vend id和p2中的vend_id)首先联结两个表,然后按第二个表中的prod_id过滤数据,返回所需的数据。与以下子查询返回的结果一致
SELECT prod_id,prod_name
FROM products
WHERE vend_id = (SELECT vend_id
FROM products
WHERE prod_id ='DTNTR ');
自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。所以用自联结而不用子查询
3.2.2 自然联结
无论何时对表进行联结,应该至少有一个列出现在不止一个表中(被联结的列)。内部联结返回所有数据,甚至相同的列多次出现。自然联结排除多次出现,使每个列只返回一次。这一般是通过对表使用通配符SELECT *
,对所有其他表的列使用明确的子集来完成的。
SELECT c.*,o.order_num,o.order_date,oi.prod_id,oi.quantity,o1.item_price
FROM customers AS c,orders AS o,orderitems AS oi
WHERE c.cust_id = o.cust_id AND oi.order_num = o.order_num AND prod_id = 'FB';
在这个例子中,通配符只对第一个表使用。所有其他列明确列出,所以没有重复的列被检索出来。
3.2.3 外部联结—LEFT OUTER J0IN
联结包含了那些在相关表中没有关联行的行,这种类型的联结称为外部联结。
SELECT customers.cust_id,orders.order_num
FROM customers LEFT OUTER J0IN orders
ON customers.cust_id = orders.cust_id;
在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。上面的例子使用LEFT OUTER JOIN
从FROM子句的左边表(customers表)中选择所有行。为了从右边的表中选择所有行,应该使用RIGHT OUTER JOIN
SELECT customers.cust_id,orders.order_num
FROM customers RIGHT OUTER JOIN orders
ON orders.cust_id = customers.cust_id;
3.3 使用带聚集函数的联结
聚集函数可以与联结一起使用
SELECT customers.cust_name,customers. cust_id,
COUNT(orders.order_num) AS num_ord
FROM customers INNER JOIN orders
ON customers.cust_id = orders. cust_id
GROUP BY customers.cust_id;
此SELECT语句使用INNER JOIN将customers和orders表互相关联。GROUP BY子句按客户分组数据,因此,函数调用COUNT(orders.order_num)
对每个客户的订单计数,将它作为num_ord返回。
SELECT customers.cust_name,customers.cust_id,
COUNT(orders.order_num) AS num_ord
FROM customers LEFT OUTER J0IN orders
ON customers.cust_id = orders.cust_id GROUP BY customers.cust_id;
这个例子使用左外部联结来包含所有客户,甚至包含那些没有任何下订单的客户。结果显示也包含了客户Mouse House,它有0个订单。
3.4 使用联结及联结条件
- 注意所使用的联结类型。一般我们使用内部联结,但使用外部联结也是有效的。
- 保证使用正确的联结条件,否则将返回不正确的数据。应该总是提供联结条件,否则会得出笛卡儿积。
- 在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们前,分别测试每个联结。这将使故障排除更为简单。
4.组合查询—UNION操作符
4.1 组合查询
多数SQL查询都只包含从一个或多个表中返回数据的单条SELECT语句。MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并(union)或复合查询。有两种基本情况需要使用组合查询:
- 在单个查询中从不同的表返回类似结构的数据
- 对单个表执行多个查询,按单个查询返回数据
4.2 创建组合查询
可用UNION操作符
来组合数条SQL查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成单个结果集。
SELECT vend_id, prod_id, prod_price
FROM products
WHERE prod_price<= 5;
第一条SELECT检索价格不高于5的所有物品。
SELECT vend_id,prod_id,prod_price
FROM products
WHERE vend_id IN (1001,1002);
第二条SELECT使用IN找出供应商1001和1002生产的所有物品。
组合语句
SELECT vend_id,prod_id,prod_price
FROM products
WHERE prod_price = 5
UNION
SELECT vend_id,prod_id,prod_price
FROM products
WHERE vend_id IN (1001,1002);
这条语句由前面的两条SELECT语句组成,语句中用UNION关键字
分隔。UNION指示MySOL执行两条SELECT语句,并把输出组合成单个查询结果集。
4.2.1 UNION规则
UNION是非常容易使用的。但有几条规则需要注意:
- UNION必须由两条或两条以上的SELECT语句组成,语句之间用
关键字UNION
分隔(因此,如果组合4条SELECT语句,将要使用3个UNION关键字)。 - UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
- 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。
4.2.2 包含或取消重复的行
UNION从查询结果集中自动去除了重复的行(换句话说,它的行为与单条SELECT语句中使用多个WHERE子句条件一样)。这是UNION的默认行为,但是如果需要,可以改变它。事实上,如果想返回所有匹配行,可使用UNION ALL
而不是UNION
。
SELECT vend_id,prod_id,prod_price
FROM products
WHERE prod_price <= 5
UNION ALL
SELECT vend_id,prod_id,prod_price
FROM products
WHERE vend_id IN (1001,1002);
使用UNION ALL,MySQL不取消重复的行。因此其中有一行出现两次
4.2.3 对组合查询结果排序
SELECT语句的输出用ORDER BY子句
排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句。
SELECT vend_id,prod_id,prod_price
FROM products
WHERE prod_price <= 5
UNION
SELECT vend_id, prod_id,prod_price
FROM products
WHERE vend_id IN (1001,1002)
ORDER BY vend_id,prod_price;
这条UNION在最后一条SELECT语句后使用了ORDER BY子句。虽然ORDER BY子句似乎只是最后一条SELECT语句的组成部分,但实际上MySQL将用它来排序所有SELECT语句返回的所有结果。
使用UNION的组合查询可以组合不同的表
参考于《MYSQL必知必会》
如果对您有帮助,麻烦点赞关注,这真的对我很重要!!!如果需要互关,请评论或者私信!