4.2.3 单表查询
数据库中包含大量的数据,根据特殊要求,可能只需要查询表中的指定数据,即对数据进行过滤。在select语句中,通过where子句可以对数据进行过滤,语法格式为:
判断运算条件表达式的结果为true、false或unknown,格式如下:
where子句根据条件对from子句的中间结果中的行进行一行一行判断,当条件为true时,该行就被包含到where子句的中间结果集中。
在SQL中,返回逻辑值(true或false)的运算符或关键字都可称为谓词,判定运算包括比较运算、模式匹配、范围比较、空值比较和子查询。关于运算符相关内容请参看“4.1.1 MySQL的运算符”。
1.查询指定记录
【例4.17】查询employees表中工资esal为3 000元的员工信息。
SQL语句如下:
该语句使用select声明从employees表中获取工资等于3 000的员工信息,从查询结果可以看到,工资等于3 000的员工有两个,其他的均不满足查询条件,查询结果如下:
本例采用了简单的相等过滤,查询一个指定字段esal等于值3 000。因为没有要求查询employees表中的哪些字段,所以用“*”查询所有字段。
相等还可以用来比较字符串,如下:
【例4.18】查询employees表中员工姓名ename为“胡歌”的姓名ename、职位ejob和工资esal。
SQL语句如下:
从查询结果可以看到,只有员工姓名为“胡歌”的行被返回,其他的均不满足查询条件。
因为此示例中明确指定select所要查询的字段列为职位ejob和工资esal,并且在题目中提到员工姓名ename,因此确定该查询的字段名列表为ename,ejob,esal,而不是“*”。
【例4.19】查询employees表中工资esal低于2 000的员工姓名ename和工资esal。
SQL语句如下:
该语句使用select声明从employees表中获取工资esal低于2 000的员工姓名ename,即esal小于2 000的员工信息被返回,查询结果如下:
可以看到查询结果中,所有记录的esal字段列的值均小于2 000元,而大于或等于2 000元的记录没有被返回。
实训:
①查询employees表中职位是“销售员”的员工信息。
②查询employees表中日期“1999-12-31”以后入职的员工的姓名、雇用日期、工资。
③查询employees表中姓名为“邓超”的员工的信息。
2.带in关键字的查询
in操作符用来查询满足指定范围内的条件记录,使用in操作符,将所有检索条件用括号括起来,检索条件之间用逗号分隔开,只要满足条件范围内的一个值即为匹配项。
【例4.20】查询employees表中部门编号为1或2或5的员工信息。
SQL语句及执行效果如下:
相反,可以使用关键字not in来检索不在条件范围内的记录。
【例4.21】查询employees表中部门编号不是1、2、5的员工信息。
SQL语句及执行效果如下:
可以看到,该语句在in关键字前面加上了not关键字,这使得查询的结果与前面一个结果正好相反,前面检索了deptno字段值等于1、2、5的记录,而这里所要求的查询记录中deptno字段值不等于这三个值中的任何一个。
实训:
①查询employees表中员工职位ejob为程序员、人事部长的员工信息。
②查询employees表中deptno等于1、2、3、4的员工的姓名、职位。
③查询employees表中不是办事员、销售员、程序员的员工信息。
3.带between and的范围查询
between and用来查询某个范围内的值,该操作符需要两个参数,即范围的开始值和结束值,如果字段值满足指定的范围查询条件,则这些记录被返回。
【例4.22】查询employees表中员工工资esal为2 000~3 000元的员工姓名ename和员工工资esal。
SQL语句及执行效果如下:
可以看到,返回结果包含了员工工资为2 000~3 000元的字段值,并且端点值2 000、3 000包括在返回结果中,即between and匹配范围中所有值,包括开始值和结束值。
between and操作符前可以加关键字not,表示指定范围之外的值,如果字段值不满足指定的范围内的值,则这些记录被返回。
【例4.23】查询employees表中员工工资esal不在2 000~10 000元的员工姓名ename和员工工资esal。
SQL语句及执行效果如下:
由结果可以看到,返回的记录有员工工资esal字段值大于10 000的,也有员工工资esal字段值小于2 000的记录。
实训:
①查询employees表中雇用日期ehiredate在‘2007-1-1’到‘2017-1-1’之间的员工信息。
②查询employees表中雇用日期ehiredate不在‘2007-1-1’到‘2017-1-1’之间的员工信息。
4.带like的字符匹配查询
在前面的检索操作中,讲述了如何查询多个字段的记录,如何进行比较查询或者是查询一个条件范围内的记录,如果要查找所有的包含汉字“王”的员工姓名,该如何查找呢?简单的比较操作在这里已经行不通了,在这里,需要使用通配符进行匹配查找,通过创建查找模式对表中的数据进行比较。执行这个任务的关键字是like。
通配符是一种在SQL的where条件子句中拥有特殊意思的字符,SQL语句中支持多种通配符,可以和like一起使用的通配符有‘%’和‘_’。
(1)百分号通配符‘%’,匹配任意长度的字符,甚至包括零字符
【例4.24】查询departments表中部门名称dname以“开”字开头的部门信息。
SQL语句及执行效果如下:
该语句查询的结果返回部门名称dname所有以“开”字开头的部门信息,‘%’告诉MySQL返回所有以“开”字开头的记录,不管“开”字后面有多少个字符。
在搜索匹配时,通配符‘%’可以放在不同位置,如例4.25。
【例4.25】查询employees表中员工姓名ename中包含“杨”字的员工信息。
SQL语句及执行效果如下:
该语句查询字符串中包含“杨”字的员工信息,只要员工姓名中有汉字“杨”,而前面或后面不管有多少个字符,都满足查询的条件。
【例4.26】查询employees表中以‘r’开头,并以‘e’结尾的员工信息。
SQL语句及执行效果如下:
通过以上查询结果可以看到,‘%’用于匹配在指定的位置的任意数目的字符。
(2)下划线通配符‘_’,一次只能匹配任意一个字符
另一个非常有用的通配符是下划线通配符‘_’,该通配符的用法和‘%’相同,区别是‘%’可以匹配多个字符,而‘_’只能匹配任意单个字符,如果要匹配多个字符,则需要使用相同个数的‘_’。
【例4.27】在employees表中,查询员工姓名ename以字母‘h’结尾,且‘h’前面只有4个字母的员工信息。
SQL语句及执行效果如下:
从结果可以看到,以‘h’结尾且前面只有4个字母的记录只有一条。其他记录的ename字段也有以‘h’结尾的,但其总的字符串长度不为5,因此不在返回结果中。
在MySQL中,一个汉字相当于两个英文字母的宽度,所以如果要匹配任意一个汉字应该使用两个‘_’。
【例4.28】在employees表中,查询员工姓名ename的第二个汉字为“晗”的员工信息。
SQL语句及执行效果如下:
实训:
①在employees表中,查询员工姓名ename中包含‘o’的员工信息。
②在employees表中,查询员工姓名ename中包含三个汉字,且第三个汉字为“东”的员工信息。
5.查询空值
创建数据表的时候,设计者可以指定某字段中是否可以包含空值(null)。空值不同于0,也不同于空字符串。空值一般表示数据未知、不适用或将在以后添加数据。在select语句中使用is null子句,可以查询某字段内容为空的记录。
【例4.29】查询employees表中员工奖金ebonus为空记录的eno、ename、ejob、ebonus字段值。
SQL语句及执行效果如下:
可以看到,显示了employees表中字段ebonus的值为null的记录,满足查询条件。
与is null相反的是is not null,该关键字查找字段值不为空的记录。
【例4.30】查询employees表中员工奖金ebonus不为空记录的eno,ename,ejob,ebonus字段的值。
SQL语句及执行效果如下:
可以看到,查询出记录的ebonus字段的值都不为空。
实训:
①查询工资表salary中奖金为空的员工的编号。
②查询员工表employees中没有分配职位的员工信息。
6.带and的多条件查询
使用select查询时,可以增加查询的限制条件,这样可以使查询的结果更加精确。MySQL在where子句中使用and操作符,限定只有满足所有查询条件的记录才会被返回。可以使用and连接两个甚至多个查询条件,多个条件表达式之间用and分开。
【例4.31】查询employees表中部门编号deptno为2,并且员工工资esal大于5 000的员工姓名ename、员工工资esal、部门编号deptno。
SQL语句及执行效果如下:
例4.31中的SQL语句检索了部门编号deptno为2的,并且员工工资esal大于5 000的员工姓名ename、员工工资esal、部门编号deptno。where子句中的条件分为两部分,and关键字指示MySQL返回所有同时满足两个条件的行。即使是deptno为2的员工信息,如果esal小于等于5 000;或者deptno不等于2的员工信息,不管esal为多少,均不是要查询的结果。
提示:上述例子的wher e子句中只包含了一个and语句,把两个过滤条件组合在一起。实际上可以添加多个and过滤条件,增加条件的同时增加一个and关键字。
【例4.32】查询employees表中职位ejob为“程序员”或者“销售员”,且员工工资esal在4000以上,并且奖金ebonus低于2 000的员工信息。
SQL语句及执行效果如下:
可以看到,符合查询条件的返回记录只有一条。
实训:
①查询工资表salary中奖金在2 000~3 000之间的信息。
②查询工资表salary中奖金不在2 000~3 000之间的信息。
7.带or的多条件查询
与and相反,在where声明中使用or操作符,表示只需要满足其中一个条件的记录即可返回。or也可以连接两个甚至多个查询条件,多个条件表达式之间用or分开。
【例4.33】查询employees表中职位ejob为“办事员”或者“销售员”,或者员工工资esal高于10 000的员工信息。
SQL语句及执行效果如下:
通过显示结果可以发现,or操作符告诉MySQL,检索的时候只需要满足其中的一个条件,不需要全部都满足。如果这里使用and操作符,将检索不到符合条件的数据。因为没有哪个员工的职位既是“办事员”又是“销售员”,同时工资还高于10 000的。
在这里,也可以使用in操作符实现与or相同的功能,下面的例子可进行说明。
【例4.34】查询employees表中职位ejob为“办事员”或者“销售员”,或者员工工资esal高于10000的员工信息。
SQL语句及执行效果如下:
在这里可以看到,or操作符和in操作符使用后的结果是一样的,它们可以实现相同的功能。但是使用in操作符使检索语句更加简洁明了,并且in执行的速度要快于or。更重要的是,使用in操作符可以执行更加复杂的嵌套查询(后面部分会讲述)。对于or对应的表达式,可以是不同的字段,而in操作符对应的表达式只有一个字段。
提示:o r可以和and一起使用,但是在使用时要注意两者的优先级,由于and的优先级高于o r,因此先对and两边的操作数进行操作,再与o r中的操作数结合。
实训:
①查询部门地址是“长春”或“沈阳”的部门信息。
②查询部门编号等于“5”或者是部长的员工信息。
8.对查询结果排序
从前面的查询结果能够发现有些字段的值是没有任何顺序的,MySQL可以通过在select语句的末尾使用order by子句,对查询的结果进行排序。基本语法格式为:
(1)单字段排序
例如,查询部门编号deptno为1的员工的姓名。
SQL语句及执行效果如下:
可以看到,查询的数据并没有以一种特定的顺序显示,如果没有对它们进行排序,将根据它们插入数据表中的顺序来显示。
下面使用order by子句对指定的列数据进行排序。
【例4.35】查询employees表中部门编号deptno为1的员工的姓名,并按姓名对其进行排序。
SQL语句及执行效果如下:
该语句查询的结果和前面的语句相同,不同的是,通过指定order by子句,MySQL对查询的ename字段的数据按字母表的顺序进行了升序排序。
(2)多字段排序
有时需要根据多字段值进行排序。比如,如果要显示一个员工列表,可能会有多个员工的姓氏是相同的,因此可能还需要根据员工的其他字段进行排序。对多列数据进行排序,须将需要排序的列之间用逗号隔开。
【例4.36】查询employees表中部门编号deptno等于1或2的部门的员工信息,先按部门编号deptno排序,再按工资esal排序。
SQL语句及执行效果如下:
由例4.36可以看出,查询先按照deptno字段升序排列,对于deptno字段值相同的记录,再按照esal升序排列。
提示:在对多字段进行排序的时候,只有当排序的第一列有相同的列值时,才会对第二列进行排序。如果第一列数据中所有值都是唯一的,将不再对第二列进行排序。
(3)指定排序方向
默认情况下,查询数据按字母升序进行排序(a~z),但数据的排序并不仅限于此,还可以使用order by对查询结果进行降序排序(z~a),这可以通过关键字desc实现。下面的例子表明了如何进行降序排列。
【例4.37】查询employees表中部门编号deptno等于1或2的部门的员工信息,先按部门编号deptno降序排序,再按工资esal降序排序。
SQL语句及执行效果如下:
例4.37先按部门编号deptno降序排序,再按工资esal降序排序,结果和例4.36的查询结果正好相反。
提示:与desc相反的是asc(升序排序),将字段列中的数据,按字母表顺序升序排序。实际上,在排序的时候asc是作为默认的排序方式,所以加不加都可以。
例4.37的代码也可以写成下面的形式,功能不变:
对于order by后面的字段名,可以用select查询列表中字段的顺序号代替(1,2,3,…),也可以用字段的别名代替,如下代码所示:
实训:
①查询所有部门信息,按照部门地址降序排序。
②查询所有员工的员工编号、员工姓名、员工工资、部门编号,要求每个字段都有别名,对查询结果按照部门编号升序,员工工资降序。排序要求使用别名和字段的顺序号。
9.分组查询
分组查询是对数据按照某个或多个字段、表达式、列编号进行分组,MySQL中使用group by关键字对数据进行分组。
基本语法格式为:
“字段名|表达式|列编号”为进行分组时所依据的信息;“having<分组条件表达式>”指定满足分组条件表达式结果的信息将被显示。
(1)创建分组
group by从句根据所给的字段名返回分组的查询结果,可用于查询具有相同值的字段。如下代码所示:
由以上结果可以看出,查询显示结果时,被分组的字段如果有重复的值,只返回靠前的记录,并且返回的记录集是排序的。这并不是一个很好的结果。仅仅使用group by从句并没有什么意义,该从句的真正作用在于与各种聚合函数配合,用于行的相关计算。
group by关键字通常和聚合函数一起使用,如max()、min()、count()、sum()、avg()。例如,要返回每个部门的总人数,这时就要在分组过程中用到count()函数,把数据分为多个逻辑组,并对每个组进行集合计算。
【例4.38】查询employees表中每个部门的总人数。
SQL语句及执行效果如下:
查询结果显示,deptno表示部门编号,“人数”字段使用count()函数计算得出,group by子句deptno升序排序并对数据分组,可以看到deptno为1、3的部门分别有5个人,deptno为null、2、4、5的部门分别有2、3、4、1个人。
如果要查看每个部门所有人的姓名,该怎么办呢?MySQL中可以使用group_concat()函数,将每个分组中各个字段的值显示出来。
【例4.39】查询employees表中每个部门的总人数,将每个部门所有人的姓名显示出来。
SQL语句及执行效果如下:
由结果可以看到,group_concat()函数将每个分组中的姓名显示出来了,其姓名的个数与count()函数计算出来的相同。因为在select语句末尾加了desc,所以查询结果按照deptno降序排列。根据语法格式可以看到,group by后面还可以加表达式、列编号。
【例4.40】查询employees表中每个部门的平均工资、奖金总和,部门编号deptno要求乘以2显示。
SQL语句及执行效果如下:
例4.40中group by后面用部门编号deptno*2作为分组。另外,例4.40中分组子句也可以写成group by 1,此处的“1”为列编号。
(2)使用having过滤分组
使用group by对表中的数据分组后,可以通过having子句对分组后的数据(最大值、最小值、计数、平均值、和)进行条件筛选。
【例4.41】查询employees表中各部门平均工资低于4 500的部门编号。
SQL语句及执行效果如下:
例4.41中可以为avg(esal)列起别名,但如果别名用在having的后面,得到的查询结果是不合理的,如下所示:
通过以上例子,可以看出聚合函数列,可以起别名,但别名不能用于having子句。
提示:hav ing关键字与whe re关键字都是用来过滤数据的,两者有什么区别呢?其中重要的一点是,hav ing在数据分组之后进行过滤来选择分组,而wher e在分组之前用来选择记录。另外whe r e排除的记录不再包括在分组中。
(3)在group by子句中使用with rollup
使用with rollup关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。
【例4.42】查询employees表中每个部门的总人数,并显示记录数量。
SQL语句及执行效果如下:
由结果可以看到,通过group by分组之后,在显示结果的最后面新添加了一行,该行count(eno)列的值正好是上面所有数值之和。
(4)多字段分组
使用group by可以对多个字段进行分组,group by关键字后面跟需要分组的字段,MySQL根据多字段的值进行层次分组。分组层次从左到右,即先按第1个字段分组,然后在第1个字段值相同的记录中,再根据第2个字段的值进行分组……依此类推。
【例4.43】根据部门编号deptno和员工职位ejob对employees表中的数据进行分组。
SQL语句及执行效果如下:
由结果可以看到,查询记录先按照deptno字段进行分组,再对ejob字段按不同的取值进行分组。
(5)group by和order by一起使用
某些情况下需要对分组进行排序,在前面的介绍中,order by用来对查询的记录排序,如果和group by一起使用,可以完成对分组之后的数据进行排序。
【例4.44】查询employees表中每个部门平均工资大于4 500的部门编号deptno和其平均工资。
SQL语句及执行效果如下:
可以看到,返回的结果中“平均工资”列的值并没有按照一定顺序显示。接下来使用order by关键字按“平均工资”排序显示结果。
SQL语句及执行效果如下:
由结果可以看到,group by子句按deptno字段对数据进行分组,avg()函数便可以返回每个部门的平均工资,having子句对分组数据进行过滤,使得只返回平均工资大于4 500的部门编号及平均工资,最后使用order by子句排序输出。
提示:当使用r o l l up时,不能同时使用o r de r by子句进行结果排序。即r o l l up和o r de r by是互相排斥的。
实训:
①查询每个部门的最低工资、最高工资、平均工资。
②统计部门员工的平均工资高于2 000的员工平均工资。
③列出各种类别工作的最低工资,并按最低工资降序排列。
④查询employees表中各部门最低工资小于2 000的部门编号deptno和其最低工资。
10.使用limit限制查询结果的数量
select返回所有匹配的行,有可能是表中所有的行,如仅仅需要返回第一行或者前几行,使用limit关键字,基本语法格式如下:
“偏移量”参数指示MySQL从哪一行开始显示,是一个可选参数。如果不指定“偏移量”,将会从表中的第一条记录开始(第一条记录的偏移量是0,第二条记录的偏移量是1……依此类推);“行数”参数指示返回的记录条数。
【例4.45】显示employees表中所有记录的前3行。
SQL语句及执行效果如下:
由结果可以看到,该语句没有指定返回记录的“偏移量”参数,显示结果从第一行开始,“行数”参数为3,因此返回的结果为employees表中的前3行记录。
如果指定返回记录的开始位置,则返回结果为从“偏移量”参数开始的指定行数,“行数”参数指定返回的记录条数。
因此例4.45的代码也可以用以下两条语句代替:
所以,带一个参数的limit指定从查询结果的首行开始,唯一的参数表示返回的行数,即“limit n”与“limit0,n”等价。带两个参数的limit可以返回从任何一个位置开始的指定的行数。返回第一行时,位置偏移量是0。因此,“limit 1,1”将返回第二行,而不是第一行。
提示:MySQL 5.5中可以使用“l imi t 4 o f f set 3”,意思是获取从第5条记录开始后面的3条记录,和“l imi t 4,3”返回的结果相同。
实训:
显示工资表salary表中所有记录的第4~8行。