分组数据
数据分组
分组是在SELECT语句的GROUP BY子句中建立的。
GROUP BY子句可以包含任意数目的列。这使得能对分组进行嵌套, 为数据分组提供更细致的控制。
如果在GROUP BY子句中嵌套了分组,数据将在最后规定的分组上 进行汇总。换句话说,在建立分组时,指定的所有列都一起计算
(所以不能从个别的列取回数据)。GROUP BY子句中列出的每个列都必须是检索列或有效的表达式
(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在 GROUP BY子句中指定相同的表达式。不能使用别名。除聚集计算语句外,SELECT语句中的每个列都必须在GROUP BY子 句中给出。
如果分组列中具有NULL值,则NULL将作为一个分组返回。如果列 中有多行NULL值,它们将分为一组。
GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。
过滤分组
除了能用 GROUP BY
分组数据外,MySQL
还允许过滤分组,规定包括 哪些分组,排除哪些分组。例如,可能想要列出至少有两个订单的所有 顾客。为得出这种数据,必须基于完整的分组而不是个别的行进行过滤
MySQL
为此目的提供了另外的子句,那就是 HAVING
子句。HAVING
非常类似于 WHERE
。事实上,目前为止所 学过的所有类型的 WHERE
子句都可以用 HAVING
来替代。唯一的差别是 WHERE
过滤行,而 HAVING
过滤分组。
HAVING和WHERE的差别 这里有另一种理解方法,WHERE在数据 分组前进行过滤,HAVING在数据分组后进行过滤。这是一个重 要的区别,WHERE排除的行不包括在分组中。这可能会改变计 算值,从而影响HAVING子句中基于这些值过滤掉的分组。
排序数据
使用 ORDER BY
子句
多个列排序
SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price, prod_name;
首先按照价格排序,在按照名称排序
排序完全按所规定的顺序进行。
换句话说,对于上述例子中的输出,仅在多个行具有相同的 prod_price
值时才对产品按 prod_name
进行排序。如果 prod_price
列中所有的值都是唯一的,则不会按 prod_name
排序。
指定排序方向
DESC 降序
ASC 升序(默认)
SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price DESC, prod_name;
关键字值应用到直接位于其前面的列名。上例中,只对 prod_price
列指定 DESC
,对
prod_name
列不指定。因此,prod_price
列以降序排序,而prod_name列(在每个价格内)仍然按标准 的升序排序。
区分大小写和排序顺序 在对文本性的数据进行排序时,A与a相同吗?a位于B之前还是位于Z之后?这些问题不是理论问题,其答案取决于数据库如何设置。在字典(dictionary)排序顺序中,A被视为与a相同,这是MySQL (和大多数数据库管理系统)的默认行为。但是,许多数据库 管理员能够在需要时改变这种行为(如果你的数据库包含大量外语字符,可能必须这样做)。这里,关键的问题是,如果确实需要改变这种排序顺序,用简 单的ORDER BY子句做不到。你必须请求数据库管理员的帮助。
使用 ORDER BY
和 LIMIT
的组合,能够找出一个列中最高或最低的值。
WHERE 子句操作符
操作符 | 说明 |
---|---|
= | 等于 |
<> | 不等于 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
BETWEEN | 在指定的两个值之间 |
示例:
SELECT
*
FROM
product
WHERE
id BETWEEN 1 AND 100;
在使用BETWEEN时,必须指定两个值——所需范围的低端值和高端值。这两个值必须用AND关键字分隔。BETWEEN匹配范围中所有的值,包括指定的开始值和结束值。
空值检查
在创建表时,表设计人员可以指定其中的列是否可以不包含值。在一个列不包含值时,称其为包含空值 NULL
。
NULL
无值(no value),它与字段包含0、空字符串或仅仅包含空格不同。
SELECT
*
FROM
product
WHERE
id IS NOT NULL;
NULL与不匹配 在通过过滤选择出不具有特定值的行时,你 可能希望返回具有NULL值的行。但是,不行。因为未知具有 特殊的含义,数据库不知道它们是否匹配,所以在匹配过滤 或不匹配过滤时不返回它们。因此,在过滤数据时,一定要验证返回数据中确实给出了被 过滤列具有NULL的行。
OR 操作符
OR
操作符与 AND
操作符不同,它指示MySQL检索匹配任一条件的行。
SELECT
*
FROM
product
WHERE
id = 1 OR id = 30;
计算次序
WHERE
可包含任意数目的 AND 和
OR 操作符。允许两者结合以进行复杂和高级的过滤。
但是,组合AND和OR带来了一个有趣的问题。为了说明这个问题,来 看一个例子。假如需要列出价格为10美元(含)以上且由1002或1003制 造的所有产品。下面的 SELECT
语句使用 AND
和 OR
操作符的组合建立了一个 WHERE
子句:
SELECT prod_name, prod_price
FROM products
WHERE vend_id = 1002 OR vend_id = 1003 AND prod_price >= 10;
prod_name | prod_price |
---|---|
Detonator | 13.00 |
Bird seed | 10.00 |
Fuses | 3.42 |
Oil can | 8.99 |
Safe | 50.00 |
TNT (5 sticks) | 10.00 |
请看上面的结果。返回的行中有两行价格小于10美元,显然,返回的行未按预期的进行过滤。为什么会这样呢?
原因在于计 算的次序。SQL(像多数语言一样)在处理OR操作符前,优先处理AND操 作符。当SQL看到上述 WHERE
子句时,它理解为由供应商1003制造的任何价格为10美元(含)以上的产品,或者由供应商1002制造的任何产品,而不管其价格如何。换句话说,由于 AND
在计算次序中优先级更高,操作符被错误地组合了。
此问题的解决方法是使用圆括号明确地分组相应的操作符。请看下 面的SELECT语句及输出:
SELECT prod_name, prod_price
FROM products
WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10;
这条SELECT语句与前一条的唯一差别是,这条语句中,前两个条件用圆括号括了起来。因为圆括号具有较AND或OR操作符高 的计算次序,DBMS首先过滤圆括号内的OR条件。这时,SQL语句变成了 选择由供应商1002或1003制造的且价格都在10美元(含)以上的任何产 品,这正是我们所希望的。
在
WHERE
子句中使用圆括号 任何时候使用具有AND
和OR
操作符的WHERE
子句,都应该使用圆括号明确地分组操作符。不要 过分依赖默认计算次序,即使它确实是你想要的东西也是如 此。使用圆括号没有什么坏处,它能消除歧义。
NOT 操作符
WHERE
子句中的 NOT
操作符有且只有一个功能,那就是否定它之后所跟的任何条件。
MySQL中的
NOT
MySQL支持使用NOT对IN
、BETWEEN
和EXISTS
子句取反,这与多数其他DBMS允许使用NOT对各种条件取反有很大的差别。
LIKE 操作符
百分号(%)通配符
最常使用的通配符是百分号(%)。在搜索串中,%表示任何字符出现 任意次数。例如,为了找出所有以词 jet 起头的产品,可使用以下 SELECT
语句:
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE 'jet%';
区分大小写 根据MySQL的配置方式,搜索可以是区分大小 写的。如果区分大小写,'jet%'与JetPack 1000将不匹配。
注意
NULL
虽然似乎%通配符可以匹配任何东西,但有一个例 外,即NULL
。即使是WHERE prod_name LIKE '%'
也不能匹配 用值NULL
作为产品名的行。
下划线(_)通配符
另一个有用的通配符是下划线(_)。下划线的用途与%一样,但下划线只匹配单个字符而不是多个字符。
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE '_ to anvil';
不要过度使用通配符。如果其他操作符能达到相同的目的,应该 使用其他操作符。
==在确实需要使用通配符时,除非绝对有必要,否则不要把它们用 在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起 来是最慢的。==
仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
用正则表达式进行搜索
基本字符匹配
SELECT prod_name
FROM products
WHERE prod_name REGEXP '1000'
ORDER BY prod_name;
LIKE匹配整个列。如果被匹配的文本在列值 中出现,LIKE将不会找到它,相应的行也不被返回(除非使用 通配符)。而REGEXP在列值内进行匹配,如果被匹配的文本在 列值中出现,REGEXP将会找到它,相应的行将被返回。这是一 个非常重要的差别。
进行 OR 匹配
SELECT prod_name
FROM products
WHERE prod_name REGEXP '1000|2000'
ORDER BY prod_name;
两个以上的 OR 条件 可以给出两个以上的 OR 条件。例如, '1000|2000|3000' 将匹配1000或2000或3000。
匹配几个字符之一
匹配任何单一字符。但是,如果你只想匹配特定的字符,怎么办?可通过指定一组用[和]括起来的字符来完成,如下所示:
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;
prod_name |
---|
1 ton anvil |
2 ton anvil |
JetPack 1000 |
JetPack 2000 |
TNT (1 stick) |
这并不是期望的输出。两个要求的行被检索出来,但还检索出了另外3行。之所以这样是由于MySQL假定你的意思是'1'或 '2'或'3 ton'。除非把字符|括在一个集合中,否则它将应用于整个串。
字符集合也可以被否定,即,它们将匹配除指定字符外的任何东西。 为否定一个字符集,在集合的开始处放置一个即可。因此,尽管[123]匹配字符1、2或3,但[123]却匹配除这些字符外的任何东西。
匹配范围
集合可用来定义要匹配的一个或多个字符。例如,下面的集合将匹 配数字0到9:
[0123456789]
为简化这种类型的集合,可使用-来定义一个范围。下面的式子功能 上等同于上述数字列表:
[0-9]
范围不限于完整的集合,[1-3]和[6-9]也是合法的范围。此外,范 围不一定只是数值的,[a-z]匹配任意字母字符。
举一个例子:
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[1-5] Ton'
ORDER BY prod_name;
prod_name |
---|
.5 ton anvil |
1 ton anvil |
2 ton anvil |
这里使用正则表达式[1-5] Ton。[1-5]定义了一个范围,这个表达式意思是匹配1到5,因此返回3个匹配行。由于5 ton匹配, 所以返回.5 ton。
匹配特殊字符
正则表达式语言由具有特定含义的特殊字符构成。我们已经看到.、[]、 |和-等,还有其他一些字符。请问,如果你需要匹配这些字符,应该怎么办呢?例如,如果要找出包含.字符的值,怎样搜索?请看下面的例子:
SELECT vend_name
FROM vendors
WHERE vend_name REGEXP '.'
ORDER BY vend_name;
vend_name |
---|
ACME |
Anvils R Us |
Furball Inc. |
Jes Set |
这并不是期望的输出,.匹配任意字符,因此每个行都被检索出来。
为了匹配特殊字符,必须用 \\
为前导。\\-
表示查找 -
,\\.
表示查找 .
。
SELECT vend_name
FROM vendors
WHERE vend_name REGEXP '\\.'
ORDER BY vend_name;
vend_name |
---|
Furball Inc. |
这才是期望的输出。\\.
匹配 .
,所以只检索出一行。这种处理
就是所谓的转义(escaping),正则表达式内具有特殊意义的所 有字符都必须以这种方式转义。这包括.、|、[]以及迄今为止使用过的其他特殊字符。
\\
也用来引用元字符(具有特殊含义的字符),如表
元字符 | 说明 |
---|---|
\\f |
换页 |
\\n |
换行 |
\\r |
回车 |
\\t |
制表 |
\\v |
纵向制表 |
匹配 \ 为了匹配反斜杠(
\
)字符本身,需要使用\\\
。
\
或\\?
多数正则表达式实现使用单个反斜杠转义特殊字符,以便能使用这些字符本身。但MySQL要求两个反斜杠(MySQL 自己解释一个,正则表达式库解释另一个)。
匹配字符类
存在找出你自己经常使用的数字、所有字母字符或所有数字字母字 符等的匹配。为更方便工作,可以使用预定义的字符集,称为字符类(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]) |
匹配多个实例
目前为止使用的所有正则表达式都试图匹配单次出现。如果存在一 个匹配,该行被检索出来,如果不存在,检索不出任何行。但有时需要 对匹配的数目进行更强的控制。例如,你可能需要寻找所有的数,不管 数中包含多少数字,或者你可能想寻找一个单词并且还能够适应一个尾 随的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;
prod_name |
---|
TNT(1 stick) |
TNT(5 sticks) |
正则表达式 \\([0-9] sticks?\\)
需要解说一下。\\
(匹配),
[0-9]匹配任意数字(这个例子中为1和5),sticks? 匹配 stick 和 sticks (s后的?使s可选,因为?匹配它前面的任何字符的0次或1次出现),\\
)匹配)。没有?,匹配 stick 和 sticks 会非常困难。
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[[:digit:]]{4}'
ORDER BY prod_name;
prod_name |
---|
JetPack 1000 |
JetPack 2000 |
如前所述,[:digit:]匹配任意数字,因而它为数字的一个集
合。{4}确切地要求它前面的字符(任意数字)出现4次,所以 [[:digit:]]{4} 匹配连在一起的任意4位数字。
定位符
目前为止的所有例子都是匹配一个串中任意位置的文本。为了匹配特定位置的文本,需要使用表列出的定位符。
元字符 | 说明 |
---|---|
^ | 文本的开始 |
$ | 文本的结尾 |
[[:<:]] | 词的开始 |
[[:>:]] | 词的结尾 |
例如,如果你想找出以一个数(包括以小数点开始的数)开始的所 有产品,怎么办?简单搜索 [0-9\\.]
(或[[:digit:]\\.]
)不行,因为 它将在文本内任意位置查找匹配。解决办法是使用^定位符,如下所示:
SELECT prod_name
FROM products
WHERE prod_name REGEXP '^[0-9\\.]'
ORDER BY prod_name;
prod_name |
---|
.5 ton anvil |
1 ton anvil |
2 ton anvil |
^ 匹配串的开始。因此,^[0-9\\.]
只在.或任意数字为串中第
一个字符时才匹配它们。没有 ^,则还要多检索出4个别的行(那 些中间有数字的行)。
^ 的双重用途 ^ 有两种用法。在集合中(用[和]定义),用它 来否定该集合,否则,用来指串的开始处。
使 REGEXP 起类似 LIKE 的作用 本章前面说过,LIKE 和 REGEXP 的不同在于,LIKE 匹配整个串而 REGEXP 匹配子串。利用定位符,通过用^开始每个表达式,用$结束每个表达式,可以使 REGEXP 的作用与 LIKE 一样。
简单的正则表达式测试 可以在不使用数据库表的情况下用 SELECT来测试正则表达式。REGEXP检查总是返回0(没有匹配) 或1(匹配)。可以用带文字串的REGEXP来测试表达式,并试 验它们。相应的语法如下:
SELECT 'hello' REGEXP '[0-9]';
这个例子显然将返回0(因为文本hello中没有数字)。
汇总数据
聚集函数
函数 | 说明 |
---|---|
AVG() | 返回某列的平均值 |
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
SUM() | 返回某列值之和 |
对非数值数据使用 MAX() 虽然 MAX() 一般用来找出最大的数值或日期值,但 MySQL 允许将它用来返回任意列中的最大值,包括返回文本列中的最大值。在用于文本数据时,如果数据按相应的列排序,则 MAX() 返回最后一行。
使用子查询
利用子查询进行过滤
SELECT cust_id
FROM orders
WHERE order_num IN (
SELECT order_num
FROM orderitems
WHERE prod_id = 'TNT2'
);
在 SELECT 语句中,子查询总是从内向外处理。在处理上面的 SELECT 语句时,MySQL 实际上执行了两个操作。
列必须匹配 在 WHERE 子句中使用子查询(如这里所示),应 该保证 SELECT 语句具有与 WHERE 子句中相同数目的列。通常, 子查询将返回单个列并且与单个列匹配,但如果需要也可以 使用多个列。
联结表
创建联结
SELECT vend_name, prod_name, prod_price
FROM vendors, products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend_name, prod_name;
我们来考察一下此代码。SELECT 语句与前面所有语句一样指定
要检索的列。这里,最大的差别是所指定的两个列(prod_name 和 prod_price)在一个表中,而另一个列(vend_name)在另一个表中。
内部联结
目前为止所用的联结称为等值联结(equijoin),它基于两个表之间的 相等测试。这种联结也称为内部联结。其实,对于这种联结可以使用稍 微不同的语法来明确指定联结的类型。下面的 SELECT 语句返回与前面例 子完全相同的数据:
SELECT vend_name,prod_name,prod_price
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id;
此语句中的 SELECT 与前面的 SELECT 语句相同,但 FROM 子句不
同。这里,两个表之间的关系是FROM子句的组成部分,以 INNER JOIN 指定。在使用这种语法时,联结条件用特定的 ON 子句而不是 WHERE 子句给出。传递给 ON 的实际条件与传递给 WHERE 的相同。
使用哪种语法 ANSI SQL 规范首选 INNER JOIN 语法。此外, 尽管使 用WHERE 子句定义联结的确比较简单,但是使用明确的 联结语法能够确保不会忘记联结条件,有时候这样做也能影响 性能。
==性能考虑== MySQL在运行时关联指定的每个表以处理联结。 这种处理可能是非常耗费资源的,因此应该仔细,不要联结 不必要的表。联结的表越多,性能下降越厉害。
自联结
如前所述,使用表别名的主要原因之一是能在单条SELECT语句中不 止一次引用相同的表。下面举一个例子。
假如你发现某物品(其ID为DTNTR)存在问题,因此想知道生产该物 品的供应商生产的其他物品是否也存在这些问题。此查询要求首先找到 生产ID为DTNTR的物品的供应商,然后找出这个供应商生产的其他物品。 下面是解决此问题的一种方法:
SELECT prod_id, prod_name
FROM products
WHERE vend_id = (
SELECT vend_id
FROM products
WHERE prod_id = 'DTNTR'
);
这是第一种解决方案,它使用了子查询。内部的SELECT语句做了一个简单的检索,返回生产ID为DTNTR的物品供应商的 vend_id。该ID用于外部查询的 WHERE 子句中,以便检索出这个供应商生产的所有物品(第14章中讲授了子查询的所有内容。更多信息请参阅该章)。
现在来看使用联结的相同查询:
SELECT p1.prod_id, p1.prod_name
FROM products AS p1, products AS p2
WHERE p1.vend_id = p2.vned_id
AND p2.prod = 'DTNTR';
此查询中需要的两个表实际上是相同的表,因此 products 表在 FROM 子句中出现了两次。虽然这是完全合法的,但对 products 的引用具有二义性,因为 MySQL 不知道你引用的是 products 表中的哪个实例。
为解决此问题,使用了表别名。products 的第一次出现为别名p1, 第二次出现为别名p2。现在可以将这些别名用作表名。例如,SELECT语 句使用p1前缀明确地给出所需列的全名。如果不这样,MySQL将返回错 误,因为分别存在两个名为 prod_id、prod_name 的列。MySQL不知道想要的是哪一个列(即使它们事实上是同一个列)。WHERE(通过匹配p1中的vend_id和p2中的vend_id)首先联结两个表,然后按第二个表中的 prod_id过滤数据,返回所需的数据。
用自联结而不用子查询 自联结通常作为外部语句用来替代 从相同表中检索数据时使用的子查询语句。虽然最终的结果是 相同的,但有时候处理联结远比处理子查询快得多。应该试一 下两种方法,以确定哪一种的性能更好。
插入数据
插入一行
INSERT INTO send_email ( pro_id, send_time, sended_date )
VALUES
( '387', '16:30', '2019-01-17' );
插入多行
INSERT INTO send_email ( pro_id, send_time, sended_date )
VALUES
( '387', '16:30', '2019-01-17' ),
( '388', '16:30', '2019-01-17' );
更新数据
为了更新(修改)表中的数据,可使用UPDATE语句。可采用两种方式使用 UPDATE:
更新表中特定行。
更新表中所有行。
UPDATE语句非常容易使用,甚至可以说是太容易使用了。基本的 UPDATE语句由3部分组成,分别是:
要更新的表。
列名和它们的新值。
确定要更新行的过滤条件。
UPDATE CUSTOMERS
SET cust_email = 'elmer@fudd.com'
WHERE cust_id = 10005;