SQL查询的艺术
上QQ阅读APP看书,第一时间看更新

第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。