目录
前言
本文中所有示例均来自于《MySQL必知必会》一书,同时引用了大量书中的话语,本文只是自我学习中对《MySQL必知必会》的一个总结,同时本文的示例不保证可以在其他数据库系统中正确运行(事实上大部分SQL都是可以在其他数据库中运行的)。
感谢作者Ben Forta!
一、SQL是什么?
SQL是结构化查询语言(Structured Query Language)的缩写。SQL是一种专门用来与数据库通信的语言。
二、准备数据
为了更好的说明查询语句的使用,假设系统中已存在如下数据表,并有若干数据存在:
create schema mysql_crash_course;
use mysql_crash_course;
创建customers表
create table customers #客户信息表
(
cust_id int not null auto_increment, #每个表只允许一个AUTO_INCREMENT列
cust_name char(50) not null,
cust_address char(50) null,
cust_city char(50) null,
cust_state char(5) null,
cust_zip char(10) null,
cust_country char (50) null,
cust_contact char(50) null,
cust_email char(255) null,
primary key(cust_id)
)engine=InnoDB;
#创建orders表
create table orders
(
order_num int not null auto_increment,
order_date datetime not null,
cust_id int not null,
primary key(order_num)
)engine=InnoDB;
#订单详情表
create table orderitems
(
order_num int not null,
order_item int not null,
prod_id char(10) not null,
quantity int not null,
item_price decimal(8,2) not null,
primary key(order_num, order_item)
)engine=InnoDB;
#创建vendors表
create table vendors
(
vend_id int not null auto_increment,
vend_name char(50) not null,
vend_address char(50) null,
vend_city char(50) null,
vend_state char(5) null,
vend_zip char(10) null,
vend_country char(50) null,
primary key(vend_id)
)engine=InnoDB;
#创建产品信息表
create table products
(
prod_id char(10) not null,
vend_id int not null,
prod_name char (255) not null,
prod_price decimal(8,2) not null,
prod_desc text null,
primary key(prod_id)
)engine=InnoDB;
#更改表-添加外键约束
alter table orders add constraint fk_orders_customers foreign key(cust_id) references customers(cust_id);
alter table orderitems add constraint fk_orderitems_orders foreign key(order_num) references orders(order_num);
alter table orderitems add constraint fk_orderitems_products foreign key(prod_id) references products(prod_id);
alter table products add constraint fk_products_vendors foreign key(vend_id) references vendors(vend_id);
#插入数据
insert into customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
values(10001, 'Coyote Inc.', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'Y Lee', 'ylee@coyote.com');
insert into customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
values(10002, 'Mouse House', '333 Fromage Lane', 'Columbus', 'OH', '43333', 'USA', 'Jerry Mouse');
insert into customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
values(10003, 'Wascals', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', 'rabbit@wascally.com');
insert into customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
values(10004, 'Yosemite Place', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Y Sam', 'sam@yosemite.com');
insert into customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
values(10005, 'E Fudd', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'E Fudd');
insert into vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
values(1001,'Anvils R Us','123 Main Street','Southfield','MI','48075', 'USA');
insert into vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
values(1002,'LT Supplies','500 Park Street','Anytown','OH','44333', 'USA');
insert into vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
values(1003,'ACME','555 High Street','Los Angeles','CA','90046', 'USA');
insert into vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
values(1004,'Furball Inc.','1000 5th Avenue','New York','NY','11111', 'USA');
insert into vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
values(1005,'Jet Set','42 Galaxy Road','London', NULL,'N16 6PS', 'England');
insert into vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
values(1006,'Jouets Et Ours','1 Rue Amusement','Paris', NULL,'45678', 'France');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('ANV01', 1001, '.5 ton anvil', 5.99, '.5 ton anvil, black, complete with handy hook');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('ANV02', 1001, '1 ton anvil', 9.99, '1 ton anvil, black, complete with handy hook and carrying case');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('ANV03', 1001, '2 ton anvil', 14.99, '2 ton anvil, black, complete with handy hook and carrying case');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('OL1', 1002, 'Oil can', 8.99, 'Oil can, red');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('FU1', 1002, 'Fuses', 3.42, '1 dozen, extra long');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('SLING', 1003, 'Sling', 4.49, 'Sling, one size fits all');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('TNT1', 1003, 'TNT (1 stick)', 2.50, 'TNT, red, single stick');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('TNT2', 1003, 'TNT (5 sticks)', 10, 'TNT, red, pack of 10 sticks');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('FB', 1003, 'Bird seed', 10, 'Large bag (suitable for road runners)');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('FC', 1003, 'Carrots', 2.50, 'Carrots (rabbit hunting season only)');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('SAFE', 1003, 'Safe', 50, 'Safe with combination lock');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('DTNTR', 1003, 'Detonator', 13, 'Detonator (plunger powered), fuses not included');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('JP1000', 1005, 'JetPack 1000', 35, 'JetPack 1000, intended for single use');
insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)
values('JP2000', 1005, 'JetPack 2000', 55, 'JetPack 2000, multi-use');
insert into orders(order_num, order_date, cust_id)
values(20005, '2005-09-01', 10001);
insert into orders(order_num, order_date, cust_id)
values(20006, '2005-09-12', 10003);
insert into orders(order_num, order_date, cust_id)
values(20007, '2005-09-30', 10004);
insert into orders(order_num, order_date, cust_id)
values(20008, '2005-10-03', 10005);
insert into orders(order_num, order_date, cust_id)
values(20009, '2005-10-08', 10001);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20005, 1, 'ANV01', 10, 5.99);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20005, 2, 'ANV02', 3, 9.99);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20005, 3, 'TNT2', 5, 10);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20005, 4, 'FB', 1, 10);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20006, 1, 'JP2000', 1, 55);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20007, 1, 'TNT2', 100, 10);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20008, 1, 'FC', 50, 2.50);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20009, 1, 'FB', 1, 10);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20009, 2, 'OL1', 1, 8.99);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20009, 3, 'SLING', 1, 4.49);
insert into orderitems(order_num, order_item, prod_id, quantity, item_price)
values(20009, 4, 'ANV03', 1, 14.99);
customers表用来存放客户信息,cust_id作为主键。
vendors表存放供应商信息,vend_id作为主键。
products表存放产品信息,prod_id作为主键,vend_id为外键,references by vendors.vend_id;
orders表用来存放客户订单信息,order_num作为主键,cust_id为外键,references by customers.cust_id;
orderitems表用来存放订单详情,使用order_num与order_item作为主键,prod_id为外键,references by products.prod_id;
三、常用查询语句
1.简单查询语句
● 查询单列数据
select cust_name from customers;
从customers表中查询cust_name列。返回的数据是未排序的(可通过order by指定排序顺序,后续介绍),返回数据的顺序可能是数据被添加到表中的顺序,也可能不是。只要返回相同数目的行,就是正常的。
默认下,NULL值也会被检索出来。
● 查询多列
select cust_id,cust_name,cust_email from customers;
从customers表中查询cust_id列、cust_name列及cust_email列。列与列之间用','隔开。
● 查询所有列
使用*通配符可查询customers所有列。
select * from customers;
或者显式指定所有列名。
select cust_id,cust_name,cust_address,cust_city,cust_state,cust_zip,
cust_country,cust_contact,cust_email from customers;
*注意:一般,除非你确实需要表中的每个列,否则最好别使用*通配符。
● 查询列时去重
默认select会返回所有匹配的行,但很多时候我们只需要查询某一列中的数据出现了哪些值,而并不需要把重复出现的值列出来,此时如果想要使某一列值只出现一次,则可以使用distinct去重。如下所示:我们需要查询客户表中的客户都来自哪些国家?
select distinct cust_country from customers;
在查询cust_country列数据时使用distinct,则'USA'只会出现一次。否则,
*注意:distinct关键字应用于所有列而不仅是前置它的那一列。除非不同行中指定的所有列值都同,否则它们都将被查询出来。
● 限制返回结果数量(mysql)
默认select返回所有匹配的行,但有时我们只需要前几行或某几行时,可以使用limit指定。
select * from customers limit 2;
表示只返回两行数据。
select * from customers limit 0,1;
表示从第0行开始,返回1行数据。
*注意:
1.行的行号是从0开始。
2.在行数不够时LIMIT中指定要检索的行数为检索的最大行数。如果没有足够的行(例如,给出LIMIT 10,5,但只有13行),MySQL将只返回它能返回的那么多行。
● 查询时限定表名
select customers.cust_id from customers;
可以在查循时通过表明.列名来指定要查询的列。在某些情形下需要使用完全限定表名。
●对查询结果进行排序
通过order by指定查询结果按照某一列排序,order by默认按照升序排列。
select * from customers order by cust_id;
也可手动指定升序或降序,使用asc表示升序,使用desc表示降序。
select * from customers order by cust_id asc; #升序
select * from customers order by cust_id desc; #降序
也可以按照多列排序,同时对每一列指定排序方向。
select * from customers order by cust_country asc, cust_name desc;
此时,对查询的结果先按照cust_country升序排序,当cust_country相同时,则按照cust_name降序排序。
2.查询时过滤数据
大部分情况下,我们查询数据时,只需要根据某些条件筛选出来的数据,此时我们需要使用where子句来指定过滤条件。
● 使用where
select prod_name,prod_price from products where prod_price=2.5;
此时,只返回cust_id为10002的行。
除了=操作符,where中的条件判断支持还如下操作符:
= | 等于 |
<> | 不等于 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
between | 在指定的两个值之间(包括左右范围) |
示例如下:
select * from customers where cust_id < 10003;
select * from customers where cust_id <= 10003;
select * from customers where cust_id > 10003;
select * from customers where cust_id >= 10003;
select * from customers where cust_id <> 10003;
select * from customers where cust_id != 10003;
select * from customers where cust_id between 10001 and 10002;
注意1:
select * from customers where cust_country='USA'
如果将值与串类型的列进行比较,则需要限定引号。在检测字符串相等时,mysql在执行匹配时默认不区分大小写(可以更改配置使系统区分大小写),所以='USA' 和 ='usa'的结果是一样的。
注意2:
select语句有一个特殊的where子句,可用来检查是否为null值的列。这个where子句就是is null(或is not null)子句。
select * from customers where cust_email is null;
将会筛选出cust_email为空的行。
select * from customers where cust_email is not null;
相反,此条语句将会筛选出cust_email不为空的行。
● 使用and组合多个过滤条件
假如在查询时需要设置多个条件,可以使用and组合过滤条件,查询结果行必须满足所有条件。
select * from products where vend_id=1003 and prod_price<10;
本条语句中,结果行必须满足vend_id为1003且prod_price<10。
● 使用or组合多个过滤条件
使用or组合条件时,查询结果只需满足任一条件即可。
select * from customers where cust_id = 10001 or cust_id = 10002;
本条语句中,结果行中vend_id为1002或1003即可。
注意: 当where子句中同时含有and和or操作符时,应该使用圆括号明确地分组操作符。
select prod_name, prod_price from products
where (vend_id = 1002 or vend_id = 1003) and prod_price >= 10;
假如本条语句中没有"()"明确分组操作,则会出现错误结果。所以不要过分依赖默认计算次序,即使它确实是你想要的东西也是如此。使用圆括号没有什么坏处,它能消除歧义。
● 使用in
in操作符用来指定条件范围,范围中的每个条件都可以进行匹配。in合法值是由圆括号包含,逗号分隔的列表。
select * from customers where cust_id in (10001, 10002);
in的功能与利用or组合多个相等判断条件的功能是一致的。与实现了相同功能的or语句相比,它有许多优点:
a.in操作符的语法更清楚且更直观。
b.在使用in时,计算的次序更容易管理(因为使用的操作符更少)。
c.in操作符一般比OR操作符清单执行更快。
d.in的最大优点是可以包含其他select语句,使得能够更动态地建立where子句。
● 使用not
not操作符用来否定它之后所跟的任何条件。
select * from customers where cust_id not in (10001, 10002);
3.like与通配符(% _)
对于很多时候,在字符串中查找是很重要的功能,但有时候我们无法确定用来比较的确切值,比如,如何搜索客户名中包含'M'的所有客户信息呢?此时需要借助like操作符和通配符。
● %
在搜索串中,%代表搜索模式中给定位置的0个、1个或多个字符。
select * from customers where cust_name like 'M%'
如上语句代表,查找以客户名'M'开头的所有客户信息。(以'M'为开头,M后面的%代表M之后可以有0、1或多个字符)。
select cust_id from customers where cust_name like '%M%'
如上语句代表,查找客户名中含有'M'的所有客户信息。('M'前或后的%代表M前或后都可以有0、1或多个字符)。
● _
下划线的用途与%一样,但下划线只匹配单个字符而不是多个字符。
select * from customers where cust_country like 'U_A';
如上条语句,查找cust_counstry以U开头,以A结尾,且中间只有一个字符的客户信息。
注意:
通配符很有用。但这种功能是有代价的:通配符搜索的处理一般要比前面讨论的其他搜索所花时间更长。使用通配符时应注意如下原则:
a.不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
b.在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
c.仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
4.as
as可以为 表名称 或 列名称 指定别名。
1.为列名指定别名
select prod_id,quantity,item_price,quantity*item_price as expanded_price
from orderitems where order_num=20005;
本条语句中,将orderitems中的quantity与item_price的乘积作为新的列,命名为expanded_price。
2.为表名指定别名
select cust_name,cust_email from customers as c where c.cust_id=10001;
本条语句中,将customers重命名为c,where子句中使用别名c。
5.聚集函数
有些时候我们只需汇总数据而不用把它们实际检索出来,此时聚集函数就是发挥作用的时候了。大部分数据库都支持以下的函数:
avg() | 返回某一列的平均值,忽略NULL值 |
count() | 返回某一列的行数(是否忽略NULL值见下文) |
max() | 返回某一列的最大值,忽略NULL值 |
min() | 返回某一列的最小值,忽略NULL值 |
sum() | 返回某一列数据之和,忽略NULL值 |
● avg()
select avg(prod_price) as avg_prod_price from products;
从products中计算prod_price的平均值。
select avg(prod_price) as avg_prod_price from products where vend_id=1003;
从products中计算vend_id为1003的产品的prod_price的平均值。
avg()函数会忽略值为NULL的行。
● count()
select count(*) as cust_num from customers;
统计customers的所有行数。
select count(cust_email) from customers;
统计cust_email的行数。
*注意:
在使用count时,count(*)对表中行的数目进行计数,不管表列中包含的是空值(NULL)还是非空值,而count(column)对特定列中具有值的行进行计数,忽略NULL值。
● max()
select max(prod_price) from products;
返回products中prod_price最大的值。
● min()
select min(prod_price) from products;
返回products中prod_price最小的值。
● sum()
select sum(quantity*item_price) as total_price from orderitems
where order_num=20005;
返回order_num=20005的订单的总额。
6.数据分组
有时候,我们也需要对数据按照某一属性分组,比如,我们想要对products中的数据按照不同的vend_id进行分组,并统计每个vend_id提供的产品数目,此时需要使用group by子句。
● group by
select vend_id,count(*) as num_prods from products group by vend_id;
本条语句中,首先按照vend_id进行分组,然后在每个分组中统计行数。
● having
在我们需要过滤分组时,having就是利器,having用于对分组进行过滤(where类似,但where过滤行而不是分组)。
select vend_id,count(*) as num_prods from products group by vend_id
having count(*) > 2;
本条语句与上条语句不同的是,本语句只会筛选出分组中行数>2的分组。
having支持所有where操作符,having和where的差别在:where在数据分组前进行过滤,having在数据分组后进行过滤。
● 同时使用having和where
select vend_id,count(*) from products where prod_price group by vend_id
having count(*) >= 2;
7.使用子查询
● 子查询过滤数据
假设现在有如下需求,需要找出订购产品prod_id为'TNT2'的所有客户,应如何查找呢?
1.从orderitems中找到prod_id='TNT2'的所有订单的order_num。
select order_num from orderitems where prod_id='TNT2';
2. 根据1中的order_num在orders中找到所有客户的客户id。
select cust_id from orders where order_num in
(select order_num from orderitems where prod_id='TNT2');
此时,前面提到的in将会发挥作用,它把一条select语句返回的结果用于另一条select语句的where子句,构成新的select语句。
3.根据2中的cust_id在customters表中查找客户信息。
select * from customers where cust_id
in (select cust_id from orders where order_num in
(select order_num from orderitems where prod_id='TNT2'));
和语句2一样,语句3利用in将语句2的结果用于where子句,完成整个查询。
● 子查询作为新的列
假如有如下需求,我们想知道customers表中每个客户的订单总数。则也可以使用子查询完成。
1.从customers表中查找所有客户信息。
2.对于每个客户,统计在orders表中的订单数量。
select cust_id,cust_name,(select count(*) from orders where
orders.cust_id=customers.cust_id) as order_num from customers;
8.联结表
联结可以说是sql最强大的功能。
● 等值联结(内部联结)
select vend_name, prod_name, prod_price from vendors, products
where vendors.vend_id=products.vend_id order by vend_name,prod_name;
本条语句查询每个供应商供应的所有产品的价格。如果没有where后的联结条件的表,则返回的结果为两个表的笛卡儿积。
如上这种基于两个表之间的相等测试的联结被称为等值联结。其实可以使用inner join来明确指定联结类型。以下语句实现了完全相同的功能:
select vend_name,prod_name,prod_price from vendors
inner join products on vendors.vend_id=products.vend_id;
平常使用中,我们应该首选inner join语法。
我们在子查询中实现过这样一个需求:需要找出订购产品prod_id为'TNT2'的所有客户,在子查询章节中如下的语句:
select * 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 customers.* from customers,orders,orderitems
where customers.cust_id=orders.cust_id and
orders.order_num=orderitems.order_num and
orderitems.prod_id='TNT2';
也可使用inner join实现联结:
select customers.cust_id,customers.cust_name from customers inner join
orders on customers.cust_id=orders.cust_id inner join
orderitems on orders.order_num=orderitems.order_num and orderitems.prod_id='TNT2';
● 自联结
假如你发现某物品(prod_id为'DTNTR')存在问题,因此想知道生产该物品的供应商生产的其他物品是否也存在这些问题。
1.我们可以使用子查询来实现。
select * from products where products.vend_id in
(select vend_id from products where products.prod_id='DTNTR');
2.使用自联结实现
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';
本条查询语句中需要的两个表实际上是相同的表,因此products表在from子句中出现了两次。虽然这是完全合法的,但对products的引用具有二义性,因为MySQL不知道你引用的是products表中的哪个实例。为了解决此问题,我们使用了表别名。products的第一次出现为别名p1,第二次出现为别名p2。现在可以将这些别名用作表名。例如,select语句使用p1前缀明确地给出所需列的全名。如果不这样,MySQL将返回错误,因为分别存在两个名为prod_id、prod_name的列。MySQL不知道想要的是哪一个列(即使它们事实上是同一个列)。where(通过匹配p1中的vend_id和p2中的vend_id)首先联结两个表,然后按第二个表中的prod_id过滤数据,返回所需的数据。自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。
● 自然联结
自然联结是一种特殊的等值连接,它要求两个关系中进行比较的列必须是相同的属性组,并且在结果中把重复的属性列去掉,使每个列只返回一次。
我们一般是通过对一个表使用通配符(select*),对所有其他表的列使用明确的子集来完成的。下面举一个例子:
select c.*, o.order_num, o.order_date,oi.prod_id, oi.quantity,oi.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';
在这个例子中,通配符只对第一个表使用。所有其他列明确列出,所以没有重复的列被检索出来。事实上,迄今为止我们建立的每个内部联结都是自然联结,很可能我们永远都不会用到不是自然联结的内部联结(因为在实际中,通常我们在联结时比较的两个列都是具有相同的属性组时才有实际意义)。
● 外部联结
内部联结是基于两个表中相同属性的列值相同产生关联行,但有时候有时候我们也会需要包含没有关联行的那些行。举个例子:
我们想要统计每个客户下了哪些订单,包括那些至今尚未下订单的客户。
假如我们使用inner join,如下:
select customers.cust_id,order_num from customers inner join orders on
customers.cust_id=orders.cust_id order by customers.cust_id;
本条语句,我们在inner join中基customers.cust_id=orders.cust_id这个筛选条件,成功的筛选出了有客户下单的数据,但是还有未下单的客户,它们在的cust_id并未出现在orders表中,自然也就无法在结果中显示出来,如果想要做到显示未关联的行,此时需要用到外部联结,如下:
select customers.cust_id,order_num from customers
left outer join orders on customers.cust_id=orders.cust_id
order by customers.cust_id;
我们使用outer join指定使用外部联结,以上语句,外部联结在结果上来说,除了包含等值测试(customers.cust_id=orders.cust_id)中的行,还包括cust_id未出现在orders表中的行,我们在指定outer join时,还需要指定left 或 right关键字,left 代表join从from子句的左边表(customers表)中选择所有行,right则表示从rom子句的右边表选择所有行。
假设我们把上面的语句改用right outer join,如下:
select customers.cust_id,order_num from customers
right outer join orders on customers.cust_id=orders.cust_id
order by customers.cust_id;
则输出结果中,将会以orders.cust_id作为结果行中cust_id列,因为我们orders表中只有下单用户的cust_id,所以改为right outer join后,并不会统计未下单的用户。
从使用来说,左外部联结和右外部联结之间的唯一差别是所关联的表的顺序不同。换句话说,左外部联结可通过颠倒from或where子句中表的顺序转换为右外部联结。
● 带聚集函数的联结
现在,我们又有一个需求, 我们想要统计每个客户下了订单的数目,该如何解决呢?
我们之前统计过每个客户下了哪些订单,如下:
select customers.cust_id,order_num from customers inner join orders on
customers.cust_id=orders.cust_id order by customers.cust_id;
那么结合之前的group by,在这个结果的基础上,用cust_id做group,然后利用count统计每个分组的order_num数目,就可得到每个客户的订单数,综合上述:
select customers.cust_id,count(order_num) from customers left join orders on
customers.cust_id=orders.cust_id group by cust_id order by customers.cust_id;
9.union
在union之前,我们的查询结果都是从单条select中得到的,但是sql中也允许我们执行多条查询并将结果作为单个结果集返回,这个就是union操作。
举个例子,假如我们现在需要价格小于等于5的所有物品的一个列表,而且还想包括供应商1001和1002生产的所有物品(不考虑价格)。当然,我们现在的第一反应是利用where做筛选,不过这次我们将使用union。我们将需求分解如下:
1.查找出价格小于5的物品列表,如下:
select vend_id,prod_id,prod_price from products where prod_price < 5;
2.查找供应商为1001和1002的物品列表,如下:
select vend_id,prod_id,prod_price from products where vend_id in(1001, 1002);
3.将两条select使用union组合起来,完成整个查询,如下:
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);
在union中,默认会自动去除重复的行,如果想要返回所有行,则需要使用union all。将上条中union语句改为union all,会发现有重复的行出现。一般情况下,union完成的工作和where相同,但是如果需要返回重复行,则需要使用union all。
union的结果也可以使用order by排序,但是只能使用一条order by子句,不允许对每个select都使用order by。
注意:union使用有如下规则我们需要遵守:
a.union必须由两条或两条以上的select语句组成,语句之间用关键字union分隔(因此,如果组合4条select语句,将要使用3个union关键字)。
b.union中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
c.列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。
总结
纸上得来终觉浅,觉知此事要躬行!