《SQL必知必会》 第四版
SQL必知必会(第4版)[www.java1234.com].pdf
windows上使用mysql命令进入数据库
mysql -hlocalhost -uroot -p
参考B.10
USE database;
本机当前是root root
ubuntu 安装 mysql
apt install mysql-server
apt isntall mysql-client
create database learn_sql default character set utf8 collate utf8_general_ci;
use learn_sql;
第一课 了解SQL
1.1 数据库基础
1.1.1 数据库
数据库( database )
保存有组织的数据的容器(通常是一个文件或一组文件)。
数据库软件应称为 数据库管 理系统(即 DBMS ),数据库是通过 DBMS 创建和操纵的容器。
1.1.2 表
表( table )
某种特定类型数据的结构化清单。
模式 ( schema )
关于数据库和表的布局及特性的信息。
1.1.3 列和数据类型
列( column )
表中的一个字段。所有表都是由一个或多个列组成的。
数据类型
所允许的数据的类型。每个表列都有相应的数据类型,它限制(或允许)该列中存储的数据。
1.1.4 行
行( row )
表中的一个记录。
1.1.5 主键
主键( primary key )
一列(或一组列),其值能够唯一标识表中每一行。
表中的任何列都可以作为主键,只要它满足以下条件:
任意两行都不具有相同的主键值;
每一行都必须具有一个主键值(主键列不允许NULL值);
主键列中的值不允许修改或更新;
主键值不能重用(如果某行从表中删除,它的主键不能赋给以后的新行)。
在使用多列作为主键时,上述条件必须应用到所有列,
所有列值的组合必须是唯一的(但单个列的值可以不唯一)。
1.2 什么是 SQL
SQL (发音为字母 S-Q-L 或 sequel )是结构化查询语言( Structured Query Language )的缩写。 SQL 是一种专门用来与数据库沟通的语言。
标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL 。
1.3 动手实践
与其他任何语言一样,学习 SQL 的最好方法是自己动手实践。
1.4 小结
这一课介绍了什么是 SQL ,它为什么很有用。因为 SQL 是用来与数据库打交道的,所以,我们也复习了一些基本的数据库术语。
第 2 课 检索数据
2.1 SELECT 语句
关键字( keyword )
作为 SQL 组成部分的保留字。关键字不能用作表或列的名字。
2.2 检索单个列
SELECT prod_name FROM Products;
SELECT 列 FROM 表;
多条 SQL 语句必须以分号(;)分隔。
请注意, SQL 语句不区分大小写,因此SELECT与select是相同的。
在处理 SQL 语句时,其中所有空格都被忽略。
2.3 检索多个列
在选择多个列时,一定要在列名之间加上逗号,但最后一个列名后不加。
SELECT prod_id, prod_name, prod_price
FROM Products;
2.4 检索所有列
SELECT *
FROM Products;
虽然使用通配符能让你自己省事,不用明确列出所需列,但检索不需 要的列通常会降低检索和应用程序的性能。
使用通配符有一个大优点,检索未知列。
2.5 检索不同的值
办法就是使用DISTINCT关键字,顾名思义,它指示数据库只返回不同的值。
SELECT DISTINCT vend_id
FROM Products;
SELECT DISTINCT vend_id告诉 DBMS 只返回不同(具有唯一性)的vend_id行。
DISTINCT关键字作用于所有的列,不仅仅是跟在其后的那一列。
2.6 限制结果
返回有限的行数,各种数据库中的这一 SQL 实现并不相同。
SQL Server 和 Access 中
SELECT TOP 5 prod_name
FROM Products;
如果你使用的是 DB2
SELECT prod_name
FROM Products
FETCH FIRST 5 ROWS ONLY;
如果你使用 Oracle ,需要基于ROWNUM(行计数器)来计算行
SELECT prod_name
FROM Products
WHERE ROWNUM <=5;
如果你使用 MySQL 、 MariaDB 、 PostgreSQL 或者 SQLite ,需要使用LIMIT 子句
SELECT prod_name
FROM Products
LIMIT 5;
LIMIT 5 OFFSET 5指示 MySQL 等 DBMS 返回从第 5 行起的 5 行数据。第一个数字是指从哪儿开始,第二个数字是检索的行数。(应该说倒了)
SELECT prod_name
FROM Products
LIMIT 5 OFFSET 5;
MySQL 和 MariaDB 支持简化版的LIMIT 4 OFFSET 3语句,即LIMIT 3,4。
第一个被检索的行是第 0 行,而不是第 1 行。因此,LIMIT 1 OFFSET 1会检索第 2 行,而不是第 1 行。
2.7 使用注释
很多 DBMS 都支持各种形式的注释语法。
行内注释:
SELECT prod_name
FROM Products;
注释使用-- (两个连字符)嵌在行内。
下面是另一种形式的行内注释(虽然这种形式很少得到支持)。
# 这是一条注释
SELECT prod_name
FROM Products;
在一行的开始处使用#,这一整行都将作为注释。
你也可以进行多行注释
/* SELECT prod_name, vend_id
FROM Products; */
SELECT prod_name
FROM Products;
注释从/*开始,到*/结束,/*和*/之间的任何内容都是注释。
第 3 课 排序检索数据
3.1 排序数据
子句( clause )
SQL 语句由子句构成,有些子句是必需的,有些则是可选的。一个子句通常由一个关键字加上所提供的数据组成。子句的例子有我们在前一 课看到的SELECT语句的FROM子句。
ORDER BY子句取一个或多个列的名字,据此对输出进行排序。
SELECT prod_name
FROM Products
ORDER BY prod_name;
在指定一条ORDER BY子句时,应该保证它是SELECT语句中最后一条子句。如果它不是最后的子句,将会出现错误消息。
用非检索的列排序数据是完全合法的。
3.2 按多个列排序
要按多个列排序,简单指定列名,列名之间用逗号分开即可。
下面的代码检索 3 个列,并按其中两个列对结果进行排序 —— 首先按价格,然后按名称排序。
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price, prod_name;
对于上述例子中的输出,仅在多个行具有相同的prod_price值时才
对产品按prod_name进行排序。
3.3 按列位置排序
除了能用列名指出排序顺序外,ORDER BY还支持按相对列位置进行排序。
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY 2, 3;
这一技术的主要好处在于不用重新输入列名。但它也有缺点。首先,不明确给出列名有可能造成错用列名排序。其次,在对SELECT清单进行更 改时容易错误地对数据进行排序(忘记对ORDER BY子句做相应的改动)。最后,如果进行排序的列不在SELECT清单中,显然不能使用这项技 术。
3.4 指定排序方向
为了进行降序排序,必须 指定DESC关键字。
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC;
如果打算用多个列排序
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC, prod_name;
DESC关键字只应用到直接位于其前面的列名。如果想在多个列上进行降序排序,必须对每一列指定DESC关键字。
DESC是DESCENDING的缩写,这两个关键字都可以使用。与DESC相对的是ASC(或ASCENDING),在升序排序时可以指定它。但实际 上,ASC没有多大用处,因为升序是默认的(如果既不指定ASC也不指定DESC,则假定为ASC)
在字典( dictionary )排序顺序中,A被视为与a相同,但是,许多 DBMS 允许数据库管理员在需要时 改变这种行为(如果你的数据库包含大量外语字符,可能必须这样做)。
第 4 课 过滤数据
4.1 使用 WHERE 子句
只检索所需数据需要指 定 搜索条件( search criteria ),搜索条件也称为 过滤条件( filter condition )。
WHERE子句在表名(FROM子句)之后给出
SELECT prod_name, prod_price
FROM Products
WHERE prod_price = 3.49;
数据也可以在应用层过滤,然后客户端代码对返回数据进行循环,提取 出需要的行。通常,这种做法极其不妥。
在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后。
4.2 WHERE 子句操作符
操作符 说 明
= 等于
<> 不等于
!= 不等于
< 小于
<= 小于等于
! 不小于
> 大于
>= 大于等于
!> 不大于
BETWEEN 在指定的两个值之间
IS NULL 为 NULL 值
表 4-1 中列出的某些操作符是冗余的(如< >与!=相同,!<相当于>=)。并非所有 DBMS 都支持这些操作符。
4.2.1 检查单个值
SELECT prod_name, prod_price
FROM Products
WHERE prod_price < 10;
4.2.2 不匹配检查
SELECT vend_id, prod_name
FROM Products
WHERE vend_id <> 'DLL01';
<>中间不能由括号(MySQL 5.5.47 测试)
4.2.3 范围值检查
要检查某个范围的值,可以使用BETWEEN操作符。
SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 5 AND 10;
在使用BETWEEN时,必须指定两个值 —— 所需范围的低端值和高端值。
4.2.4 空值检查
SELECT prod_name
FROM Products
WHERE prod_price IS NULL;
不为空
IS NOT NULL (MySQL 5.5.47 测试)
许多 DBMS 扩展了标准的操作符集,提供了更高级的过滤选择。
第 5 课 高级数据过滤
5.1 组合 WHERE 子句
操作符( operator )
用来联结或改变WHERE子句中的子句的关键字,也称为 逻辑操作符( logical operator )。
5.1.1 AND 操作符
要通过不止一个列进行过滤,可以使用AND操作符给WHERE子句附加条件。
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'DLL01' AND prod_price <= 4;
AND
用在WHERE子句中的关键字,用来指示检索满足所有给定条件的行。
可以增加多个过滤条件,每个条件间都要使用AND关键字。
5.1.2 OR 操作符
OR操作符与AND操作符正好相反,它指示 DBMS 检索匹配任一条件的行。
SELECT prod_name, prod_price
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = ‘BRS01’;
OR
WHERE子句中使用的关键字,用来表示检索匹配任一给定条件的行。
5.1.3 求值顺序
SELECT prod_name, prod_price
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = ‘BRS01’
AND prod_price >= 10;
返回的行中有 4 行价格小于 10 美元,显然,返回的行未按预期的进行过滤。SQL (像多数语言一样)在处理OR操作符前,优先处理AND操作符。
它理解为:由供应商BRS01制造的价格为 10 美元以上的所有产品,以及由供应商DLL01制造的所有产品,而不管其价格如何。
由于AND在求值过程中优先级更高,操作符被错误 地组合了。
此问题的解决方法是使用圆括号对操作符进行明确分组。
5.2 IN 操作符
IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN取一组由逗号分隔、括在圆括号中的合法值。
SELECT prod_name, prod_price
FROM Products
WHERE vend_id IN ( 'DLL01', 'BRS01' )
ORDER BY prod_name;
IN
WHERE子句中用来指定要匹配值的清单的关键字,功能与OR相当。
5.3 NOT 操作符
NOT
WHERE子句中用来否定其后条件的关键字。
SELECT prod_name
FROM Products
WHERE NOT vend_id = 'DLL01'
ORDER BY prod_name;
MariaDB 支持使用NOT否定IN、BETWEEN和EXISTS子句。大多数 DBMS 允许使用NOT否定任何条件。
第 6 课 用通配符进行过滤
6.1 LIKE 操作符
通配符( wildcard )
用来匹配值的一部分的特殊字符。
搜索模式( search pattern )
由字面值、通配符或两者组合构成的搜索条件。
谓词( predicate )
操作符何时不是操作符?答案是,它作为谓词时。从技术上说,LIKE是谓词而不是操作符。虽然最终的结果是相同的,但应该对此术语有所
了解,以免在 SQL 文献或手册中遇到此术语时不知所云。
通配符搜索只能用于文本字段(串),非文本数据类型字段不能使用通配符搜索。
6.1.1 百分号( % )通配符
在搜索串中,%表示任何字符出现任意次数。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE 'Fish%';
在执行这条子句时,将检索任意以Fish起头的词。
Access 通配符
如果使用的是 Microsoft Access ,需要使用*而不是%。
区分大小写
根据 DBMS 的不同及其配置,搜索可以是区分大小写的。如果区分大小写,则'fish%'与Fish bean bag toy就不匹配。
搜索模式'%bean bag%'表示匹配任何位置上包含文本bean bag的值,不论它之前或之后出现什么字符。
根据部分信息搜索电子邮件地址
有一种情况下把通配符放在搜索模式中间是很有用的,就是根据邮件地址的一部分来查找电子邮件,例如WHERE email LIKE b%@forta.com。
需要特别注意,除了能匹配一个或多个字符外,%还能匹配 0 个字符。%代表搜索模式中给定位置的 0 个、 1 个或多个字符。
警告: 请注意后面所跟的空格
包括 Access 在内的许多 DBMS 都用空格来填补字段的内容。
警告:请注意 NULL
通配符%看起来像是可以匹配任何东西,但有个例外,这就是NULL。子句WHERE prod_name LIKE '%'不会匹配产品名称为NULL的行。
6.1.2 下划线( _ )通配符
下划线的用途与%一样,但它只匹配单个字符,而不是多个字符。
说明: DB2 通配符
DB2 不支持通配符_。
说明: Access 通配符
如果使用的是 Microsoft Access ,需要使用?而不是_。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '__ inch teddy bear';
请注意后面所跟的空格
与上例一样,可能需要给这个模式添加一个通配符。
6.1.3 方括号( [ ] )通配符
方括号([])通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符。
说明:并不总是支持集合
与前面描述的通配符不一样,并不是所有 DBMS 都支持用来创建集合的[]。只有微软的 Access 和 SQL Server 支持集合。
(MySQL 5.5.47 测试不支持,在MySQL中,“[]”属于正则模式,用REGEXP)
找出所有名字以J或M起头的联系人
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[JM]%'
ORDER BY cust_contact;
6.2 使用通配符的技巧
正如所见, SQL 的通配符很有用。但这种功能是有代价的,即通配符搜索一般比前面讨论的其他搜索要耗费更长的处理时间。
第 7 课 创建计算字段
7.1 计算字段
存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是检索 出数据,然后再在客户端应用程序中重新格式化。
字段( field )
基本上与 列( column )的意思相同,经常互换使用,不过数据库列一般称为列,而术语 字段通常与计算字段一起使用。
提示:客户端与服务器的格式
在 SQL 语句内可完成的许多转换和格式化工作都可以直接在客户端应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户 端中完成要快得多。
7.2 拼接字段
拼接( concatenate )
将值联结到一起(将一个值附加到另一个值)构成单个值。
把两个列拼接起来。在 SQL 中的SELECT语句中,可使用一个特殊的操作符来拼接两个列。根据你所使用的 DBMS ,此操作符可用加 号(+)或两个竖杠(||)表示。在 MySQL 和 MariaDB 中,必须使用特殊的函数。
说明:是+ + 还是 ||? ?
Access 和 SQL Server 使用+号。 DB2 、 Oracle 、 PostgreSQL 、 SQLite 和 Open Office Base 使用||。详细请参阅具体的 DBMS 文档。
SELECT vend_name + ' (' + vend_country + ')'
FROM Vendors
ORDER BY vend_name;
下面是相同的语句,但使用的是||语法:
SELECT vend_name || ' (' || vend_country || ')'
FROM Vendors
ORDER BY vend_name;
为正确返回格式化的数据,必须去掉这些空格。这可以使用 SQL 的RTRIM()函数来完成
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
FROM Vendors
ORDER BY vend_name;
(MySQL 5.5.47 测试不支持以上两种)
说明: TRIM 函数
大多数 DBMS 都支持RTRIM()(正如刚才所见,它去掉字符串右边的空格)、LTRIM()(去掉字符串左边的空格)以及TRIM()(去掉字符串左 右两边的空格)。
SELECT语句可以很好地拼接地址字段,实际上它没有名字,它只是一个值,一个未命名的列不能用于客户端应用中,因为客户端没有办法引用它。
为了解决这个问题, SQL 支持列别名。别名( alias )是一个字段或值的替换名。别名用AS关键字赋予。
ELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
AS vend_title
FROM Vendors
ORDER BY vend_name;
下面是 MySQL 和 MariaDB 中使用的语句:
SELECT Concat(vend_name, ' (', vend_country, ')')
AS vend_title
FROM Vendors
ORDER BY vend_name;
说明:导出列
别名有时也称为导出列( derived column )
(
SQL中 单引号 双引号区别
参考: https://segmentfault.com/q/1010000000236690
标准SQL只有单引号
单引号转义 两个单引号 表示字符串里的一个单引号
MySQL支持使用双引号
)
7.3 执行算术计算
SELECT prod_id,
quantity,
item_price,
quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
SQL 算术操作符
操作符 说明
+ 加
- 减
* 乘
/ 除
如何测试计算
SELECT语句为测试、检验函数和计算提供了很好的方法。虽然SELECT通常用于从表中检索数据,但是省略了FROM子句后就是简单地访问和处 理表达式,例如SELECT 3 * 2;将返回 6 ,SELECT Trim(' abc ');将返回abc,SELECT Now();使用Now()函数返回当前日期和时间。现在你明 白了,可以根据需要使用SELECT语句进行检验。
第 8 课 使用数据处理函数
8.1 函数
函数带来的问题
与几乎所有 DBMS 都等同地支持 SQL 语句(如SELECT)不同,每一个 DBMS 都有特定的函数。事实上,只有少数几个函数被所有主要的 DBMS 等同地支持。虽然所有类型的函数一般都可以在每个 DBMS 中使用,但各个函数的名称和语法可能极其不同。
DBMS 函数的差异
提取字符串的组成部分
Access 使用 MID() ; DB2 、 Oracle 、 PostgreSQL 和 SQLite 使用 SUBSTR() ; MySQL 和 SQL Server 使用 SUBSTRING()
数据类型转换
Access 和 Oracle 使用多个函数,每种类型的转换有一个函数; DB2 和 PostgreSQL 使用 CAST() ; MariaDB 、 MySQL 和 SQL Server 使用 CONVERT()
取当前日期
Access 使用 NOW() ; DB2 和 PostgreSQL 使用 CURRENT_DATE ; MariaDB 和 MySQL 使用 CURDATE() ; Oracle 使用 SYSDATE ; SQL Server 使用 GETDATE() ; SQLite 使用 DATE()
可以看到,与 SQL 语句不一样, SQL 函数不是可移植的。
可移植( portable )
所编写的代码可以在多个系统上运行。
8.2 使用函数
大多数 SQL 实现支持以下类型的函数。
用于处理文本字符串(如删除或填充值,转换值为大写或小写)的文本函数。
用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
用于处理日期和时间值并从这些值中提取特定成分(如返回两个日期之差,检查日期有效性)的日期和时间函数。
返回 DBMS 正使用的特殊信息(如返回用户登录信息)的系统函数。
8.2.1 文本处理函数
SELECT vend_name, UPPER(vend_name) AS vend_name_upcase
FROM Vendors
ORDER BY vend_name;
UPPER()将文本转换为大写
常用的文本处理函数
函 数 说 明
LEFT() (或使用子字符串函数) 返回字符串左边的字符
LENGTH() (也使用 DATALENGTH() 或 LEN() ) 返回字符串的长度
LOWER() ( Access 使用 LCASE() ) 将字符串转换为小写
LTRIM() 去掉字符串左边的空格
RIGHT() (或使用子字符串函数) 返回字符串右边的字符
RTRIM() 去掉字符串右边的空格
SOUNDEX() 返回字符串的 SOUNDEX 值
UPPER() ( Access 使用 UCASE() ) 将字符串转换为大写
SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。SOUNDEX考虑了类似的发 音字符和音节,使得能对字符串进行发音比较而不是字母比较。虽然SOUNDEX不是 SQL 概念,但多数 DBMS 都提供对SOUNDEX的支持。
SOUNDEX 支持
Microsoft Access 和 PostgreSQL 不支持SOUNDEX(),因此以下的例子不适用于这些 DBMS 。
(MySQL 5.5.47 测试支持SOUNDEX)
SELECT cust_name, cust_contact
FROM Customers
WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green');
在这个例子中,WHERE子句使用SOUNDEX()函数把cust_contact列值和搜索字符串转换为它们的SOUNDEX值。因为Michael Green和Michelle Green发音相似,所以它们的SOUNDEX值匹配,因此WHERE子句正确地过滤出了所需的数据。
8.2.2 日期和时间处理函数
日期和时间采用相应的数据类型存储在表中,每种 DBMS 都有自己的特殊形式。
SQL Server
WHERE DATEPART(yy, order_date) = 2012
Access
WHERE DATEPART('yyyy', order_date) = 2012
PostgreSQL
WHERE DATE_PART('year', order_date) = 2012
Oracle
WHERE to_number(to_char(order_date, 'YYYY')) = 2012
MySQL 和 MariaDB
WHERE YEAR(order_date) = 2012
SQLite
WHERE strftime('%Y', order_date) = 2012
8.2.3 数值处理函数
数值处理函数仅处理数值数据。在主要 DBMS 的函数中,数值函数是最一致、最统一的函数。
常用数值处理函数
函 数 说 明
ABS() 返回一个数的绝对值
COS() 返回一个角度的余弦
EXP() 返回一个数的指数值
PI() 返回圆周率
SIN() 返回一个角度的正弦
SQRT() 返回一个数的平方根
TAN() 返回一个角度的正切
第 9 课 汇总数据
9.1 聚集函数
使用这些函数, SQL 查询可用于检索数据,以便分析和报表 生成。
比如:
确定表中行数(或者满足某个条件或包含某个特定值的行数);
获得表中某些行的和;
找出表列(或所有行或某些特定的行)的最大值、最小值、平均值。
为方便这种类型的检索, SQL 给出了 5 个聚集函数, SQL 的聚集函数 在各种主要 SQL 实现中得到了相当一致的支持。
聚集函数( aggregate function )
对某些行运行的函数,计算并返回一个值。
函 数 说 明
AVG() 返回某列的平均值
COUNT() 返回某列的行数
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值之和
9.1.1 AVG() 函数
AVG()通过对表中行数计数并计算其列值之和,求得该列的平均值。
SELECT AVG(prod_price) AS avg_price
FROM Products;
警告:只用于单个列
AVG() 只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。
说明: NULL 值
AVG()函数忽略列值为NULL的行。
(
测试 SELECT AVG(prod_price) AS avg_price, prod_price
只返回一行,prod_price只返回第一个结果的值
)
9.1.2 COUNT() 函数
COUNT()函数有两种使用方式:
- 使用COUNT(*)对表中行的数目进行计数,不管表列中包含的是空值(NULL)还是非空值。
- 使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值。
SELECT COUNT(*) AS num_cust
FROM Customers;
下面的例子只对具有电子邮件地址的客户计数:
SELECT COUNT(cust_email) AS num_cust
FROM Customers;
(不计算cust_email列值为NULL的行)
说明: NULL 值
如果指定列名,则COUNT()函数会忽略指定列的值为空的行,但如果COUNT()函数中用的是星号(*),则不忽略。
9.1.3 MAX() 函数
MAX()返回指定列中的最大值。MAX()要求指定列名。
SELECT MAX(prod_price) AS max_price
FROM Products;
提示:对非数值数据使用 MAX()
虽然MAX()一般用来找出最大的数值或日期值,但许多(并非所有) DBMS 允许将它用来返回任意列中的最大值,包括返回文本列中的最大 值。在用于文本数据时,MAX()返回按该列排序后的最后一行。
说明: NULL 值
MAX()函数忽略列值为NULL的行。
9.1.4 MIN() 函数
MIN()的功能正好与MAX()功能相反,它返回指定列的最小值。与MAX()一样,MIN()要求指定列名。
SELECT MIN(prod_price) AS min_price
FROM Products;
提示:对非数值数据使用 MIN()
虽然MIN()一般用来找出最小的数值或日期值,但许多(并非所有) DBMS 允许将它用来返回任意列中的最小值,包括返回文本列中的最小
值。在用于文本数据时,MIN()返回该列排序后最前面的行。
(同样忽略NULL值所在行)
9.1.5 SUM() 函数
SUM()用来返回指定列值的和(总计)。
SELECT SUM(quantity) AS items_ordered
FROM OrderItems
WHERE order_num = 20005;
SUM()也可以用来合计计算值。
SELECT SUM(item_price*quantity) AS total_price
FROM OrderItems
WHERE order_num = 20005;
提示:在多个列上进行计算
如本例所示,利用标准的算术操作符,所有聚集函数都可用来执行多个列上的计算。
说明: NULL 值
SUM()函数忽略列值为NULL的行。
9.2 聚集不同值
以上 5 个聚集函数都可以如下使用:
- 对所有行执行计算,指定ALL参数或不指定参数(因为ALL是默认行为)。
- 只包含不同的值,指定DISTINCT参数。
提示: ALL 为默认
ALL参数不需要指定,因为它是默认行为。如果不指定DISTINCT,则假定为ALL。
说明:不要在 Access 中使用
Microsoft Access 在聚集函数中不支持DISTINCT,因此下面的例子不适合于 Access 。要在 Access 得到类似的结果,需要使用子查询 把DISTINCT数据返回到外部SELECT COUNT(*)语句。
下面的例子使用AVG()函数返回特定供应商提供的产品的平均价格。它与上面的SELECT语句相同,但使用了DISTINCT参数,因此平均值只考虑各 个不同的价格:
SELECT AVG(DISTINCT prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';
警告: DISTINCT 不能用于 COUNT(*)
将 DISTINCT 用于 MIN() 和 MAX(),没有价值,结果都是相同 的。
说明:其他聚集参数
除了这里介绍的DISTINCT和ALL参数,有的 DBMS 还支持其他参数,如支持对查询结果的子集进行计算的TOP和TOP PERCENT。为了解具体的 DBMS 支持哪些参数,请参阅相应的文档。
( MySQL 没有TOP、TOP PERCENT 函数)
9.3 组合聚集函数
但实际上,SELECT语句可根据需要包含多个聚集函数。
SELECT COUNT(*) AS num_items,
MIN(prod_price) AS price_min,
MAX(prod_price) AS price_max,
AVG(prod_price) AS price_avg
FROM Products;
这里用单条SELECT语句执行了 4 个聚集计算,返回 4 个值(Products表中物品的数目,产品价格的最高值、最低值以及平均值)。
9.4 小结
聚集函数用来汇总数据。 SQL 支持 5 个聚集函数,可以用多种方法使用它们,返回所需的结果。这些函数很高效,它们返回结果一般比你在自己 的客户端应用程序中计算要快得多。
第 10 课 分组数据
10.1 数据分组
使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。
10.2 创建分组
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;
上面的SELECT语句指定了两个列:vend_id包含产品供应商的 ID ,num_prods为计算字段(用COUNT(*)函数建立)。GROUP BY子句指示 DBMS 按vend_id排序并分组数据。这就会对每个vend_id而不是整个表计算num_prods一次。从输出中可以看到,供应商BRS01有3个产品,供应 商DLL01有4个产品,而供应商FNG01有2个产品。
GROUP BY子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致地进行数据分组。
如果在GROUP BY子句中嵌套了分组,数据将在最后指定的分组上进行汇总。
如果分组列中包含具有NULL值的行,则NULL将作为一个分组返回。
GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。
10.3 过滤分组
除了能用GROUP BY分组数据外, SQL 还允许过滤分组,规定包括哪些分组,排除哪些分组。
提示: HAVING 支持所有 WHERE 操作符
SELECT cust_id, COUNT(*) AS orders
FROM Orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;
最后一行增加了HAVING子句,它过滤COUNT(*) >= 2(两个以上订单)的那些分组。
10.4 分组和排序
ORDER BY GROUP BY
对产生的输出排序 对行分组,但输出可能不是分组的顺序
任意列都可以使用 只可能使用选择列或表达式列,而且必须使用每个选择列表达式
要按订购物品的数目排序输出,需要添加ORDER BY子句,如下所示:
SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3
ORDER BY items, order_num;
10.5 SELECT 子句顺序
子句 说明 是否必须使用
SELECT 要返回的列或表达式 是
FROM 从中检索数据的表 仅在从表选择数据时使用
WHERE 行级过滤 否
GROUP BY 分组说明 仅在按组计算聚集时使用
HAVING 组级过滤 否
ORDER BY 输出排序顺序 否
第 11 课 使用子查询
11.1 子查询
SELECT语句是 SQL 的查询。
SQL 还允许创建子查询( subquery ),即嵌套在其他查询中的查询。
11.2 利用子查询进行过滤
SELECT cust_name, cust_contact
FROM Customers
WHERE cust_id IN (SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01'));
在WHERE子句中使用子查询能够编写出功能很强且很灵活的 SQL 语句。对于能嵌套的子查询的数目没有限制,不过在实际使用时由于性能 的限制,不能嵌套太多的子查询。
11.3 作为计算字段使用子查询
要对每个顾客执行COUNT(*)
SELECT cust_name,
cust_state,
(SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;
子查询中的WHERE子句与前面使用的WHERE子句稍有不同,因为它使用了完全限定列名,而不只是列名(cust_id)。用一个句点分隔表名和列名,这种语法必须在有可能混淆列名时使用。
第 12 课 联结表
12.1 联结
SQL 最强大的功能之一就是能在数据查询的执行中联结( join )表。联结是利用 SQL 的SELECT能执行的最重要的操作。
12.1.1 关系表
关系表的设计就是要把信息分解成多个表,一类数据一个表。各 表通过某些共同的值互相关联(所以才叫关系数据库)。
关系数据库的可伸缩性远比非关系数据库要好。
12.1.2 为什么使用联结
联结是一种机制,用来在一条SELECT语句中关联表,因此称为联结。使用特殊的语法,可以联结多个表返回一组输 出,联结在运行时关联表中正确的行。
12.2 创建联结
创建联结非常简单,指定要联结的所有表以及关联它们的方式即可。
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;
12.2.1 WHERE 子句的重要性
笛卡儿积( cartesian product )
由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products;
从上面的输出可以看到,相应的笛卡儿积不是我们想要的。这里返回的数据用每个供应商匹配了每个产品,包括了供应商不正确的产品(即使 供应商根本就没有产品)。
不要忘了 WHERE 子句
12.2.2 内联结
目前为止使用的联结称为等值联结( equijoin ),它基于两个表之间的相等测试。这种联结也称为内联结( inner join )。
可以对这种联结 使用稍微不同的语法,明确指定联结的类型。
SELECT vend_name, prod_name, prod_price
FROM Vendors INNER JOIN Products
ON Vendors.vend_id = Products.vend_id;
这里,两个表之间的关系是以INNER JOIN指定的部分FROM子句。在使用这种语 法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。
12.2.3 联结多个表
SQL 不限制一条SELECT语句中可以联结的表的数目。
SELECT prod_name, vend_name, prod_price, quantity
FROM OrderItems, Products, Vendors
WHERE Products.vend_id = Vendors.vend_id
AND OrderItems.prod_id = Products.prod_id
AND order_num = 20007;
联结的表越多,性能 下降越厉害。
如第 11 课所述,子查询并不总是执行复杂SELECT操作的最有效方法,下面是使用联结的相同查询:
SELECT cust_name, cust_contact
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num
AND prod_id = 'RGAN01';
第 13 课 创建高级联结
13.1 使用表别名
SQL 除了可以对列名和计算字段使用别名,还允许给表名起别名。
这样做有两个主要理由:
缩短 SQL 语句;
允许在一条SELECT语句中多次使用相同的表
警告: Oracle 中没有AS,简单地指定列名即可
13.2 使用不同类型的联结
迄今为止,我们使用的只是内联结或 等值联结的简单联结。现在来看三种其他联结:自联结( self-join )、自然联结( natural join )和外联结 ( outer join )。
13.2.1 自联结
SELECT c1.cust_id, c1.cust_name, c1.cust_contact
FROM Customers AS c1, Customers AS c2
WHERE c1.cust_name = c2.cust_name
AND c2.cust_contact = 'Jim Jones';
提示:用自联结而不用子查询
自联结通常作为外部语句,用来替代从相同表中检索数据的使用子查询语句。虽然最终的结果是相同的,但许多 DBMS 处理联结远比处理子 查询快得多。应该试一下两种方法,以确定哪一种的性能更好。
(使用自连接可以将自身表的一个镜像当作另一个表来对待)
13.2.2 自然联结
(自然连接(Natural join)是一种特殊的等值连接,它要求两个关系中进行比较的分量必须是相同的属性组,并且在结果中把重复的属性列去掉。而等值连接并不去掉重复的属性列。)
SELECT C.*, O.order_num, O.order_date,
OI.prod_id, OI.quantity, OI.item_price
FROM Customers AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
AND OI.order_num = O.order_num
AND prod_id = 'RGAN01';
事实上,我们迄今为止建立的每个内联结都是自然联结,很可能永远都不会用到不是自然联结的内联结。
13.2.3 外联结
联结包含了那些在相关表中没有关联行的行。这种联结称为外联结。
检索所有顾客及其订单:
SELECT Customers.cust_id, Orders.order_num
FROM Customers INNER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
要检索包括没有订单顾客在内的所有顾客,可如下进行:
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表.
使用RIGHT OUTER JOIN的例子:
SELECT Customers.cust_id, Orders.order_num
FROM Customers RIGHT OUTER JOIN Orders
ON Orders.cust_id = Customers.cust_id;
警告: SQLite 外联结
SQLite 支持LEFT OUTER JOIN,但不支持RIGHT OUTER JOIN,但有一种更简单的办法。
调整FROM或WHERE子句中表的顺序,左外联结可以转换为右外联结。
还存在另一种外联结,就是全外联结( full outer join ),它检索两个表中的所有行并关联那些可以关联的行。
SELECT Customers.cust_id, Orders.order_num
FROM Orders FULL OUTER JOIN Customers
ON Orders.cust_id = Customers.cust_id;
警告: FULL OUTER JOIN 的支持
Access 、 MariaDB 、 MySQL 、 Open Office Base 或 SQLite 不支持FULL OUTER JOIN语法。
13.3 使用带聚集函数的联结
聚集函数用来汇总数据。
SELECT Customers.cust_id,
COUNT(Orders.order_num) AS num_ord
FROM Customers INNER JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
这条SELECT语句使用INNER JOIN将Customers和Orders表互相关联。GROUP BY子句按顾客分组数据,因此,函数调 用COUNT(Orders.order_num)对每个顾客的订单计数,将它作为num_ord返回。
13.4 使用联结和联结条件
应该总是提供联结条件,否则会得出笛卡儿积。
(感觉其他是废话)
第 14 课 组合查询
14.1 组合查询
SQL 也允许执行多个查询(多条SELECT语句),并将结果作为一个 查询结果集返回。这些组合查询通常称为 并( union )或 复合查询( compound query )。
主要有两种情况需要使用组合查询:
在一个查询中从不同的表返回结构数据;
对一个表执行多个查询,按一个查询返回数据。
14.2 创建组合查询
14.2.1 使用 UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
警告:性能问题
14.2.2 UNION 规则
UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔
UNION中的每个查询必须包含相同的列、表达式或聚集函数
列数据类型必须兼容:类型不必完全相同,但必须是 DBMS 可以隐含转换的类型(例如,不同的数值类型或不同的日期类型)
14.2.3 包含或取消重复的行
UNION从查询结果集中自动去除了重复的行
如果想返回所有的匹配行,可使用UNION ALL而不是UNION。
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION ALL
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
14.2.4 对组合查询结果排序
SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须位于最后一条SELECT语句之后。
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All'
ORDER BY cust_name, cust_contact;
(在MySQL中实验,ORDER BY的项必须在两条SELECT中都存在且只有一列)
说明:其他类型的 UNION
某些 DBMS 还支持另外两种UNION:EXCEPT(有时称为MINUS)可用来检索只在第一个表中存在而在第二个表中不存在的行;而INTERSECT可
用来检索两个表中都存在的行。实际上,这些UNION很少使用,因为相同的结果可利用联结得到。
提示:操作多个表
为了简单,本课中的例子都是使用UNION来组合针对同一表的多个查询。实际上,UNION在需要组合多个表的数据时也很有用,即使是有不匹 配列名的表,在这种情况下,可以将 UNION 与别名组合,检索一个结果集。
14.3 小结
第 15 课 插入数据
15.1 数据插入
INSERT用来将行插入(或添加)到数据库表。插入有几种方式:
- 插入完整的行;
- 插入行的一部分;
- 插入某些查询的结果。
使用INSERT前,需要保证自己有足够的安全权限。
15.1.1 插入完整的行
基本用法
INSERT INTO Customers VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY',
'11111',
'USA',
NULL,
NULL);
这个例子将一个新顾客插入到Customers表中。
存储到表中每一列的数据在VALUES子句中给出,必须给每一列提供一个值。
如果某列没 有值,则应该使用NULL值(假定表允许对该列指定空值)。
各列必须以它们在表定义中出现的次序填 充。
提示:INTO关键字
在某些SQL实现中,跟在INSERT之后的INTO关键字是可选的。为了保证可移植性,最好提供。
编写依赖于特定列次序的SQL语句是很不安全的,不能保证各列在下一次表结构变动后保持完全相同的次序。
INSERT INTO Customers(cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email)
VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY', '11111',
'USA',
NULL,
NULL);
在表名后的括号里明确给出了列名。
提示:总是使用列的列表
不要使用没有明确给出列的INSERT语句。
15.1.2 插入部分行
INSERT INTO Customers(cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country)
VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY',
'11111',
'USA');
警告:省略列
如果表的定义允许,则可以在INSERT操作中省略某些列。
省略的列必须满足以下某个条件。
- 该列定义为允许NULL值(无值或空值)。
- 在表定义中给出默认值。这表示如果不给出值,将使用默认值。
15.1.3 插入检索出的数据
INSERT还存在另一种形式,可以利用它将SELECT语句的结果插入表中,这就是所谓的INSERT SELECT。
INSERT INTO Customers(cust_id,
cust_contact,
cust_email,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country)
SELECT cust_id,
cust_contact,
cust_email,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country
FROM CustNew;
这个例子从一个名为CustNew的表中读出数据并插入到Customers表。
DBMS一点儿也不关心SELECT返 回的列名。它使用的是列的位置。
提示:插入多行
INSERT SELECT是个例外,它可以用一条INSERT插入多行,不管SELECT语 句返回多少行,都将被INSERT插入。
15.2 从一个表复制到另一个表
有一种数据插入不使用INSERT语句。要将一个表的内容复制到一个全新的表(运行中创建的表),可以使用SELECT INTO语句。
说明:DB2不支持
DB2不支持这里描述的SELECT INTO。
说明:INSERT SELECT与SELECT INTO
它们之间的一个重要差别是前者导出数据,而后者导入数据。
SELECT *
INTO CustCopy
FROM Customers;
这条SELECT语句创建一个名为CustCopy的新表,并把Customers表的整个内容复制到新表中 。要想只复制部分的列,可以明确给出列名,而不是使用*通配符。
MariaDB、MySQL、Oracle、PostgreSQL和SQLite使用的语法稍有不同:
CREATE TABLE CustCopy AS
SELECT * FROM Customers;
在使用SELECT INTO时,需要知道一些事情:
- 任何SELECT选项和子句都可以使用,包括WHERE和GROUP BY;
- 可利用联结从多个表插入数据;
- 不管从多少个表中检索数据,数据都只能插入到一个表中。
第16课 更新和删除数据
16.1 更新数据
有两种使用UPDATE的方式:
- 更新表中的特定行;
- 更新表中的所有行。
UPDATE Customers
SET cust_email = 'kim@thetoystore.com'
WHERE cust_id = '1000000005';
在这个例子中,要更新的表名为Customers。SET命令用来将新值赋给被更新的列。
UPDATE Customers
SET cust_contact = 'Sam Roberts',
cust_email = 'sam@toyland.com'
WHERE cust_id = '1000000006';
在更新多个列时,只需要使用一条SET命令,每个“列=值”对之间用逗号分隔(最后一列之后不用逗号)。
16.2 删除数据
有两种使用DELETE的方式:
- 从表中删除特定的行;
- 从表中删除所有行。
警告:不要省略WHERE子句
在使用DELETE时一定要细心。
DELETE FROM Customers
WHERE cust_id = '1000000006';
提示:友好的外键
存在外键时,DBMS使用它们实施引用完整性。
使用外键确保引用完整性的一个好处是,DBMS通常可以防止删除某个关系需要用到的行。
提示:FROM关键字
在某些SQL实现中,跟在DELETE后的关键字FROM是可选的
但是即使不需要,也最好提供这个关键字
这样做将保证SQL代码在DBMS之间 可移植。
DELETE不需要列名或通配符。DELETE删除整行而不是删除列。
要删除指定的列,请使用UPDATE语句。
提示:更快的删除
如果想从表中删除所有行,不要使用DELETE。
可使用TRUNCATE TABLE语句,它完成相同的工作,而速度更快(因为不记录数据的变动)。
16.3 更新和删除的指导原则
下面是许多SQL程序员使用UPDATE或DELETE时所遵循的重要原则。
- 除非确实打算更新和删除每一行,否则绝对不要使用不带WHERE子句的UPDATE或DELETE语句。
- 保证每个表都有主键(如果忘记这个内容,请参阅第12课),尽可能像WHERE子句那样使用它(可以指定各主键、多个值或值的范围)。
- 在UPDATE或DELETE语句使用WHERE子句前,应该先用SELECT进行测试,保证它过滤的是正确的记录,以防编写的WHERE子句不正确。
- 使用强制实施引用完整性的数据库(关于这个内容,请参阅第12课),这样DBMS将不允许删除其数据与其他表相关联的行。
- 有的DBMS允许数据库管理员施加约束,防止执行不带WHERE子句的UPDATE或DELETE语句。如果所采用的DBMS支持这个特性,应该使用它。
第17课 创建和操纵表
17.1 创建表