第5章 简单的查询
查询(SELECT)语句是SQL的核心,是SQL语句中使用最频繁的语句之一。因为大量的选项可以用于SELECT语句,所以整个SQL数据查询功能就是围着SELECT语句进行的。查询可以按复杂性进行划分,从用一个简单语句返回表中的所有行,到用一个语句连接多个表并定义搜索条件。本章在介绍SELECT语句基本结构的基础上,主要介绍SQL的简单查询操作。
5.1 查询的基本结构
在SQL语言中,对数据库的读操作就体现在SELECT查询语句上。SELECT语句由一系列灵活的子句组成,这些子句共同确定检索哪些数据。
5.1.1 SELECT语句的结构
SELECT语句的基本语法可以分成几个特定的子句。通过每个子句细化查询,以便返回需要的数据。SELECT语句的完整语法如下。
SELECT [DISTINCT | ALL] select_list FROM table_name_source [ WHERE search_condition ] [ GROUP BY group_by_expression ] [ HAVING search_condition ] [ ORDER BY order_expression [ ASC | DESC ] ]
我们可以看出,必需的子句只有SELECT子句和FROM子句,其他的子句都是可选的。各子句具体含义如下。
• 关键词SELECT后面输入要显示的字段清单。SELECT子句包含可选关键字DISTINCT和ALL。在需要删除查询结果中相同的行时,我们使用关键字DISTINCT;在需要返回查询结果中的所有行时,使用关键字ALL。如果没有指定任何一个关键字,那么关键字ALL就是默认的关键字。
• FROM子句包括关键字FROM和一个或多个表结构。如果表结构不止一个,那么它们之间必须用逗号分开。一般情况下,表结构是表的名称,但也可以是一种子查询。在WHERE子句中列出搜索标准,用于选择要显示的数据行。如果SELECT语句没有WHERE子句,DBMS假设目标表中的所有行都满足搜索条件。
• GROUP BY子句用于分组查询结果,根据group_by_expression中列出的列,归纳信息类型,汇总相关数据。
• HAVING子句列出另外的行选择标准,以便根据GROUP BY子句产生的结果筛选行。
• ORDER BY子句接受SELECT子句的输出,并且按照ORDER BY子句中的规范对查询结果进行排列。这里的规范就是ASC和DESC关键字。ASC关键字表示升序排列结果,DESC关键字表示降序排列结果。如果没有指定任何一个关键字,那么ASC就是默认的关键字。如果没有ORDER BY子句,DBMS将根据输入表中的数据来显示数据。
5.1.2 SELECT语句的执行步骤
由5.1.1节介绍的SELECT语句的语法结构可知,SELECT语句包含很多子句。当执行SELECT语句时,DBMS的执行步骤可表示如下。
(1)DBMS执行FROM子句,根据FROM子句中的一个或多个表创建工作表。如果在FROM子句中有两个或多个表,DBMS将对表进行交叉连接,作为工作表。
(2)如果有WHERE子句,DBMS将WHERE子句列出的搜索条件作用于步骤(1)生成的工作表。DBMS将保留那些满足搜索条件的行,删除那些不满足搜索条件的行。
(3)如果有GROUP BY子句,DBMS将步骤(2)生成的结果表中的行分成多个组,每个组所有行的group_by_expression字段具有相同的值,DBMS将每组减少到单行,然后将其添加到新的结果表中。
(4)如果有HAVING子句,DBMS将HAVING子句列出的搜索条件作用于步骤(3)生成的“组合”表中的每一行。DBMS将保留那些满足搜索条件的行,删除那些不满足搜索条件的行。
(5)DBMS将SELECT子句作用于结果表,删除结果表中不包含在select_list中的列。如果SELECT子句包含DISTINCT关键字,DBMS将从结果中删除重复的行。
(6)如果有ORDER BY子句,DBMS按指定的排序规则对结果进行排序。
(7)对于交互式的SELECT语句,DBMS在屏幕上显示结果,对于嵌入式SQL,使用游标将结果传递给宿主程序中。
以上就是SQL SELECT语句的基本执行过程,对于初学者来讲,可能很难理解。本书之所以在这里提出,是为了让读者对SELECT语句有一个整体的了解。下面我们就从简单的查询开始,详细讲解SELECT查询语句的具体使用。
5.2 列的查询
在了解了查询的基本结构后,本小节将介绍最简单的查询——列(字段)的查询,包括单列查询、多列查询以及所有列的查询。
5.2.1 本章用到的实例表
本章所有的查询实例操作的表都是教师信息表(TEACHER)。TEACHER表的字段包括教工号(TNO)、教师姓名(TNAME)、所授课程的课程号(CNO)、工资(SAL)、所在系(DNAME)、性别(SEX)和年龄(AGE)。TEACHER表的结构和内容如表5-1所示。
表5-1 TEACHER表
TEACHER表创建的SQL代码如下。
CREATE TABLE_NAME TEACHER ( TNO INT NOT NULL, TNAME CHAR(10) NOT NULL, CNO INT NOT NULL, SAL INT , DNAME CHAR(10) NOT NULL, TSEX CHAR(2) NOT NULL, AGE INT NOT NULL ) INSERT INTO TEACHER VALUES(1,'王军',4,800,'数学','男',32) INSERT INTO TEACHER VALUES(2,'李彤',5,1200,'生物','女',54) ……
5.2.2 单列查询
在数据库表中,每个表都包含若干列信息。用户在查询表中的记录时,大多数情况下只是关心表的一列或者几列的信息。在SQL中,查询表中某一列(字段)信息的语法可表示如下。
SELECT column FROM table_name_name
SELECT关键词指明了要查询字段名称(column),FROM关键词指明了要获取字段信息的表的名字。
实例1 单列查询
查询TEACHER表中所有教师的姓名信息。代码如下。
SELECT TNAME FROM TEACHER
运行结果如图5-1所示。
图5-1 单列查询
从运行结果我们可以发现,SELECT语句返回了TNAME列的所有信息,包括重复信息——“李伟”。
注意
在SQL语言中,SQL关键词对大小写不敏感,所以对SELECT关键词来说,SELECT、select或者Select都是一样的;然而对于表名或者列名来说,可能对大小写敏感,这取决于数据库的DBMS。
5.2.3 使用DISTINCT去除重复信息
在查询中,我们经常需要去除查询结果中的重复信息。例如,一张学生成绩表,其字段包含学生姓名、选修课程和课程成绩3个字段。如果用户想要查询这张表中包含的所有学生的姓名信息,由于同一学生可能有多门选修课,同一学生在该表中就有多条记录,那么查询的姓名字段就会有多个重复值,显然不能很好满足用户的需求。
在SELECT子句中,我们通过指明DISTINCT关键字,可以去除列中的重复信息。语法如下。
SELECT DISTINCT column FROM table_name_name
说明
DISTINCT关键字去除的是SELECT子句查询的列,即column的重复信息。如果SELECT子句查询的列为多列,那么只有这些列的信息同时重复的记录才能被去除。
实例2 使用DISTINCT去除重复信息
在TEACHER表中查询所有教师的姓名信息(TNAME),对于重复的姓名只显示一个。实例代码如下。
SELECT DISTINCT TNAME FROM TEACHER
运行结果如图5-2所示。
图5-2 去除列中的重复记录
从执行结果我们可以发现,重名的“李伟”只显示了一个,即去除了查询结果中的重复值,并且对查询结果进行了排序(升序)。实际上,DBMS的操作过程是先对查询结果排序,然后查找并从结果中除去重复的值。
说明
对于汉字的排序,DBMS是根据其汉语拼音的第一个字母的顺序进行的,如果第一个字母相同,则比较第二个字母,以此类推。
DISTINCT的使用是要付出代价的。因为我们要去掉重复值,必须对结果关系进行排序,相同的元组排在一起,只有按这种方法对元组分组,才能去掉重复值,而这一工作甚至比查询本身还费时间。
5.2.4 多列查询
同单列查询一样,多列查询需要在SELECT关键词后指定要查询的列,但是各列之间必须用逗号分开。注意一点,列出的最后一列的名字后面不能加逗号,否则会造成语法错误。语法如下。
SELECT col1,col2,……,coln FROM table_name
多列查询只是在SELECT关键词后罗列出所要查询的列名并以逗号相互间隔。
实例3 多列查询
从TEACHER表中查询所有教师的姓名(TNAME)、工资(SAL)、年龄(AGE)信息。实例代码如下。
SELECT TNAME,SAL,AGE FROM TEACHER
运行结果如图5-3所示。
图5-3 多列查询
查询结果中,字段的左右位置与SELECT子句的位置相对应。
实例4 使用DISTINCT关键字
在实例3实例代码的SELECT子句中使用DISTINCT关键字,代码如下。
SELECT DISTINCT TNAME,SAL,AGE FROM TEACHER
运行结果如图5-4所示。
图5-4 去除多列中的重复记录
比较两个查询结果可知,使用DISTINCT关键字后,查询结果的记录与未使用DISTINCT关键字的查询记录完全相同,只是进行了排序(升序)显示。这是因为此时DISTINCT关键字作用的字段为TNAME、SAL和AGE 3个字段,只有这3个字段的记录均相同的记录才被作为重复记录而被去除。
5.2.5 查询所有的列
除了能够进行单列查询和多列查询以外,使用SELECT语句还可以查询表中的所有列,这是通过星号(*)通配符实现的。语法如下。
SELECT * FROM table_name
使用“*”通配符,查询结果将列出表中所有列的元素,而不必指明各列的列名,这在用户不清楚表中各列的列名时非常有用。
实例5 查询所有的列
查询TEACHER表中所有列的信息。实例代码如下。
SELECT * FROM TEACHER
运行结果如图5-5所示。
图5-5 教师表记录
说明
使用“*”通配符时我们要慎重,在不需要查询所有列时,尽量采用前面介绍的单列查询或多列查询,以免占用过多的资源。
通过实例我们可以看到,无论是单列查询、多列查询还是查询所有的列,查询结果的纵向排序基本是按照其在表中存储的位置排列(使用DISTINCT关键字的情况除外)。但是,在实际应用中往往需要查询结果按照某一特定的顺序排列,在5.3节我们将实现查询结果的排序。
5.3 排序查询结果
在本节中,我们将介绍如何在SELECT查询语句中采用ORDER BY子句对查询结果进行排序。
5.3.1 单列排序
这里所讲的单列排序查询结果的排列是按照某一列的顺序(升序)进行排列显示的,即ORDER BY子句后面只有一个字段。语法如下。
SELECT column1, column12,…… FROM table_name_name ORDER BY column N
这里要说明的是,ORDER BY子句后面为需要排序的列名,一般情况下为查询的列,当然如果有特殊的需求也可以是表中的其他列。
说明
ORDER BY子句一定要放在所有子句的最后(无论包含多少子句)。
实例6 查询结果排序
在TEACHER表中查询所有教师的姓名(TNAME)、教工号(TNO)和工资(SAL)信息,并将结果按姓名排序。实例代码如下。
SELECT TNAME, TNO, SAL FROM TEACHER ORDER BY TNAME
运行结果如图5-6所示。
图5-6 按照姓名排序后的教师表
由此可见,查询结果按照TNAME字段(即教师姓名)的升序顺序排列,其排序原则前面已经介绍过了。对该例,如果我们要求查询结果按SAL字段进行排序,代码如下。
SELECT TNAME, TNO, SAL FROM TEACHER ORDER BY SAL
运行结果如图5-7所示。
图5-7 按照SAL字段排序后的教师表
由此可见,系统将空值NULL作为最小值进行排序。
5.3.2 多列排序
在实际应用中,我们往往需要根据多列信息进行排序,如根据性别和年龄对教师信息进行排序。使用ORDER BY子句同样可以实现上述功能。语法如下。
SELECT col1,col2,……,coln FROM table_name ORDER BY col1,col2,……,colm
需要说明的是,实际排序结果是根据ORDER BY子句后面列名的顺序确定优先级的,即查询结果首先按照列col1的顺序进行排列,而只有当列col1出现相同的信息时,这些相同的信息再按照列col2的顺序进行排列,以此类推。
实例7 多列排序
在TEACHER表中查询所有教师的姓名(TNAME)、工资(SAL)、年龄(AGE)、性别(TSEX)信息并按性别(TSEX)和年龄(AGE)对查询结果排序。实例代码如下。
SELECT TNAME,SAL,AGE, TSEX FROM TEACHER ORDER BY TSEX, AGE
运行结果如图5-8所示。
图5-8 按照多列排序后的教师表
从查询结果我们可以发现,系统根据排序的优先级,首先依据TSEX字段的记录进行排序,当TSEX字段的记录相同时,如均为“男”或均为“女”,再依据AGE字段的记录进行排序。当然,所有的排序都是按升序进行的。
5.3.3 采用序号进行多列排序
ORDER BY子句除了可以跟据列名进行排序外,还支持根据列的相对位置(即序号)进行排序。语法如下。
SELECT col1,col2,……,coln FROM table_name ORDER BY i,j,……
这里需要说明的是,ORDER BY子句后面的序号i,j,……实际上就是SELECT关键词后面的列名的序号。如果i=2,那么ORDER BY 2就等同于ORDER BY col2。
实例8 采用序号进行多列排序
在TEACHER表中查询所有教师的姓名(TNAME)、工资(SAL)、年龄(AGE)、性别(TSEX)信息并按性别(TSEX)和年龄(AGE)对查询结果排序。实例代码如下。
SELECT TNAME,SAL,AGE, TSEX FROM TEACHER ORDER BY 4, 3
运行结果如图5-9所示。
图5-9 按照列序排序后的教师表
由此可见,得到了与5.3.2节实例7相同的查询结果。
注意
当ORDER BY所需要的列不在SELECT语句所选的范围内时,采用列的序号显然是行不通的。当然,我们可以将列别与序号混合起来解决这个问题。
5.3.4 反向排序
前面提到的排序,默认是按照升序进行的(例如,对于字符来说,其升序方向为A~Z)。同样,ORDER BY子句支持反向排序即降序排序(对于字符来说,其降序方向为Z~A)。要实现反向(降序)排序,我们必须使用关键字“DESC”。语法如下。
SELECT col1,col2,……,coln FROM table_name ORDER BY col1 DESC,col2
这里需要说明的是,在该语法中,查询结果按照col1列降序、col2列升序的方向进行排序。如果col2列也要按照降序的方向排序,则在col2后也要加上关键字DESC。
注意
对大小写字符的排序,一般的数据库管理系统认为二者是等同的。
实例9 按照多列降序排序
在TEACHER表中查询所有教师的姓名(TNAME)、工资(SAL)、年龄(AGE)、性别(TSEX)信息,按性别(TSEX)升序和年龄(AGE)降序的顺序对查询结果排序。实例代码如下。
SELECT TNAME,SAL,AGE, TSEX FROM TEACHER ORDER BY TSEX, 3 DESC
运行结果如图5-10所示。
图5-10 按照多列降序排列
实现代码中,对列的排序采用了列名与序号相结合的方式,TSEX列升序,而序号3代表的列就是SELECT语句后面的第3列,即AGE列。
5.4 使用WHERE子句定义搜索条件查询
数据库中的表往往包含大量的数据,用户一般很少需要查询表中所有数据行的信息,而只是需要其中一些满足特定条件的信息。在SELECT语句中,按一定条件进行查询是通过WHERE子句来实现的。
5.4.1 WHERE子句单条件查询
WHERE子句获取FROM子句返回的结果集并应用WHERE子句中定义的搜索条件对结果集进行筛选。WHERE子句相当于从FROM子句返回结果的筛选器,对FROM子句返回结果的每一行都要根据搜索条件进行校验,符合搜索条件的那些行作为查询结果的一部分返回,不符合搜索条件的从结果中去除。
WHERE子句中定义的搜索条件可以是简单的单值比较条件,也可以使用各种运算符、组合子句条件。本章介绍的是简单的值比较条件。复杂的查询条件将在第6章介绍。语法如下。
SELECT column FROM table_name WHERE column condition value
需要说明的是,该语句用来查询表table_name中的column列,但不是返回column列的所有信息,而是返回那些满足value条件的信息。
实例10 使用WHERE子句单条件查询
在TEACHER表中查询计算机系的教师的姓名(TNAME)、系(DNAME)、年龄(AGE)、性别(TSEX)信息。实例代码如下。
SELECT TNAME, DNAME,AGE, TSEX FROM TEACHER WHERE DNAME='计算机' ORDER BY AGE
运行结果如图5-11所示。
图5-11 TEACHER表中计算机系教师的相关信息
在该例中,WHERE子句中使用了“=”运算符,它要求两边的数值类型必须相同。DNAME所在列为CHAR型,所以后面的“计算机”必须写在单引号内,表明它是一个字符串;如果是数值,如INT型,直接写值即可。
注意
当ORDER BY子句和WHERE子句同时存在的时候,ORDER BY子句一定要在WHERE子句的后面。
5.4.2 单值比较运算符
在5.4.1节的实例中,在WHERE子句中用到了“=”(等于)运算符。它是一种最常用的单值比较运算符。实际上,除了它之外,WHERE子句还支持多种运算符,如表5-2所示。
表5-2 WHERE子句单值比较运算符
注意
不是所有的数据库管理系统都支持这些运算符,运用时我们需要根据自己的数据库管理系统选择合适的运算符。
下面我们就通过具体的实例讲一下运算符的用法。这里主要通过两个例子说明一下“>=”和“<>”运算符的运用。
1.“>=”运算符
实例11 对数值类型使用“>=”运算符进行条件查询
在TEACHER表中查询年龄大于等于40岁的教师的姓名(TNAME)、所在系(DNAME)、年龄(AGE)、性别(TSEX)信息。实例代码如下。
SELECT TNAME, DNAME,AGE, TSEX FROM TEACHER WHERE AGE >=40 ORDER BY AGE
运行结果如图5-12所示。
图5-12 TEACHER表中查询年龄大于等于40岁教师的相关信息
由此可见,使用该运算符可以成功搜索出年龄大于等于40岁的教师的相关信息。同样,对于字符串类型的记录也可以使用“>=”运算符,它的比较是根据字符的排列顺序进行的。
实例12 对字符串类型使用“>=”运算符进行条件查询
在TEACHER表中查询系名(DNAME)排在“计算机”后的所有教师的姓名(TNAME)、工资(SAL)、年龄(AGE)、性别(TSEX)信息。实例代码如下。
SELECT TNAME, DNAME,AGE, TSEX FROM TEACHER WHERE DNAME >= '计算机' ORDER BY DNAME
运行结果如图5-13所示。
图5-13 TEACHER表中系名(DNAME)排在“计算机”后的所有教师的相关信息
由此可见,“>=”运算符对字符串型数据也是有效的。
2.“<>”运算符
“<>”运算符表示不等于,实际上“<>”运算符与“!=”运算符是完全相同的。
实例13 对字符串类型使用“<>”运算符进行条件查询
在TEACHER表中查询不在计算机系的所有教师的姓名(TNAME)、所在系(DNAME)、年龄(AGE)、性别(TSEX)信息。实例代码如下。
SELECT TNAME, DNAME,AGE, TSEX FROM TEACHER WHERE DNAME<>'计算机' ORDER BY DNAME
运行结果如图5-14所示。
图5-14 TEACHER表中不在计算机系的所有教师的相关信息
同样,“<>”运算符也可以对数值型数据进行操作。
实例14 对数值型使用“<>”运算符进行条件查询
在TEACHER表中查询工资不为1200的所有教师的姓名(TNAME)、所在系(DNAME)、工资(SAL)、年龄(AGE)、性别(TSEX)信息。实例代码如下。
SELECT TNAME, DNAME,AGE, TSEX FROM TEACHER WHERE SAL <>1200 ORDER BY SAL
运行结果如图5-15所示。
图5-15 TEACHER表中工资不为1200的所有教师的相关信息
从结果中我们不难发现,工资为1200的教师信息不在查询结果内,但是同时工资为NULL的教师信息也不在查询结果内。NULL值参与任何比较运算,结果都将视为False,即,无论是“NULL<>1200”还是“NULL =1200”,其结果都为False,因此对应的记录都不满足WHERE子句查询条件。
5.4.3 BETWEEN运算符范围筛选
在WHERE子句中,我们可以采用BETWEEN运算符在两个值之间进行比较筛选。例如,我们选择范围在5~10之间的数,采用BETWEEN运算符可以表示为BETWEEN 5 AND 10。下面给出一个用BETWEEN运算符进行范围筛选的实例。
实例15 对数值型使用BETWEEN运算符进行范围筛选
在TEACHER表中查询年龄在30岁到50岁之间的教师的姓名(TNAME)、所在系(DNAME)、年龄(AGE)、性别(TSEX)信息。实例代码如下。
SELECT TNAME, DNAME,AGE, TSEX FROM TEACHER WHERE AGE BETWEEN 30 AND 50 ORDER BY AGE
运行结果如图5-16所示。
图5-16 TEACHER表中年龄在30岁到50岁之间的教师的相关信息
BETWEEN运算符也可以对字符型数据进行操作。
实例16 对字符型使用BETWEEN运算符进行范围筛选
在TEACHER表中,查询所在系的系名在“计算机”和“生物”之间的教师的姓名(TNAME)、所在系(DNAME)、年龄(AGE)、性别(TSEX)信息。实例代码如下。
SELECT TNAME, DNAME,AGE, TSEX FROM TEACHER WHERE DNAME BETWEEN '计算机' AND '生物' ORDER BY DNAME
运行结果如图5-17所示。
图5-17 TEACHER表中所在系的系名在“计算机”和“生物”之间的教师的信息
说明
我们进行运算符操作,如“BETWEEN A1 AND A2”,在进行结果筛选时,满足查询要求的结果是A1到A2之间的信息,并且包含A1和A2。
5.4.4 NULL值的判断
在前面表的创建中我们提到,在表中有些列的值可以为NULL,即不含任何值,称之为空值。在实际查询中,我们经常遇到针对NULL值的操作。例如,我们经常需要获取某列的信息,并把该列的信息进行某种运算,这时就要求该列不能包含NULL值,因为NULL值不能参与运算。而有时,我们需要查询某列中记录为NULL的信息。在SQL中,有关空值的运算符为IS [NOT] NULL,用于判断数值是否为NULL。下面我们通过具体实例说明IS NULL运算符的用法。
实例17 NULL值的判断
在TEACHER表中,查询工资不为空的教师的姓名(TNAME)、工资(SAL)、年龄(AGE)、性别(TSEX)信息。实例代码如下。
SELECT TNAME, SAL,AGE, TSEX FROM TEACHER WHERE SAL IS NOT NULL ORDER BY SAL
运行结果如图5-18所示。
图5-18 TEACHER表中工资不为空的教师的信息
判断列中的值是否为NULL,只能通过IS [NOT] NULL运算符。如上述实例代码中的判断工资不为NULL,我们只能表示成“SAL IS NOT NULL”,不可以写成“SAL <> NULL”或者“SAL != NULL”,如下面的代码所示。
SELECT TNAME, SAL,AGE, TSEX FROM TEACHER WHERE SAL <> NULL ORDER BY SAL
运行结果如图5-19所示。
图5-19 用“SAL <> NULL”判断列中的值是否为NULL
由此可见,查询结果中不包含任何记录,即SAL列中没有任何记录满足“SAL <> NULL”条件。这是因为表达式“SAL <> NULL”实际上是把NULL值与SAL值进行比较运算,而NULL值与任何值参与任何比较运算,结果都可以视为False,也就没有满足要求的查询记录。
注意
SAL IS NULL不可以写成SAL=NULL;除了IS [NOT] NULL之外,空值不满足任何查找条件;如果NULL参与算术运算,则该算术表达式的值为NULL;如果NULL参与比较运算,则结果可视为False,在SQL-92中可看成UNKNOWN;如果NULL参与聚集运算,则除count(*)之外其他聚集函数都忽略NULL。