SQL 最强大的功能之一就是在查询数据时能够连接多个表。在 SQL 查询语句中,连接是非常重要的操作。通过连接可以实现更多、更复杂的查询。本章将会对 SQL 中的多表连接和组合查询进行介绍,其中主要包括内连接、外连接、自连接和组合查询等。通过对本章的学习,读者将会对比较复杂的数据查询有更深入的了解。
本章需要的数据库文件下载地址如下:数据库源文件及日志文件
空值从技术上来说就是 未知的值
。但空值并不包括零、一个或者多个空格组成的字符串,以及零长度的字符串。在实际应用中,空值说明还没有向数据库中输入相应的数据,或者某个特定的记录行不需要使用该列。在实际的操作中下列几种情况可使一列成为空值。
本节将对空值的查询、转换为有效值等操作进行详细的介绍。
1. 查询空值(IS NULL)
在表中有可能会出现空值,由于空值是未知的值,在对其进行查询时不可以像查询其他值那样使用比较或运算等。例如,在查询某列是否存在空值时不可以使用 WHERE 列名=NULL
这种写法,这种写法是不正确的,此时可以使用 IS NULL 操作符对空值进行查询。IS NULL 操作符可以查询出某列中所有的空值。
【示例01】在 users 用户信息表中,查询出昵称(nickname)为空值的用户id (user_id)、注册邮箱(email)和昵称(nickname)。SQL 语句如下:
SELECT user_id,email,nickname
FROM users
WHERE nickname is NULL;
在查询空值时可以使用 IS NULL 操作符,而在查询非空值时可以使用 IS NOT NULL 操作符来实现。
【示例02】在 users 用户信息表中,查询出昵称(nickname)不为空值的用户id (user_id)、注册邮箱(email)和昵称(nickname)。SQL 语句如下:
SELECT user_id,email,nickname
FROM users
WHERE nickname is NOT NULL;
在实际使用数据表中的数据时,通常需要将空值转换为一个有效的值,以便于对数据的理解,或者防止表达式出错。SQL 提供了几个专门用来处理空值的函数。ISNULL() 函数可以将空值转换为有效的值,而 NULLIF() 函数可以根据指定的条件来生成空值。下面通过几个示例演示如何对空值进行处理。
【示例03】在 bookinfo_zerobasis 零基础系列图书信息表中,查询所有图书名称(BookName)、图书作者(Writer),并将新书(newbook)列中的所有空值转换为有效值 0。SQL 语句如下:
SELECT BOOKName,ISNULL(newbook,0) AS newbook
FROM bookinfo_zerobasis;
【示例04】在 user 用户信息表中,查询所有的用户id(user_id)、注册邮箱(email) 和昵称(nickname),并将昵称列中的 Andy
转换为 NULL。SQL 语句如下:
SELECT user_id,email,NULLIF(nickname,'ANDY') AS nickname
FROM users;
Xk-数据库【练习1】查询学生选课表(StuCou) 中随机数为空的课程信息。
内连接就是使用比较运算符进行表与表之间列数据的比较操作,并列出这些表中与连接条件相匹配的数据行。内连接可以用来组合两个或者多个表中的数据。如下图所示:
在内连接中,根据使用的比较方式不同,可以将内连接分为以下 3 种。
1、等值连接
等值连接是指在连接条件中使用 =
运算符比较被连接的列。在连接条件中的各个连接列的类型必须是可比的,但不一定是相同的。例如:可以都是字符型或者都是日期型;也可以一个是整型,一个是实型,因为它们都是数值型。虽然连接条件中各列的类型可以不同,但是在应用中最好还是使用相同的类型,因为系统在进行类型转换时要花费很多时间。
【示例01】在 goods 商品信息表和 goods_type 商品类型表中,通过等值连接查询每个商品的商品id(goods_id)、商品名称(goods_name)和商品所属类型(name)。SQL 语句如下:
SELECT goods_id,goods_name,name
FROM goods,goods_type
WHERE goods.goods_type=goods_type.id;
查询结果如下图所示:
说明:WHERE 子句作为过滤条件是非常重要的。在关系数据库中表就是一个联合,当从两个(或多个)表中查询数据时,如果没有指定条件,那么这种查询结果就是笛卡尔乘积。笛卡尔乘积就是从多个表中提取数据时,在 WHERE 子句中没有指定多个表的公共关系。例如: A 表中有 M 条记录,B 表中有 N 条记录,其笛卡儿乘积是 M*N 条记录。显然这不是我们想要的,因此要保证所有连接都有 WHERE 子句。
对于等值连接还可以使用一种特定的语法,明确指定连接的类型。具体语法如下:
SELECT fieldlist FROM
table1 [INNER] JOIN table2
ON table1.column=table2.column
参数说明:
例如,下面的 SELECT 语句返回与【示例01】完全相同的数据。SQL 语句如下:
SELECT goods_id,goods_name,name
FROM goods INNER JOIN goods_type
ON goods.goods_type=goods_type.id;
在使用这种语法时,连接条件用特定的 ON 子句,而不是 WHERE 子句。传递给 ON 的连接条件和传递给 WHERE 的连接条件相同。
2、不等值连接
在 SQL 中既支持等值连接,也支持不等值连接。不等值连接是指在连接条件中使用除了等于运算符以外的其他比较运算符比较被连接的列值。可以使用的运算符包括:>、>=、<=、<、!>、!< 和 <>。
【示例02】在 goods 商品信息表和 goods_type 商品类型表中,通过不等值连接查询商品类型不是平板电脑的商品 id(goods_id) 和商品名称(goods_name)。SQL 语句如下:
SELECT a.goods_id, a.goods_name
FROM goods a INNER JOIN (SELECT * FROM goods_type WHERE name='平板电脑') b
ON a.goods_type<>b.id;
3、自然连接
自然连接是等值连接的一种特殊形式。如果是按照两个表中的相同属性进行等值连接,且目标中去除重复的列,保留所有不重复的列,则可以称之为自然连接。自然连接只有在两个表中有相同名称的列且列的含义相似时才能使用。
【示例03】在 users 用户信息表和 user_address 用户地址表中,通过自然连接查询用户的用户 id(user _id)、用户地址(address)和最后登录时间( last_login)。 SQL 语句如下:
SELECT a.user_id,b.address,CONVERT(VARCHAR(10),last_login,120) AS last_login
FROM users a, user_address b
WHERE a.user_id=b.user_id;
聚合函数用来汇总数据。前面使用聚合函数的例子都是从一个表中汇总数据,但是这些函数也可以和连接一起使用。
【示例04】在 goods_category 商品分类和 goods 商品信息表中,通过内连接查询商品分类id(id)、商品分类名称(name)和对应商品的数量。SQL 语句如下:
SELECT a.id,a.name,COUNT(b.cat_id) num
FROM goods_category a INNER JOIN goods b
ON a.id=b.cat_id GROUP BY a.id,a.name;
SQL 不会限制一条 SELECT 查询语句中可以连接的表的数量。在创建多个表的连接查询时,首先定义要查询的列,然后通过各个表之间的关系定义连接条件。
【示例05】在 goods 商品信息表、brand 商品品牌表和 goods_type 商品类型表中,通过内连接查询商品的商品id(goods_id)、商品名称(goods_name)、商品品牌(name)和商品类型(name)。SQL 语句如下:
SELECT a.goods_id,a.goods_name,b.name,c.name type
FROM goods a, brand b, goods_type c
WHERE a.brand_id=b.id AND a.goods_type=c.id;
查询结果如下:
注意:在使用中如果为表指定了别名,那么在该 SQL 语句中对该表的所有显式引用都必须使用别名,而不能使用表名。如果连接中的多个表中有相同名称的列存在,要求必须使用表名或别名来限定列名(表名.列名)。
通过内连接可以返回所有满足连接条件的记录。而有时则需要显示表中的所有记录,包括那些不符合连接条件的记录,此时就需要使用外连接。使用外连接可以方便地在连接结果中包含某个表中的其他记录。外连接的查询结果是内连接查询结果的扩展。
外连接一个显著的特点就是将某些不满足连接条件的数据也在连接结果中输出。外连接以指定的数据表为主体,将主体表中不满足连接条件的数据也一并输出。根据外连接保存下来的行的不同,可以将外连接分为以下 3 种连接:
在连接语句中,JOIN 关键字左边的表示左表,右边的表示右表。
1. 左外连接
左外连接保留了第一个表的所有行,但只包含第二个表与第一个表匹配的行。第二个表的相应空行被放入 NULL 值。左外连接的语法如下:
SELECT fieldlist FROM
table1 LEFT JOIN table2
ON table1.column=table2.column
【示例06】在 goods 商品信息表和 goods_type 商品类型表中,通过左外连接查询每个商品的商品id(goods_id)、商品名称(goods_name) 和商品所属类型(name)。SQL 语句如下:
SELECT a.goods_id,a.goods_name,b.name
FROM goods a LEFT JOIN goods_type b
ON a.goods_type = b.id ORDER BY a.goods_id DESC;
右外连接保留了第二个表的所有行,但只包含第一个表与第二个表匹配的行。第一个表的相应空行被放入 NULL 值。右外连接的语法如下:
SELECT fieldlist FROM
table1 RIGHT JOIN table2
ON table1.column=table2.column
【示例07】在 goods 商品信息表和 goods_type 商品类型表中,通过右外连接查询每个商品的商品id(goods_id)、商品名称(goods_name) 和商品所属类型(name)。SQL 语句如下:
SELECT a.goods_id,a.goods_name,b.name
FROM goods a RIGHT JOIN goods_type b
ON a.goods_type = b.id;
查询结果如下图所示:
说明:左外连接和右外连接之间唯一的区别就是所关联的表的顺序,换句话说,通过调整 FROM 子句中表的顺序,就可以使左外连接转换为右外连接。因此,这两种外连接可以互换使用。
3. 全外连接
全外连接是将两个表所有的行都显示在结果中。返回的结果除内连接的数据外,还包括两个表中不符合条件的数据,并在左表或右表的相应列中放入 NULL 值。全外连接的语法如下:
SELECT fieldlist FROM
table1 FULL JOIN table2
ON table1.column=table2.column
【示例08】在 goods 商品信息表和 goods_type 商品类型表中,通过全外连接查询每个商品的商品id(goods_id)、商品名称(goods_name) 和商品所属类型(name)。SQL 语句如下:
SELECT a.goods_id,a.goods_name,b.name
FROM goods a FULL JOIN goods_type b
ON a.goods_type = b.id ORDER BY a.goods_id;
【示例09】通过左外连接查询多个表中的数据信息,将 goods 商品信息表、 brand 商品品牌表和 goods_type 商品类型表中的数据通过左外连接汇总显示出来。其中使用了两次左外连接查询,即商品信息表与商品品牌表进行左外连接后再与商品类型表进行左外连接,将商品信息表中的商品id (goods_id)、商品名称(goods_name) 和商品品牌表中的商品品牌(name)以及商品类型表中的商品所属类型(name)全部显示出来。SQL 语句如下:
SELECT goods.goods_id,goods_name,brand.name brand,goods_type.name type
FROM (goods LEFT JOIN brand ON goods.brand_id=brand.id)
LEFT JOIN goods_type ON goods.goods_type=goods_type.id;
1. 自连接
自连接是指一个表同自身进行连接。为了更好地理解自连接,可以把一个表想象成两个独立的表。而在 FROM 子句中表被列出了两次,为了区别,必须给每个表提供一个别名来区别这两个副本。
【示例10】在 brand 商品品牌表中,通过自连接查询与 OPPO
是同一品牌分类的所有品牌id(id)、品牌名称(name)和品牌分类名称(cat_name) 。SQL 语句如下:
SELECT b1.id,b1.name,b1.cat_name
FROM brand b1,brand b2
WHERE b1.cat_name=b2.cat_name
AND b2.name='OPPO';
查询结果如下图所示:
在上述 SQL 语句中,首先找出品牌名称为 OPPO
所在的品牌分类,然后再找出该分类下的所有品牌名称。
2. 交叉连接
交叉连接是两个表的笛卡儿乘积的另一个名称。交叉连接会将第一个表的每一行与第二个表的每一行相匹配,这导致了所有可能的合并。在交叉连接中的列是原表中列的数量的总和(相加);交叉连接中的行是原表中的行数的积(相乘)。交叉连接的操作通过 CROSS JOIN 关键字来完成,并且忽略 ON 条件。交叉连接的语法如下:
SELECT fieldlist FROM
table1 CROSS JOIN table2
【示例11】在 brand 商品品牌表和 goods 商品信息表中,通过交叉连接查询品牌名称(name)、品牌分类名称(cat_name) 和 商品名称(goods_name)。SQL 语句如下:
SELECT a.name,a.cat_name,b.goods_name
FROM brand a CROSS JOIN goods b;
组合查询通过 UNION 操作符来完成。使用 UNION 操作符可以执行多个 SELECT 查询语句,并将多个查询的结果作为一个查询结果集返回。在使用 UNION 操作符时应注意以下几点。
说明:在使用 UNION 进行组合查询时,可以对一个表进行多个查询,也可以对不同的表进行多个查询。
1. 通过UNION合并多个结果集
使用 UNION 操作符进行组合查询很简单,只需要给出每个 SELECT 语句,并在各个语句之间加上关键字 UNION。
【示例12】在 goods 商品信息表中,首先查询商品分类id (cat_id) 为123和131 的所有商品信息,然后查询商品名称(goods name)包含 华为
的所有商品信息,并对两个查询结果进行合并。SQL 语句如下:
SELECT cat_id,goods_name,shop_price
FROM goods
WHERE cat_id IN(123,131)
UNION
SELECT cat_id,goods_name,shop_price
FROM goods
WHERE goods_name LIKE '%华为%';
查询结果如下图所示:
【示例13】在 bookinfo 图书信息表中查询图书价格(Price) 为 59.80 元的图书信息,在 bookinfo_zerobasis 零基础系列图书信息表中查询图书价格(Price)为 69.80 元的图书信息,并对两个查询结果进行合并。SQL 语句如下:
SELECT BookName,Price
FROM bookinfo
WHERE Price=59.80
UNION
SELECT BookName,Price
FROM bookinfo_zerobasis
WHERE price=69.80
查询结果如下图所示:
2. 通过 UNION ALL 返回重复的行
在 【示例12】中,第一个 SELECT 语句的查询结果返回 4 行商品信息,第二个 SELECT 语句的查询结果返回 3 行商品信息,而在应用 UNION 操作符组合查询后,只返回 6 行商品信息。由此可见,使用 UNION 操作符会从最后的结果集中自动去除重复的行。如果希望返回重复的行,需要使用 UNION ALL 而不是 UNION。
【示例14】在 bookinfo_zerobasis 零基础系列图书信息表中,查询图书价格 (Price)为 69.80 元和出版日期(pDate)为 2017年9月 的所有图书信息,并使用 UNION ALL 对两个查询结果进行合并。SQL 语句如下:
SELECT BookName,Price,pDate
FROM bookinfo_zerobasis
WHERE Price=69.80
UNION ALL
SELECT BookName,Price,pDate
FROM bookinfo_zerobasis
WHERE pDate='2017年9月';
在使用 UNION 操作符进行组合查询时,查询结果将对 SELECT 列表中的列按照从左到右的顺序自动排序。在使用 UNION 操作符进行组合查询时,只能使用一个 ORDER BY 子句,而且该子句必须放在最后一个 SELECT 语句之后,所使用的排序列名必须是第一个 SELECT 语句中的列名。
【示例15】对【示例12】中返回的组合查询结果按照商品的本店价格(shop_price)进行排序。SQL 语句如下:
SELECT cat_id,goods_name,shop_price
FROM goods
WHERE cat_id IN(123,131)
UNION
SELECT cat_id,goods_name,shop_price
FROM goods
WHERE goods_name LIKE '%华为%' ORDER BY shop_price;
查询结果如下图所示:
使用 Xk-数据库
【练习2】查询学生表(Student) 和班级表(Class) 的信息(使用交叉连接查询)。
【练习3】查询学生基本信息和所在班级的信息。
【练习4】查询学生基本信息和学生所在班级名称。
【练习5】查询学生的选课信息,要求显示学号、姓名、课程编号、课程名称、志愿号,并按照学号升序排列,当学号相同时则按照志愿号升序排列。
【练习6】查询学生报名 计算机应用工程系
开设的选修课程情况,显示信息包括学生姓名、课程名称和教师名。
【练习7】查询每个班级可以选修的、不是自己所在部门开设的选修课程的信息,显示信息包括班级、课程名、课程类别、学分、老师、上课时间和报名人数。
【练习8】查询课程类别相同但开课部门不同的课程信息,要求显示课程编号、课程名称、课程类别和部门编号,并按照课程编号降序排列查询结果。
使用 student 数据库
【练习9】把 student 表和 grade 表左外连接。第1个表 student 有不满足连接条件的。
【练习10】把 grade 表和 course 表右外连接。第2个表 course 有不满足连接条件的行。
【练习11】把 grade 表和 course 表实现全连接。两个表都有不满足连接条件的。
本章主要介绍了 SQL 查询语句中多表连接和组合查询的操作。通过本章的学习,读者可以对内连接、外连接、自连接和组合查询等有一定的了解,可以实现一些更加复杂的查询操作。