- 过滤数据
第6章 过滤数据
6.1 ★使用 WHERE 子句
数据库表一般包含大量的数据,很少需要检索表中所有行。通常只会根据特定操作或报告的需要提取表数据的子集。只检索所需数据需要指定搜索条件(search criteria),搜索条件也称为过滤条件(filter condition)。
在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。WHERE子句在表名(FROM子句)之后给出,如下所示:
SELECT prod_id, prod_price, prod_name FROM products WHERE prod_price = 2.50;
SQL过滤与应用过滤:数据也可以在应用层过滤。为此,SQL的SELECT语句为客户机应用检索出超过实际所需的数据,然后客户机代码对返回数据进行循环,以提取出需要的行。通常,这种实现并不令人满意。因此,对数据库进行了优化,以便快速有效地对数据进行过滤。让客户机应用(或开发语言)处理数据库的工作将会极大地影响应用的性能,并且使所创建的应用完全不具备可伸缩性。此外,如果在客户机上过滤数据,服务器不得不通过网络发送多余的数据,这将导致网络带宽的浪费。
WHERE子句的位置:在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后,否则将会产生错误。
SELECT prod_id, prod_price, prod_name FROM products WHERE prod_price = 2.50 ORDER BY prod_name DESC;
6.2 WHERE子句操作符
- 在
WHERE
子句中经常使用的运算符:
操作符 | 说明 |
---|---|
= | 等于 |
<> | 不等于 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
BETWEEN …AND… | 在指定的两个值之间 |
IS NULL | 是否为空 |
AND | 多个条件同时成立 |
OR | 多个条件任一成立 |
NOT | 不成立 |
IN(set) | 显示在 set 列表中的值,如 in(100, 200) |
LIKE ‘pattern’ | 模糊查询 |
6.2.1 检索单个值
SELECT prod_id, prod_price, prod_name FROM products WHERE prod_name = 'fuses';
WHERE prod_name=‘fuses’
子句,它返回prod_name
的值为Fuses
的一行。★MySQL在执行匹配时默认不区分大小写,所以fuses
与Fuses
匹配。列出价格小于10美元的所有产品:
SELECT prod_id, prod_price, prod_name FROM products WHERE prod_price < 10;
6.2.2 不匹配检索
以下例子列出不是由供应商1003制造的所有产品:
SELECT prod_id, prod_price, prod_name FROM products WHERE vend_id <> 1003;
或者SELECT prod_id, prod_price, prod_name FROM products WHERE vend_id != 1003;
何时使用引号:如果仔细观察上述WHERE子句中使用的条件,会看到有的值括在单引号内(如前面使用的’fuses’),而有的值未括起来。单引号用来限定字符串。如果将值与串类型的列进行比较,则需要限定引号。用来与数值列进行比较的值不用引号。
6.2.3 范围值检索BETWEEN
为了检索某个范围的值,可使用BETWEEN操作符。其语法与其他WHERE子句的操作符稍有不同,因为它需要两个值,即范围的开始值和结束值。
下面的例子说明如何使用BETWEEN操作符,它检索价格在5美元和10美元之间的所有产品:
SELECT prod_id, prod_name, prod_price FROM products WHERE prod_price BETWEEN 5 AND 10;
从这个例子中可以看到,在使用BETWEEN时,必须指定两个值——所需范围的低端值和高端值。这两个值必须用AND关键字分隔。BETWEEN匹配范围中所有的值,包括指定的开始值和结束值。
6.2.4 空值检索IS NULL
在创建表时,表设计人员可以指定其中的列是否可以不包含值。在一个列不包含值时,称其为包含空值NULL。
NULL 无值(no value):它与字段包含0、空字符串或仅仅包含空格不同。
SELECT语句有一个特殊的WHERE子句,可用来检索具有NULL值的列,即若指定的列中的某几行的值为NULL,则这几行被检索出来。这个WHERE子句就是IS NULL子句。其语法如下:
SELECT prod_name FROM products WHERE prod_price IS NULL;
这条语句返回没有价格(空prod_price字段,不是价格为0)的所有产品,由于表中没有这样的行,所以没有返回数据。但是,customers表确实包含有具有空值的列,如果在文件中没有某位顾客的电子邮件地址,则cust_email列将包含NULL值:
SELECT cust_id FROM customers WHERE cust_email IS NULL;
★NULL与不匹配:在通过过滤选择出不具有特定值的行时,你可能希望返回具有NULL值的行,但是,不行。因为未知具有特殊的含义,数据库不知道它们是否匹配,所以在匹配过滤或不匹配过滤时不返回它们。 因此,在过滤数据时,一定要验证返回数据中确实给出了被过滤列具有NULL的行。
例如:
SELECT cust_id FROM customers WHERE cust_email != '';
,在 customers 表中有五行数据,其中两行数据的 cust_email 为 null,但是检索时不返回,只返回了三行数据。
第7章 数据过滤
7.1 组合 WHERE 子句
第6章中介绍的所有WHERE子句在过滤数据时使用的都是单一的条件。为了进行更强的过滤控制,MySQL允许给出多个WHERE子句。这些子句可以两种方式使用:以AND子句的方式或OR子句的方式使用。
操作符(operator):用来联结或改变WHERE子句中的子句的关键字。也称为逻辑操作符(logical operator)。
7.1.1 AND 操作符
为了通过不止一个列进行过滤,可使用AND操作符给WHERE子句附加条件。下面的代码给出了一个例子:
SELECT prod_id, prod_price, prod_name FROM products WHERE vend_id = 1003 AND prod_price <=10;
此SQL语句检索由供应商1003制造且价格小于等于10美元的所有产品的名称和价格。这条SELECT语句中的WHERE子句包含两个条件,并且用AND关键字联结它们。AND指示DBMS只返回满足所有给定条件的行。如果某个产品由供应商1003制造,但它的价格高于10美元,则不检索它。类似,如果产品价格小于10美元,但不是由指定供应商制造的也不被检索。
AND:用在WHERE子句中的关键字,用来指示检索满足所有给定条件的行。
上述例子中使用了只包含一个关键字AND的语句,把两个过滤条件组合在一起。还可以添加多个过滤条件,每添加一条就要使用一个AND。
7.1.2 OR 操作符
OR操作符与AND操作符不同,它指示MySQL检索匹配任一条件的行。
SELECT prod_id, prod_price, prod_name FROM products WHERE vend_id = 1002 OR vend_id = 1003;
此SQL语句检索由任一个指定供应商制造的所有产品的产品名和价格。OR操作符告诉DBMS匹配任一条件而不是同时匹配两个条件。如果这里使用的是AND操作符,则没有数据返回(此时创建的WHERE子句不会检索到匹配的产品)。
OR:WHERE子句中使用的关键字,用来表示检索匹配任一给定条件的行。
7.1.3 计算次序
WHERE可包含任意数目的AND和OR操作符。允许两者结合以进行复杂和高级的过滤。但是,组合AND和OR带来了一个有趣的问题——优先级的问题。SQL(像多数语言一样)在处理OR操作符前,优先处理AND操作符。此问题的解决方法是使用圆括号明确地分组相应的操作符。
SELECT prod_id, prod_price, prod_name, vend_id FROM products WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >=10;
-> SQL语句的含义是选择由供应商1002或1003制造的且价格都在10美元(含)以上的任何产品。在WHERE子句中使用圆括号:任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认计算次序,即使它确实是你想要的东西也是如此。使用圆括号没有什么坏处,它能消除歧义。
7.2 IN 操作符
圆括号在WHERE子句中还有另外一种用法。IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN取由逗号分隔的清单中的合法值,它们全都括在圆括号中。
SELECT prod_id, prod_price, prod_name FROM products WHERE vend_id IN (1002,1003) AND prod_price >= 10;
这一句和前面一句的含义与作用相同。此SELECT语句检索由供应商1002或1003制造的且价格都在10美元(含)以上的任何产品。IN操作符后跟由逗号分隔的合法值清单,整个清单必须括在圆括号中。注意:IN操作符完成与OR相同的功能,但是更简短。
IN:WHERE子句中用来指定要匹配值的清单的关键字,功能与OR相当。
为什么要使用IN操作符?其优点具体如下。
- 在使用长的合法选项清单时,IN操作符的语法更清楚且更直观。
- 在使用IN时,计算的次序更容易管理(因为使用的操作符更少)。
- IN操作符一般比OR操作符清单执行更快。
- IN的最大优点是可以包含其他SELECT语句,使得能够更动态地建立WHERE子句。
7.3 NOT 操作符
WHERE子句中的NOT操作符有且只有一个功能,那就是否定它之后所跟的任何条件。
NOT:WHERE子句中用来否定后跟条件的关键字。
下面的例子说明NOT的使用。为了列出除1002和1003之外的所有供应商制造的产品,可编写如下的代码:
SELECT prod_id, prod_name, prod_price FROM products WHERE vend_id NOT IN (1002,1003);
这里的NOT否定跟在它之后的条件,因此,MySQL不是匹配1002和 1003 的 vend_id ,而是匹配 1002 和 1003 之外供应商的vend_id。
为什么使用NOT?对于简单的WHERE子句,使用NOT确实没有什么优势。但在更复杂的子句中,NOT是非常有用的。例如,在与IN操作符联合使用时,NOT使得找出与条件列表不匹配的行非常简单。
第8章 用通配符进行过滤
8.1 LIKE 操作符
通配符(wildcard):用来匹配 “值的一部分” 的特殊字符。
搜索模式(search pattern):由字面值、通配符或两者组合构成的搜索条件。
为在搜索子句中使用通配符,必须使用LIKE操作符。LIKE指示MySQL后跟的搜索模式利用通配符匹配而不是直接相等匹配进行比较。
使用LIKE,能够查找包含特殊值或部分值的行(不管这些值位于列内什么位置)。
8.1.1 百分号(%)通配符
最常使用的通配符是百分号(%)。在搜索串中,**%表示任何字符出现任意次数**。
例如,为了找出所有以词jet起头的产品,可使用以下SELECT语句:
SELECT prod_id, prod_name FROM products WHERE prod_name LIKE 'jet%';
在执行这条子句时,将检索任意以jet起头的词。%告诉MySQL接受jet之后的任意字符,不管它有多少字符。注意:区分大小写:根据MySQL的配置方式,搜索可以是区分大小写的。如果区分大小写,
'jet%'
与JetPack 1000
将不匹配。通配符可在搜索模式中任意位置使用,并且可以使用多个通配符。下面的例子使用两个通配符,它们位于模式的两端:
SELECT prod_id, prod_name FROM products WHERE prod_name LIKE '%anvil%';
搜索模式
'%anvil%'
表示匹配任何位置包含文本anvil
的值,而不论它之前或之后出现什么字符。注意尾空格:尾空格可能会干扰通配符匹配。例如,在保存词anvil 时,如果它后面有一个或多个空格,则子句WHERE prod_name LIKE ‘%anvil’ 将不会匹配它们,因为在最后的 l 后有多余的字符。解决这个问题的一个简单的办法是在搜索模式最后附加一个%。一个更好的办法是使用函数(第11章将会介绍)去掉首尾空格。例如:
SELECT prod_name FROM products WHERE Trim(prod_name) LIKE '_ ton%' ORDER BY prod_name;
注意:除了一个或多个字符外,**%还能匹配0个字符。%代表搜索模式中给定位置的0个、1个或多个字符。**
注意NULL:虽然似乎%通配符可以匹配任何东西,但有一个例外,即
NULL
。即使是WHERE prod_name LIKE '%'
也不能匹配用值NULL
作为产品名的行。
8.1.2 下划线(_)通配符
另一个有用的通配符是下划线(_)。下划线的用途与%一样,但下划线只匹配单个字符而不是多个字符。
SELECT prod_id, prod_name FROM products WHERE prod_name LIKE '_ ton anvil';
与%能匹配0个字符不一样,**_总是匹配一个字符,不能多也不能少。**
8.2 使用通配符的技巧
不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
★第9章 用正则表达式进行搜索
9.1 正则表达式介绍
- 正则表达式是用来匹配文本的特殊的串(字符集合)。
- 如果你想从一个文本文件中提取电话号码,可以使用正则表达式。如果你需要查找名字中间有数字的所有文件,可以使用一个正则表达式。如果你想在一个文本块中找到所有重复的单词,可以使用一个正则表达式。如果你想替换一个页面中的所有URL为这些URL的实际HTML链接,也可以使用一个正则表达式(对于最后这个例子,或者是两个正则表达式)。
9.2 使用MySQL正则表达式
- 正则表达式与MySQL有何关系?已经说过,正则表达式的作用是匹配文本,将一个模式(正则表达式)与一个文本串进行比较。MySQL用WHERE子句对正则表达式提供了初步的支持,允许你指定正则表达式,过滤SELECT检索出的数据。
- 注意:MySQL仅支持多数正则表达式实现的一个很小的子集。
9.2.1 基本字符匹配
我们从一个非常简单的例子开始。下面的语句检索列prod_name包含文本1000的所有行:
SELECT prod_name FROM products WHERE prod_name REGEXP '1000' ORDER BY prod_name;
除关键字LIKE被REGEXP替代外,这条语句看上去非常像使用LIKE的语句(第8章)。它告诉MySQL:REGEXP后所跟的东西作为正则表达式(与文字正文1000匹配的一个正则表达式)处理。
为什么要费力地使用正则表达式?在刚才的例子中,正则表达式确实没有带来太多好处(可能还会降低性能),不过,请考虑下面的例子:
SELECT prod_name FROM products WHERE prod_name REGEXP '.000' ORDER BY prod_name;
这里使用了正则表达式 .000 。**. 是正则表达式语言中一个特殊的字符。它表示匹配任意一个字符**,因此,1000和2000都匹配且返回。
LIKE与REGEXP的区别:在LIKE和REGEXP之间有一个重要的差别。请看以下两条语句:
SELECT prod_name FROM products WHERE prod_name LIKE '1000' ORDER BY prod_name;
SELECT prod_name FROM products WHERE prod_name REGEXP '1000' ORDER BY prod_name;
如果执行上述两条语句,会发现第一条语句不返回数据,而第二条语句返回一行。为什么?正如第8章所述,LIKE匹配整个列。如果被匹配的文本在列值中出现,LIKE将不会找到它,相应的行也不被返回(除非使用通配符)。而REGEXP在列值内进行匹配,如果被匹配的文本在列值中出现,REGEXP将会找到它,相应的行将被返回。这是一个非常重要的差别。
那么,REGEXP能不能用来匹配整个列值(从而起与LIKE相同的作用)?答案是肯定的,使用^和$定位符(anchor)即可。
匹配不区分大小写:MySQL中的正则表达式匹配(自版本3.23.4后)不区分大小写(即,大写和小写都匹配)。为区分大小写,可使用BINARY关键字,如WHERE prod_name REGEXP BINARY ‘JetPack .000’。
9.2.2 进行OR匹配
为搜索两个串之一(或者为这个串,或者为另一个串),使用 | ,如下所示:
SELECT prod_name FROM products WHERE prod_name REGEXP '1000|2000' ORDER BY prod_name;
语句中使用了正则表达式1000|2000。|为正则表达式的OR操作符。它表示匹配其中之一,因此1000和2000都匹配并返回。
9.2.3 匹配几个字符之一
. 匹配任何单一字符。但是,如果你只想匹配特定的字符,怎么办?可通过指定一组用 [ 和 ] 括起来的字符来完成,如下所示:
SELECT prod_name FROM products WHERE prod_name REGEXP '[123] ton' ORDER BY prod_name;
这里,使用了正则表达式[123] Ton。**[123]定义一组字符,它的意思是匹配1或2或3**,因此,1 ton和2 ton都匹配且返回(没有3 ton)。
正如所见,**[] 是另一种形式的OR语句**。事实上,正则表达式[123]Ton为[1|2|3]Ton的缩写,也可以使用后者。但是,需要用[]来定义OR语句查找什么。为更好地理解这一点,请看下面的例子:
SELECT prod_name FROM products WHERE prod_name REGEXP '1|2|3 ton' ORDER BY prod_name;
两个要求的行被检索出来,但还检索出了另外3行。之所以这样是由于MySQL假定你的意思是’1’或’2’或’3 ton’。除非把字符|括在一个集合中,否则它将应用于整个串。
字符集合也可以被否定,即,它们将匹配除指定字符外的任何东西。为否定一个字符集,在集合的开始处放置一个^即可。因此,尽管[123]匹配字符1、2或3,但 [^123] 却匹配除这些字符外的任何东西。
★9.2.4 匹配范围
★集合可用来定义要匹配的一个或多个字符。例如,下面的集合将匹配数字0到9:[0123456789] ,为简化这种类型的集合,可使用 - 来定义一个范围。下面的式子功能上等同于上述数字列表:[0-9] ,范围不限于完整的集合,[1-3]和[6-9]也是合法的范围。此外,范围不一定只是数值的,**[a-z]匹配任意字母字符**。
★匹配数字0到9:[0-9];匹配任意字母字符:[a-z]。
★9.2.5 匹配特殊字符
正则表达式语言由具有特定含义的特殊字符构成。我们已经看到 . 、[]、 | 和 - 等,还有其他一些字符。请问,如果你需要匹配这些字符,应该怎么办呢?例如,如果要找出包含 . 字符的值,怎样搜索?请看下面的例子:
SELECT vend_name FROM vendors WHERE vend_name REGEXP '\\.' ORDER BY vend_name;
为了匹配特殊字符,必须用\为前导。\- 表示查找 - ,\. 表示查找 . 。 这种处理就是所谓的转义(escaping),正则表达式内具有特殊意义的所有字符都必须以这种方式转义。这包括 . 、| 、[] 以及迄今为止使用过的其他特殊字符。
\ 也用来引用元字符(具有特殊含义的字符),如表9-1所列。
元字符 | 说明 |
---|---|
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 制表 |
\v | 纵向制表 |
匹配\:为了匹配反斜杠(\)字符本身,需要使用\\。
\ 或 \ ?:多数正则表达式实现使用单个反斜杠转义特殊字符,以便能使用这些字符本身。但MySQL要求两个反斜杠(MySQL自己解释一个,正则表达式库解释另一个)。
★9.2.6 匹配字符类
- 为更方便工作,可以使用预定义的字符集,称为字符类(character class),如下表所示。
类 | 说明 |
---|---|
[:alnum:] | 任意字母和数字(同[a-zA-Z0-9]) |
[:alpha:] | 任意字符(同[a-zA-Z]) |
[:blank:] | 空格和制表(同[\t]) |
[:cntrl:] | ASCII控制字符(ASCII 0到31和127) |
[:digit:] | 任意数字(同[0-9]) |
[:graph:] | 与[:print:]相同,但不包括空格 |
[:lower:] | 任意小写字母(同[a-z]) |
[:print:] | 任意可打印字符 |
[:punct:] | 既不在[:alnum:]又不在[:cntrl:]中的任意字符 |
[:space:] | 包括空格在内的任意空白字符(同[\f\n\r\t\v]) |
[:upper:] | 任意大写字母(同[A-Z]) |
[:xdigit:] | 任意十六进制数字(同[a-fA-F0-9]) |
★9.2.7 匹配多个实例
- 目前为止使用的所有正则表达式都试图匹配单次出现。如果存在一个匹配,该行被检索出来,如果不存在,检索不出任何行。但有时需要对匹配的数目进行更强的控制。例如,你可能需要寻找所有的数,不管数中包含多少数字,或者你可能想寻找一个单词并且还能够适应一个尾随的s(如果存在),等等。这可以用下表列出的正则表达式重复元字符来完成。
元字符 | 说明 |
---|---|
* | 0个或多个匹配 |
+ | 1个或多个匹配(等于{1,}) |
? | 0个或1个匹配(等于{0,1}) |
{n} | 指定数目的匹配 |
{n,} | 不少于指定数目的匹配 |
{n,m} | 匹配数目的范围(m不超过255) |
下面举几个例子。
SELECT prod_name FROM products WHERE prod_name REGEXP '\\([0-9] sticks?\\)' ORDER BY prod_name;
正则表达式 \([0-9] sticks?\) 说明:\( 匹配 (,\) 匹配 ),[0-9]匹配任意数字,sticks? 匹配 stick 和 sticks(s后的?使s可选,因为?匹配它 前面 的任何字符的0次或1次出现),如果没有?,匹配stick和sticks会非常困难。
SELECT prod_name FROM products WHERE prod_name REGEXP '[[:digit:]]{4}' ORDER BY prod_name;
如前所述,[:digit:] 匹配任意数字,因而它为数字的一个集合。{4}确切地要求它前面的字符(任意数字)出现4次,所以[[:digit:]]{4}匹配连在一起的任意4位数字。
注意:这里匹配字符类指的是形式,使用时一般在[]中使用 ,所以应为 [[:digit:]] ,而不只是 [:digit:]
★9.2.8 定位符
- 目前为止的所有例子都是匹配一个串中任意位置的文本。为了匹配特定位置的文本,需要使用下表中列出的定位符。
元字符 | 说明 |
---|---|
^ | 文本的开始 |
$ | 文本的结尾 |
[[:<:]] | 词的开始 |
[[:>:]] | 词的结尾 |
例如,如果你想找出以一个数(包括以小数点开始的数)开始的所有产品,怎么办?简单搜索[0-9\.](或[[:digit:]\.])不行,因为它将在文本内任意位置查找匹配。解决办法是使用^定位符,如下所示:
SELECT prod_name FROM products WHERE prod_name REGEXP '^[0-9\\.]' ORDER BY prod_name;
^匹配串的开始。因此,^[0-9\.]只在 . 或任意数字为串中第一个字符时才匹配它们。没有^,则还要多检索出4个别的行(那些中间有数字的行)。
^的双重用途:^有两种用法。在集合中(用 [ 和 ] 定义),用它来否定该集合,否则,用来指串的开始处。
使REGEXP起类似LIKE的作用:本章前面说过,LIKE和REGEXP的不同在于,LIKE匹配整个串而REGEXP匹配子串。利用定位符,通过用^开始每个表达式,用$结束每个表达式,可以使REGEXP的作用与LIKE一样。
简单的正则表达式测试:可以在不使用数据库表的情况下用SELECT来测试正则表达式。REGEXP检查总是返回0(没有匹配)或1(匹配)。可以用带文字串的REGEXP来测试表达式,并试验它们。相应的语法如下:
SELECT 'hello' REGEXP '[0-9]';
这个例子显然将返回0(因为文本hello中没有数字)。