本来是看第五版的,结果拿错书了,等发现时候已经看了好多了就没再换书
数据库基本概念
数据库:保存有组织的数据的容器(通常是一个文件或一组文件)。
我们常说的数据库软件是“数据库管理系统(DBMS)”,而非数据库,数据库是通过 DBMS 创建和操纵的容器,而具体它究竟是什么,形式如何,各种数据库都不一样。
表:某种特定类型数据的结构化清单。
模式:关于数据库和表的布局及特性的信息。包括表具有一些特性,这些特性定义了数据在表中如何存储,包含存储什么 样的数据,数据如何分解,各部分信息如何命名等信息。
列(column):表中的一个字段。所有表都是由一个或多个列组成的。
数据类型:所允许的数据的类型。每个表列都有相应的数据类型,它限制(或允许)该列中存储的数据。
行(row):表中的一个记录。
主键(primary key):一列(或一组列),其值能够唯一标识表中每一行。
提示:应当把主键作为每行必须项
主键条件:
- 任意两行都不具有相同的主键值
每一行都必须具有一个主键值(主键列不允许有 NULL 值)
主键列中的值不允许修改或更新
主键值不能重用(如果某行从表中删除,它的主键不能赋给以后的新行)
SQL:Structured Query Lanuage (结构化查询语言),是一种专门用来与数据库沟通的语言。
关键字:作为 SQL 组成部分的保留字。关键字不能用作表或列的名字。
SQL 语句特点:
- 多条 SQL 语句必须以分号
;
分隔。多数 DBMS 不需要在单条 SQL 语句后加分号,但也有 DBMS 可能必须在单条 SQL 语句后加上分号。 - SQL 语句不区分大小写,但多数人对 SQL 关键字大写,更易于阅读和调试。
- 在处理 SQL 语句时,其中所有空格都被忽略。SQL 语句可以写成长长的一行,也可以分写在多行。多数 SQL 开发人员认为,将 SQL 语句分成多行更容易阅读和调试。
操作符:用来联结或改变WHERE
字句中的字句的关键字,也称为逻辑操作符
聚集函数(aggregate function):对某些行运行的函数,计算并返回一个值。
检索数据
检索数据使用SELECT
语句
检索单列
1 | SELECT name FROM students; |
检索多列
传入多个列名即可,列名之间用,
分隔,但最后一个列名后面不可以加。
1 | SELECT name,class_id,id FROM students; |
检索所有列
使用通配符*
1 | SELECT * FROM students; |
检索不同的值
使用DISTINCT
关键字
用于指示数据库只返回不同的值,即筛选掉重复行,将会应用于所有列
1 | SELECT DISTINCT class_id |
限制前 n 行结果
TOP
关键字限制最多返回多少行
1 | SELECT TOP 5 name |
对于 MySQL、MariaDB、PostgreSQL 或者 SQLite,需使用
LIMIT
1
2
3 SELECT name
FROM students
LIMIT 5;
对于 DB2 DBMS 的特定语句
1
2
3 SELECT name
FROM students
FETCH FIRST 5 ROWS ONLY;
对于 Oracle DBMS
1
2
3 SELECT name
FROM students
WHERE ROWNUM <=5;
限制第 m 行开始输出 n 行结果
使用OFFSET
关键字调整检索位移
1 | SELECT name |
输出从第 3 行开始的 5 行数据
行数从第
0
行开始计算
简化版
MySQK 和 MariaDB 支持简化版语句,例如LIMIT 4 OFFSET 3
,即为LIMIT 3,4
使用注释
行尾注释
在指令后面加--
即可
多行指令也可以
1 | SELECT name -- 注释1 |
行内注释
1 | # 这是一个注释 |
多行注释
1 | /* 这是一个 |
过滤数据
使用WHERE
语句过滤数据
基本用法
1 | SELECT name, id, class_id |
注意:WHERE 子句的位置
在同时使用ORDER BY
和WHERE
子句时,应当让ORDER BY
位于WHERE
之后,否则将会产生错误
操作符
WHERE
字句支持的操作符:
操作符 | 说明 |
---|---|
= | 等于 |
<> | 不等于 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
!< | 不小于 |
> | 大于 |
>= | 大于等于 |
!> | 不大于 |
BETWEEN AND | 在两个值之间 |
IS NULL | 为 NULL |
eg
小于等于
1 | SELECT name, id, class_id |
等于
1 | SELECT name, id, class_id |
当比较字符串时应当使用引号
范围
1 | SELECT name, id, class_id |
空值检查
1 | SELECT name, id, class_id |
NULL
无值,它与字段包含 0、空字符串或仅仅包含空格不同
检查值是否为 NULL,不可以用= NULL
判断,只能用IS NULL
语句
高级数据过滤
AND
操作符
用在WHERE
子句中的关键字,用来指示检索满足所有给定条件的行(且)。
1 | SELECT name, id, class_id, score |
OR
操作符
WHERE
子句中使用的关键字,用来表示检索匹配任一给定条件的行。在部分 DBMS 里会出现逻辑短路
1 | SELECT name, id, class_id, score |
操作符优先级
AND
比OR
更先运算,在使用时可以通过圆括号对操作符进行分组,控制运算顺序
IN
操作符
IN
用于WHERE
子句中用来指定要匹配值的清单的关键字,功能与OR
相当。
1 | SELECT name, id, class_id, score |
IN
操作符与OR
类似
下面为等价代码
1 | SELECT name, id, class_id, score |
NOT
操作符
WHERE
子句中用来否定其后条件的关键字
1 | SELECT name, id, class_id, score |
用通配符进行过滤
LIKE
操作符
通配符:用来匹配值的一部分特殊字符
搜索模式:由字面值、通配符或两者组合构成的搜索条件
谓词:从技术上说,LIKE
是谓词而不是操作符
%
通配符
表示任意字符出现任意次数(包括 0 次)(在 MS Access 中,通配符为*
)
筛选李姓的同学
1 | SELECT * |
LIKE
搜索区分大小写
空格问题:
在 MS Access 等 DBMS 中,会在字段内容尾部填补空格,所以像WHERE name LIKE 'F%y'
的语句,匹配以 y 结尾的数据时,无法匹配到尾部补全空格的数据,解决办法是使用'F%y%'
NULL 问题
WHERE name LIKE '%'
不会匹配 NULL
_
通配符
用于匹配单个字符
DB2 不支持
_
通配符
在 MS Access 中应使用
?
1 | SELECT prod_id, prod_name |
[]
通配符
表示匹配方括号内的任意一个字符
1 | SELECT cust_contact |
只有 MS Access 和 SQL Server 支持
[]
否定方法:方括号里面加^
前缀表示否定,例如[^JM]
表示匹配除 J、M 以外的字符
在 MS Access 中,应将
^
替换为!
使用,即[^JM]
换为[!JM]
也可以使用NOT
来否定
创建计算字段
字段:基本上与列的意思相同
拼接字段
拼接:将值联结到一起构成单个值。
可以使用一个特殊的操作符用来拼接两个列
+
加号:MS Access、SQL Server(多数 DBMS 使用此方法)||
两个竖杠:DB2、Oracle、PostgreSQL、SQLite、Open Office Base
样例代码拼接了名称和国家,并在国家两边加了括号
使用+
的样例:
1 | SELECT vend_name + ' (' + vend_country + ')' |
使用||
的样例
1 | SELECT vend_name || ' (' || vend_country || ')' |
在 MySQL 或 MariaDB 中需要使用的语句有所不同
使用
Concat()
函数完成
1
2
3 SELECT Concat(vend_name, ' (', vend_country, ')')
FROM Vendors
ORDER BY vend_name;
在上述代码中,有的 DBMS 会将字段用空格填充使其宽度保持一致,而我们并不需要空格,可以使用 SQL 的RTRIM()
函数完成
以+
形式代码为例:
1 | SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' |
TRIM 函数
RTRIM()
:去掉右边空格LTRIM()
:去掉左边空格TRIM()
:去掉左右两边空格
使用别名
拼接后的字符串需要有一个名字才能正常用于客户端,所以可以给拼接后字符串起一个别名
1 | SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title |
即 在Select
后的表达式后面加上 AS title
建议在使用拼接字符串后最好加一个AS
别名有时也称为导出列
执行算术计算
例:
1 | SELECT prod_id, |
这段代码输出了order_num = 20008
的数据,并且每行数据加一个expanded_price
字段显示quantity * item_price
值
支持的算数操作符:
+
-
*
/
测试计算表达式
虽然SELECT
常用于检索数据,但如果省略FROM
字句,则用与简单的访问和处理表达式
例如:
SELECT 3 * 2
将返回6
SELECT Trim(' abc ')
将返回abc
SELECT Now()
将返回当前日期和时间
使用函数处理数据
文本处理函数
RTRIM()
:去掉字符串右边空格LTRIM()
:去掉字符串左边空格TRIM()
:去掉字符串左右两边空格
UPPER()
:将字符串转换为大写(在 MS Access 中使用UCASE()
)LOWER()
:将字符串转换为小写(在 MS Access 中使用LCASE()
)
LEFT()
:返回字符串左边的字符RIGHT()
:返回字符串右边的字符
LENGTH()
、DATALENGTH()
、LEN()
:返回字符串的长度
SOUNDEX()
:返回字符串的 SOUNDEX 值。
关于 SOUNDEX
SOUNDEX 是一个将任何文本串转换为描述其语音表示的字母数字模式的算法,SOUNDEX 考虑了类似的发音音符和音节,使得能对字符串进行发音比较而不是字母比较。
多数 DBMS 都提供对 SOUNDEX 的支持。MS Access 和 PostgreSQL 不支持SOUNDEX()
。
如果在创建 SQLite 时使用了 SQLITE_SOUNDEX 编译时选项,那么SOUNDEX()
在 SQLite 中就可用,因为 SQLITE_SOUNDEX 不是默认选项,所以多数 SQLite 实现不支持SOUNDEX()
使用 SOUNDEX 可以匹配发音相同的单词,在拼错时很有用
1
2
3 SELECT cust_name, cust_contact
FROM Customers
WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green');(查询到
Michelle Green
的字段)
日期和时间处理函数
不同的 DBMS 都有自己特殊的日期和时间处理函数
举例:Orders
表中包含的订单都有订单日期,检索 2012 年的所有订单
SQL Server 和 Sybase
1 | SELECT order_num |
MS Access
1 | SELECT order_num |
通过上面的两个例子,可以看出DATEPART()
函数接收两个参数,分别是返回的成分标识和从中返回成分的日期
类似的例子:
PostgreSQL
1 | SELECT order_num |
Oracle
1 | SELECT order_num |
这里to_char
函数用于提取日期的成分,to_number
函数用于将提取出来的成分转为数值,以便与 2012 进行比较
也可以使用BETWEEN
操作符:
1 | SELECT order_num |
使用BETWEEN
判断时间区间
此代码在 SQL Server 中不起作用,因为其不支持to_date
函数
MySQL 和 MariaDB
MySQL 和 MariaDB 没有DATEPART()
函数,但可以使用YEAR()
函数提取年份
1 | SELECT order_num |
SQLite
在 SQLite 中有一个小技巧,可以直接用strftime()
时间格式化函数提取年份比较
1 | SELECT order_num |
数值处理函数
函数 | 说明 |
---|---|
ABS() |
返回一个数的绝对值 |
COS() |
返回一个角度的余弦 |
EXP() |
返回一个数的指数值 |
PI() |
返回圆周率 |
SIN() |
返回一个角度的正弦 |
SQRT() |
返回一个数的平方根 |
TAN() |
返回一个角度的正切 |
汇总数据
聚集函数
用于汇总表中的数据,而不需要数据本身。
函数 | 说明 |
---|---|
AVG() |
返回某列的平均值 |
COUNT() |
返回某列的行数 |
MAX() |
返回某列的最大值 |
MIN() |
返回某列的最小值 |
SUM() |
返回某列值之和 |
AVG()
1 | SELECT AVG(prod_price) AS avg_price |
计算 prod_price 的平均值,并以 avg_price 别名返回。
注意
AVG()
只能用于单个列- 会自动忽略值为 NULL 的行
COUNT()
有两种使用方式:
COUNT(*)
对表中行的数目进行技术,不管列中是否包含 NULL1
2SELECT COUNT(*) AS num_cust
FROM Customers;COUNT(column)
对特定列中具有值的行进行计数,并且忽略 NULL1
2SELECT COUNT(cust_email) AS num_cust
FROM Customers;
MAX()
返回指定列中的最大值,需要指定列名。
1 | SELECT MAX(prod_price) AS max_price |
该函数同样也忽略 NULL 值。
MIN()
返回指定列中的最小值,需要指定列名。
1 | SELECT MIN(prod_price) AS min_price |
该函数同样也忽略 NULL 值。
SUM()
返回指定列值的和
1 | SELECT SUM(quantity) AS items_ordered |
也可以在其中使用表达式:
1 | SELECT SUM(item_price*quantity) AS total_price |
该函数同样也忽略 NULL 值。
聚集不同值
所有聚集函数都可以有如下两种用法:
ALL
(默认):对所有行操作(无需指定,默认行为)DISTINCT
:只包含不同的值(需要用DISTINCT
指定)
MS Access中不支持
DISTINCT
,要实现类似结果,需要将DISTINCT
数据返回到外部SELECT COUNT(*)
语句。
下面的例子只计算了不同数据的平均值:
1 | SELECT AVG(DISTINCT prod_price) AS avg_price |
**在
COUNT
中使用DISTINCT
**时必须指定列名(COUNT(column)
)不能用于计算或表达式。
部分 DBMS 还支持其他聚集参数,如支持对查询结果的子集进行计算的
TOP
和TOP PERCENT
。
组合聚集函数
SELECT
语句可根据需要包含多个聚集函数:
1 | SELECT COUNT(*) AS num_items, |
分组数据
创建分组
1 | SELECT vend_id, COUNT(*) AS num_prods |
上例表示计算不同vend_id
对应的num_prods
的总和。
GROUP BY
的一些规定:
GROUP BY
子句可以包含任意数目的列,因而可以对分组进行嵌套, 更细致地进行数据分组。- 如果在
GROUP BY
子句中嵌套了分组,数据将在最后指定的分组上进行汇总 GROUP BY
子句中列出的每一列都必须事检索列或有效的表达式(但不能是聚集函数)。如果在SELECT
中使用表达式,则必须在GROUP BY
子句中指定相同的表达式。不能使用别名。- 大多数 SQL 实现不允许
GROUP BY
列带有长度可变的数据类型(如文本或备注型字段)。 - 除聚集计算语句外,
SELECT
语句中的每一列都必须在GROUP BY
子句中给出。 GROUP BY
子句必须出现在WHERE
子句之后,ORDER BY
子句之前。
ALL
子句MS SQL Server 等 SQL 实现 在
GROUP BY
中支持可选ALL
子句,这个子句可用来返回所有分组,即使没有匹配行的分组也返回 NULL。
过滤分组
使用HAVING
子句过滤分组,它与WHERE
类似,但是WHERE
不能过滤分组。
所有类型的
WHERE
子句都可以用HAVING
来替代,句法也相同。
1 | SELECT cust_id, COUNT(*) AS orders |
上面这段代码筛选所有分组中行数大于 2 的分组。
WHERE
和HAVING
之间的区别
有一种说法,WHERE
是在分组前过滤,HAVING
是在分组后过滤
SELECT 子句顺序
子句 | 说明 | 是否必须 |
---|---|---|
SELECT |
要返回的列或表达式 | 是 |
FROM |
从中检索数据的表 | 仅在从表选择数据时使用 |
WHERE |
行级过滤 | 否 |
GROUP BY |
分组说明 | 仅在按组计算聚集时使用 |
HAVING |
组级过滤 | 否 |
ORDER BY |
输出排序顺序 | 否 |
使用子查询
子查询
查询(query):任何 SQL 语句都是查询,但此属于一般指 SELECT 语句。
子查询(subquery):嵌套在其他查询中的查询。
利用子查询过滤
1 | SELECT cust_id |
这段代码的意思是:
从 OrderItems 找到 prod_id = ‘RGAN01’对应的 order_num,并在 Orders 里面找其对应的 cust_id
子查询限制
- 子查询只能查询单列
- 性能方面,使用子查询并不总是执行这类数据检索的最有效方法
作为计算字段使用子查询
子查询语句可以放在计算字段使用:
1 | SELECT cust_name, |
这段代码意思是从 Customers 表中输出三个字段,并按 cust_name 排序,其中 orders 字段使用子查询从 Orders 表中查询 Orders.cust_id = Customers.cust_id 得到。
这里的 WHERE 子句不同的是:它使用了完全限定别名,而不是别名(cust_id),它指定表名和列名(Orders.cust_id)。用一个句点分隔表名和列名,在有可能混淆列名时必须使用这种语法。
完全限定列名:指定表名和列名(Orders.cust_id),避免出现冲突列名而导致的歧义性,不这样写会导致 DBMS 抛出错误信息。 好的做法是,如果在 SELECT 语句中操作多个表,就应使用完全限定列名来避免歧义。
联结表
联结
SQL 最强大的功能之一就是能在数据查询的执行中联结(join)表。
关系表
关系表将数据分解为多个表能更有效地存储,更方便地处理,并且可伸缩性更好。但是有代价的。
使用联结可以在一条 SELECT 语句中导出关联数据。
使用交互式 DBMS 工具
要先理解联结在数据库中并不是实际存在的,DBMS 会根据需要建立联结,它在查询执行期间一直存在。
有的 DBMS 会提供图形界面来交互式定义表关系,以及管理联结关系的合法性。
创建联结
1 | SELECT vend_name, prod_name, prod_price |
通过 vend_id 字段创建 Vendors 和 Products 两个表的联结关系。
WHERE 子句重要性:用于给定联结条件来联结两个表。所以一定要有 WHERE 语句,并保证其准确性。
笛卡尔积
由没有联结条件的表关系返回的结果为笛卡尔积,即检索出的行的数目将是两表行数乘积。
联结多个表
1 | SELECT prod_name, vend_name, prod_price, quantity |
使用 JOIN 子句创建联结
之前的联结都基于两个表之间的相等测试,称为等值联结(equijoin),也称内联结(inner join)
内联结也可以使用INNER JOIN``ON
创建:
1 | SELECT vend_name, prod_name, prod_price |
联结注意
性能考虑:DBMS 在运行时关联指定的每个表,以处理联结。这种处理可能非常耗费资源,因此应该注意,不要联结不必要的表。联结的表越多,性能下降越厉害。
许多 DBMS 都有联结中表最大数目限制。
联结替代子查询
子查询并不总是执行复杂 SELECT 操作的最有效方法,使用联结会更有效。
子查询:
1 | SELECT cust_name, cust_contact |
联结:
1 | SELECT cust_name, cust_contact |
创建高级联结
为表起别名
SQL 除了可以对列名起别名( AS XXX ),还可以给表名起别名
1 | SELECT cust_name, cust_contact |
Oracle 中使用别名不可以使用
AS
,直接省略AS
即可,例如Customers AS C
写为Customers C
自联结
在同一个表中查询两次
子查询写法:
1 | SELECT cust_id, cust_name, cust_contact |
自联结写法:
1 | SELECT c1.cust_id, c1.cust_name, c1.cust_contact |
必须将 Customers 表分别起 c1 和 c2 别名来使用
在很多 DBMS 中,自联结比子查询快得多
自然联结
自然联结排除多次出现,使每一列只返回一次。
我们迄今为止建立的每个内联结都是自然联结,很可能永远都不会用到不是自然联结的内联结。
外联结
即内联结包括没有匹配的数据,但要选择一个表展示所有数据
内联结(不包含没有匹配的数据):
1 | SELECT Customers.cust_id, Orders.order_num |
外联结(包含没有匹配的数据):
1 | SELECT Customers.cust_id, Orders.order_num |
在这个代码中,没匹配的数据 order_num 表示为 NULL
外联结的关键字有LEFT OUTER JOIN
和RIGHT OUTER JOIN
两种(左外联结和右外联结),通过左右选择展示哪个表的全部数据(包含没有匹配的数据)
SQLite 中:
SQLite 仅支持LEFT OUTER JOIN
一个方向
还有一种外联结是“全外联结”FULL OUTER JOIN
包含两个表的全部数据。
1 | SELECT Customers.cust_id, Orders.order_num |
全外联结支持性很差:
Access、MariaDB、MySQL、Open Office Base 和 SQLite 不支持 FULL OUTER JOIN 语法。