6.2 单表查询

单表查询是指从一张表数据中查询所需的数据。本节将介绍单表查询中各种基本的查询方式,主要有:查询所有字段、查询指定字段、查询指定记录、查询空值、多条件查询、对查询结果进行排序等。

6.2.1 查询所有字段

1.在SELECT语句中使用星号“*”通配符查询所有字段

SELECT查询记录最简单的形式是从一个表中检索所有记录,实现的方法是使用星号(*)通配符指定查找所有列的名称。语法格式如下:

【例6.1】从fruits表中检索所有字段的数据,SQL语句如下:

可以看到,使用星号(*)通配符时,将返回所有列,列按照定义表时的顺序显示。

2.在SELECT语句中指定所有字段

下面介绍另外一种查询所有字段值的方法。根据前面SELECT语句的格式,SELECT关键字后面的字段名为将要查找的数据,因此可以将表中所有字段的名称跟在SELECT子句后面,如果忘记了字段名称,可以使用DESC命令查看表的结构。有时,由于表中的字段可能比较多,不一定能记得所有字段的名称,因此该方法会很不方便,不建议使用。例如查询fruits表中的所有数据,SQL语句也可以书写如下:

查询结果与【例6.1】相同。

提示

一般情况下,除非需要使用表中所有的字段数据,最好不要使用通配符“*”。使用通配符虽然可以节省输入查询语句的时间,但是获取不需要的列数据通常会降低查询和所使用的应用程序的效率。通配符的优势是,当不知道所需要的列的名称时,可以通过通配符获取它们。

6.2.2 查询指定字段

1.查询单个字段

查询表中的某一个字段,语法格式为:

【例6.2】查询fruits表中f_name列所有水果名称,SQL语句如下:

该语句使用SELECT声明从fruits表中获取名称为f_name字段下的所有水果名称,指定字段的名称紧跟在SELECT关键字之后,查询结果如下:

输出结果显示了fruits表中f_name字段下的所有数据。

2.查询多个字段

使用SELECT声明,可以获取多个字段下的数据,只需要在关键字SELECT后面指定要查找的字段的名称,不同字段名称之间用逗号(,)分隔开,最后一个字段后面不需要加逗号,语法格式如下:

【例6.3】从fruits表中获取f_name和f_price两列,SQL语句如下:

该语句使用SELECT声明从fruits表中获取名称为f_name和f_price两个字段下的所有水果名称和价格,两个字段之间用逗号分隔开,查询结果如下:

输出结果显示了fruits表中f_name和f_price两个字段下的所有数据。

提示

Oracle中的SQL语句是不区分大小写的,因此SELECT和select作用相同,但是,许多开发人员习惯将关键字使用大写,而数据列和表名使用小写,读者也应该养成一个良好的编程习惯,这样写出来的代码更容易阅读和维护。

6.2.3 查询指定记录

数据库中包含大量的数据,根据特殊要求,可能只需要查询表中的指定数据,即对数据进行过滤。在SELECT语句中,通过WHERE子句可以对数据进行过滤,语法格式为:

在WHERE子句中,Oracle提供了一系列的条件判断符,查询结果如表6-1所示。

表6-1 WHERE条件判断符

【例6.4】查询价格为10.2元的水果的名称,SQL语句如下:

该语句使用SELECT声明从fruits表中获取价格等于10.2的水果的数据,从查询结果可以看到,价格是10.2的水果的名称是blackberry,其他的均不满足查询条件,查询结果如下:

本例采用了简单的相等过滤,查询一个指定列f_price具有值10.20。

相等还可以用来比较字符串。

【例6.5】查找名称为“apple”的水果的价格,SQL语句如下:

执行结果如下。该语句使用SELECT声明从fruits表中获取名称为“apple”的水果的价格,从查询结果可以看到只有名称为“apple”的行被返回,其他的均不满足查询条件。

【例6.6】查询价格小于10的水果的名称,SQL语句如下:

该语句使用SELECT声明从fruits表中获取价格低于10的水果名称,即f_price小于10的水果信息被返回,查询结果如下:

可以看到查询结果中所有记录的f_price字段的值均小于10.00元,而大于或等于10.00元的记录没有被返回。

6.2.4 带IN关键字的查询

IN操作符用来查询满足指定范围内的条件的记录,使用IN操作符,将所有检索条件用括号括起来,检索条件之间用逗号分隔开,只要满足条件范围内的一个值即为匹配项。

【例6.7】s_id为101和102的记录,SQL语句如下:

查询结果如下:

相反的,可以使用关键字NOT来检索不在条件范围内的记录。

【例6.8】查询所有s_id不等于101也不等于102的记录,SQL语句如下:

查询结果如下:

可以看到,该语句在IN关键字前面加上了NOT关键字,这使得查询的结果与前面一个的结果正好相反,前面检索了s_id等于101和102的记录,而这里所要求的查询的记录中的s_id字段值不等于这两个值中的任何一个。

6.2.5 带BETWEEN AND的范围查询

BETWEEN AND用来查询某个范围内的值,该操作符需要两个参数,即范围的开始值和结束值,如果字段值满足指定的范围查询条件,则这些记录被返回。

【例6.9】查询价格在2.00元到10.20元之间的水果名称和价格,SQL语句如下:

查询结果如下:

可以看到,返回结果包含了价格从2.00元到10.20元之间的字段值,并且端点值10.20也包括在返回结果中,即BETWEEN匹配范围中所有值,包括开始值和结束值。

BETWEEN AND操作符前可以加关键字NOT,表示指定范围之外的值,如果字段值不满足指定的范围内的值,则这些记录被返回。

【例6.10】查询价格在2.00元到10.20元之外的水果名称和价格,SQL语句如下:

查询结果如下:

由结果可以看到,返回的记录只有f_price字段大于10.20的,其实,f_price字段小于2.00的记录也满足查询条件。因此,如果表中有f_price字段小于2.00的记录,也应当作为查询结果。

6.2.6 带LIKE的字符匹配查询

在前面的检索操作中,讲述了如何查询多个字段的记录,如何进行比较查询或者是查询一个条件范围内的记录,如果要查找所有的包含字符“ge”的水果名称,该如何查找呢?简单的比较操作在这里已经行不通了,在这里,需要使用通配符进行匹配查找,通过创建查找模式对表中的数据进行比较。执行这个任务的关键字是LIKE。

通配符是一种在SQL的WHERE条件子句中拥有特殊意思的字符,SQL语句中支持多种通配符,可以和LIKE一起使用的通配符有“%”和“_”。

1.百分号通配符“%”,匹配任意长度的字符,甚至包括零字符

【例6.11】查找所有以“b”字母开头的水果,SQL语句如下:

查询结果如下:

该语句查询的结果返回所有以“b”开头的水果的id和name,“%”告诉Oracle,返回所有以字母“b”开头的记录,不管“b”后面有多少个字符。

在搜索匹配时通配符“%”可以放在不同位置,如【例6.12】。

【例6.12】在fruits表中,查询f_name中包含字母“g”的记录,SQL语句如下:

查询结果如下:

该语句查询字符串中包含字母“g”的水果名称,只要名字中有字符“g”,而前面或后面不管有多少个字符,都满足查询的条件。

【例6.13】查询以“b”开头,并以“y”结尾的水果的名称,SQL语句如下:

查询结果如下:

通过以上查询结果,可以看到,“%”用于匹配在指定的位置的任意数目的字符。

2.下划线通配符“_”,一次只能匹配任意一个字符

另一个非常有用的通配符是下划线通配符“_”,该通配符的用法和“%”相同,区别是“%”可以匹配多个字符,而“_”只能匹配任意单个字符,如果要匹配多个字符,则需要使用相同个数的“_”。

【例6.14】在fruits表中,查询以字母“y”结尾,且“y”前面只有4个字母的记录,SQL语句如下:

查询结果如下:

从结果可以看到,以“y”结尾且前面只有4个字母的记录只有一条。其他记录的f_name字段也有以“y”结尾的,但其总的字符串长度不为5,因此不在返回结果中。

6.2.7 查询空值

数据表创建的时候,设计者可以指定某列中是否可以包含空值(NULL)。空值不同于0,也不同于空字符串。空值一般表示数据未知、不适用或将在以后添加数据。在SELECT语句中使用IS NULL子句,可以查询某字段内容为空记录。

下面在数据库中创建数据表customers,该表中包含了本章中需要用到的数据。

为了演示需要插入数据,请读者执行以下语句:

查询上述4条记录是否成功插入,查询插入记录的个数,输入SQL语句,执行结果如下:

【例6.15】查询customers表中c_email为空的记录的c_id、c_name和c_email字段值,SQL语句如下:

查询结果如下:

可以看到,显示customers表中字段c_email的值为NULL的记录,满足查询条件。

与IS NULL相反的是NOT IS NULL,该关键字查找字段不为空的记录。

【例6.16】查询customers表中c_email不为空的记录的c_id、c_name和c_email字段值,SQL语句如下:

查询结果如下:

可以看到,查询出来的记录的c_email字段都不为空值。

6.2.8 带AND的多条件查询

使用SELECT查询时,可以增加查询的限制条件,这样可以使查询的结果更加精确。Oracle在WHERE子句中使用AND操作符限定只有满足所有查询条件的记录才会被返回。可以使用AND连接两个甚至多个查询条件,多个条件表达式之间用AND分开。

【例6.17】在fruits表中查询s_id=101,并且f_price大于等于5的水果价格和名称,SQL语句如下:

查询结果如下:

前面的语句检索了s_id=101的水果供应商所有价格大于等于5元的水果名称和价格。WHERE子句中的条件分为两部分,AND关键字指示Oracle返回所有同时满足两个条件的行。即使是id=101的水果供应商提供的水果,如果价格<5,或者是id不等于101的水果供应商里的水果不管其价格为多少,均不是要查询的结果。

提示

上述例子的WHERE子句中只包含了一个AND语句,把两个过滤条件组合在一起。实际上可以添加多个AND过滤条件,增加条件的同时增加一个AND关键字。

【例6.18】在fruits表中查询s_id=101或者102,且f_price大于5,并且f_name='apple'的水果价格和名称,SQL语句如下:

查询结果如下:

可以看到,符合查询条件的返回记录只有一条。

6.2.9 带OR的多条件查询

与AND相反,在WHERE声明中使用OR操作符,表示只需要满足其中一个条件的记录即可返回。OR也可以连接两个甚至多个查询条件,多个条件表达式之间用OR分开。

【例6.19】查询s_id=101或者s_id=102的水果供应商的f_price和f_name,SQL语句如下:

查询结果如下:

结果显示了s_id=101和s_id=102的商店里的水果名称和价格,OR操作符告诉Oracle,检索的时候只需要满足其中的一个条件,不需要全部都满足。如果这里使用AND的话,将检索不到符合条件的数据。

在这里,也可以使用IN操作符实现与OR相同的功能,下面的例子可进行说明。

【例6.20】查询s_id=101或者s_id=102的水果供应商的f_price和f_name,SQL语句如下:

查询结果如下:

在这里可以看到,OR操作符和IN操作符使用后的结果是一样的,它们可以实现相同的功能。但是使用IN操作符使得检索语句更加简洁明了,并且IN执行的速度要快于OR。更重要的是,使用IN操作符,可以执行更加复杂的嵌套查询(后面章节将会讲述)。

提示

OR可以和AND一起使用,但是在使用时要注意两者的优先级,由于AND的优先级高于OR,因此先对AND两边的操作数进行操作,再与OR中的操作数结合。

6.2.10 查询结果不重复

从前面的例子可以看到,SELECT查询返回所有匹配的行。例如,查询fruits表中所有的s_id,其结果为:

可以看到查询结果返回了16条记录,其中有一些重复的s_id值,有时,出于对数据分析的要求,需要消除重复的记录值,如何使查询结果没有重复呢?在SELECT语句中,可以使用DISTINCT关键字指示Oracle消除重复的记录值。语法格式为:

【例6.21】查询fruits表中s_id字段的值,返回s_id字段值且不得重复,SQL语句如下:

查询结果如下:

可以看到,这次查询结果只返回了7条记录的s_id值,且不再有重复的值,SELECT DISTINCT s_id告诉Oracle只返回不同的s_id行。

6.2.11 对查询结果排序

从前面的查询结果,读者会发现有些字段的值是没有任何顺序的,Oracle可以通过在SELECT语句中使用ORDER BY子句,对查询的结果进行排序。

1.单列排序

例如,查询f_name字段,查询结果如下:

可以看到,查询的数据并没有以一种特定的顺序显示,如果没有对它们进行排序,它们将根据插入到数据表中的顺序来显示。

下面使用ORDER BY子句对指定的列数据进行排序。

【例6.22】查询fruits表的f_name字段值,并对其进行排序,SQL语句如下:

该语句查询的结果和前面的语句相同,不同的是,通过指定ORDER BY子句,Oracle对查询的name列的数据,按字母表的顺序进行了升序排序。

2.多列排序

有时,需要根据多列值进行排序。比如,如果要显示一个学生列表,可能会有多个学生的姓氏是相同的,因此还需要根据学生的名进行排序。对多列数据进行排序,须将需要排序的列之间用逗号隔开。

【例6.23】查询fruits表中的f_name和f_price字段,先按f_name排序,再按f_price排序,SQL语句如下:

查询结果如下:

提示

在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。如果第一列数据中所有值都是唯一的,将不再对第二列进行排序。

3.指定排序方向

默认情况下,查询数据按字母升序进行排序(A~Z),但数据的排序并不仅限于此,还可以使用ORDER BY对查询结果进行降序排序(Z~A),这可以通过关键字DESC实现,下面的例子表明了如何进行降序排列。

【例6.24】查询fruits表中的f_name和f_price字段,对结果按f_price降序方式排序,SQL语句如下:

查询结果如下:

提示

与DESC相反的是ASC(升序排序),将字段列中的数据按字母表顺序升序排序。实际上,在排序的时候ASC是默认的排序方式,所以加不加都可以。

也可以对多列进行不同的顺序排序,如【例6.25】所示。

【例6.25】查询fruits表,先按f_price降序排序,再按f_name字段升序排序,SQL语句如下:

查询结果如下:

DESC排序方式只应用到直接位于其前面的字段上,由结果可以看出。

提示

DESC关键字只对其前面的列进行降序排列,在这里只对f_price排序,而并没有对f_name进行排序,因此,f_price按降序排序,而f_name列仍按升序排序。如果要对多列都进行降序排序,必须要在每一列的列名后面加DESC关键字。

6.2.12 分组查询

分组查询是对数据按照某个或多个字段进行分组,Oracle中使用GROUP BY关键字对数据进行分组,基本语法形式为:

字段值为进行分组时所依据的列名称。“HAVING<条件表达式>”指定满足表达式限定条件的结果将被显示。

1.创建分组

GROUP BY关键字通常和集合函数一起使用,例如MAX()、MIN()、COUNT()、SUM()、AVG()。例如,要返回每个水果供应商提供的水果种类,这时就要在分组过程中用到COUNT()函数,把数据分为多个逻辑组,并对每个组进行集合计算。

【例6.26】根据s_id对fruits表中的数据进行分组,SQL语句如下:

查询结果如下:

查询结果显示,s_id表示供应商的ID,Total字段使用COUNT()函数计算得出,GROUP BY子句按照s_id排序并对数据分组,可以看到ID为101、102、105的供应商分别提供3种水果,ID为103、104、107的供应商分别提供2种水果,ID为106的供应商只提供一种水果。

如果要查看每个供应商提供的水果的种类的名称,该怎么办呢?Oracle中可以在GROUP BY字节中使用LISTAGG()函数,将每个分组中各个字段的值显示出来。

【例6.27】根据s_id对fruits表中的数据进行分组,将每个供应商的水果名称显示出来,SQL语句如下:

查询结果如下:

由结果可以看到,LISTAGG()函数将每个分组中的名称显示出来了,其名称的个数与COUNT()函数计算出来的相同。

2.使用HAVING过滤分组

GROUP BY可以和HAVING一起限定显示记录所需满足的条件,只有满足条件的分组才会被显示。

【例6.28】根据s_id对fruits表中的数据进行分组,并显示水果种类大于1的分组信息,SQL语句如下:

查询结果如下:

由结果可以看到,ID为101、102、103、104、105、107的供应商提供的水果种类大于1,满足HAVING子句条件,因此出现在返回结果中。而ID为106的供应商的水果种类等于1,不满足限定条件,因此不在返回结果中。

提示

HAVING关键字与WHERE关键字都是用来过滤数据,两者有什么区别呢?其中重要的一点是,HAVING在数据分组之后进行过滤来选择分组,而WHERE在分组之前用来选择记录。另外,WHERE排除的记录不再包括在分组中。

3.在GROUP BY子句中使用ROLLUP

使用ROLLUP关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。

【例6.29】根据s_id对fruits表中的数据进行分组,并显示记录数量,SQL语句如下:

查询结果如下:

由结果可以看到,通过GROUP BY分组之后,在显示结果的最后面新添加了一行,该行Total列的值正好是上面所有数值之和。

4.多字段分组

使用GROUP BY可以对多个字段进行分组,GROUP BY关键字后面跟需要分组的字段,Oracle根据多字段的值来进行层次分组。分组层次从左到右,即先按第1个字段分组,然后在第1个字段值相同的记录中,再根据第2个字段的值进行分组,以此类推。

【例6.30】根据s_id和f_name字段对fruits表中的数据进行分组,SQL语句如下,

查询结果如下:

由结果可以看到,查询记录先按照s_id进行分组,再对f_name字段按不同的取值进行分组。

5.GROUP BY和ORDER BY一起使用

某些情况下需要对分组进行排序,在前面的介绍中,ORDER BY用来对查询的记录排序,如果和GROUP BY一起使用可以完成对分组的排序。

为了演示效果,首先创建数据表,SQL语句如下:

然后插入演示数据。SQL语句如下:

【例6.31】查询订单价格大于100的订单号和总订单价格,SQL语句如下:

查询结果如下:

可以看到,返回的结果中orderTotal列的总订单价格并没有按照一定顺序显示,接下来,使用ORDER BY关键字按总订单价格排序显示结果,SQL语句如下:

查询结果如下:

由结果可以看到,GROUP BY子句按订单号对数据进行分组,SUM()函数便可以返回总的订单价格,HAVING子句对分组数据进行过滤,使得只返回总价格大于100的订单,最后使用ORDER BY子句排序输出。

提示

当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。

6.2.13 使用ROWNUM限制查询结果的数量

SELECT返回所有匹配的行,有可能是表中所有的行,如仅仅需要返回第一行或者前几行,则可以使用ROWNUM来限制。

【例6.32】显示fruits表查询结果的前4行,SQL语句如下:

查询结果如下:

由结果可以看到,显示结果从第一行开始,“行数”为小于5行,因此返回的结果为表中的前4行记录。

提示

使用rownum时,只支持<、<=和!=符号,不支持>、>=、=和between...and符号。