1.了解SQL
数据库(database)保存有组织的数据的容器(通常是一个文件或一组文件)。
表 (table)某种特定类型数据的结构化清单。
模式(schema)关于数据库和表的布局及特性的信息。
列(column)表中的一个字段。所有的表都是由一个或多个列组成的。
行(row)表中的一个记录。
数据类型(datatype)所容许的数据的类型。
主键(primary key)一列(或一组列),其值能够唯一区分表中每个行。
SQL(发音为字母S-Q-L或sequel)是结构化查询语言(Structured Query Language)的缩写。SQL是一种专门用来与数据库通信的语言。
表中的任何列都可以作为主键,只要它满足以下条件:
- 任意两行都不具有相同的主键值;
- 每个行都必须具有一个主键值(主键列不允许 NULL 值)。
SQL 有如下的优点
- SQL 不是某个特定数据库供应商专有的语言。几乎所有重要的 DBMS 都支持 SQL ,所以,学习此语言使你几乎能与所以数据库打交道。
- SQL 简单易学。它的语句全都是由描述性很强的英语单词组成,而且这些单词的数目不多。
- SQL 尽管看上去很简单,但它实际上使一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作。
2.操作数据库表
show databases; // 返回当前数据库的列表
use crash; // 选择使用 crash 数据库
show tables; // 返回当前选择数据库内的表的列表
show columns from customers; // 显示 customers 表列信息
show status; // 用于显示广泛的服务器状态信息
show create database 和 show create table 分别用来显示创建数据库或表的mysql语句
show errors 和 show warnings 用来显示服务器错误或警告信息
3.检索数据
检索单个列
// 利用 select 语句从 products 表中检索一个名为 prod_name的列
select prod_name from products;
检索多个列
// 在选择多个列时,一定要在列明之间加上逗号,最后一个列名后不加
select prod_id, prod_name, prod_price from products;
检索所有列
// 使用通配符* 检索所有列
select * from products;
检索不同的行
// 使用 distinct 关键字,将返回不同的值
// distinct 将应用于所有列,而不仅仅是它前置的列
select distinct vend_id from products;
限制结果
/*
* limit 5 , 5 返回行5开始的5行。
* 第一个数为开始位置,第二个数为要检索的行数
* 带一个值的 limit 总是从第一行开始,给出的数为返回的行数。
* 带两个值的 limit 可以指定从行号为第一个值的位置开始。
*/
select prod_name from products limit 5 , 5;
4.排序检索数据
// 排序数据
// order by 对 prod_name列以字母顺序排序数据
select prod_name from products order by prod_name;
// 按多个列排序
// order by 指定列名,列名之间用逗号分隔
select prod_id , prod_price , prod_name from products order by prod_price , prod_name;
// 指定排序方向
// order by 默认使用升序,为了进行降序排序,可以指定 desc 关键字
select prod_id , prod_price , prod_name from products order by prod_price desc;
/*
* 如果想在多个列上进行降序,必须要对每个列指定 desc 关键字
* order by 应该位于 from 之后。如果使用 limit ,它必须位于 order by 之后
*/
5.过滤数据
// 数据根据 where 子句中指定的搜索条件进行过滤
select prod_name , prod_price from products where prod_price = 2.1;
// between
select * from prod_price between 5 and 10;
// IS NULL
select cust_id from customers where cust_email IS NULL;
where子句操作符
操作符 | 说明 |
---|---|
= | 等于 |
<> | 不等于 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
between | 在指定的两个值之间 |
6.数据过滤
// and | or 操作符用来联结或改变 where 子句中的子句的关键字。也称逻辑操作符
select prod_name , prod_price from products where vend_id = 1002 or vend_id ==1003 and prod_price >= 10;
// 在 where 子句中使用圆括号明确分组操作符。使用圆括号没什么坏处,还能消除歧义。
select prod_name , prod_price from products where (vend_id = 1002 or vend_id ==1003) and prod_price >= 10;
// in 操作符用来指定条件范围,范围中的每个条件都可以进行匹配
select prod_name , prod_price from products where vend_id in (1002,1003);
// not 操作符有且只有一个功能,那就是否定它之后所跟的任何条件
// not 支持in、between和exists
select prod_name , prod_price from products where vend_id not in (1002,1003);
7.用通配符过滤
通配符(wildcard)用来匹配值的一部分的特殊字符
搜索模式(search pattern)由字面值、通配符或两者组合构成的搜索条件。
百分号(%)通配符,% 表示任何字符出现任意次数。
区分大小写 根据MySQL的配置方式,搜索可以是区分大小写的。
// 找出所有以jet起头的产品
select prod_id , prod_name from products where prod_name like 'ject%';
下划线( _ )通配符 下划线的用途跟 % 一样,但只匹配单个字符而不是多个字符
与 % 能匹配 0 个字符不一样,_ 总是匹配一个字符,不能多不能少。
// 找出后面跟有指定文本内容的产品
select prod_id , prod_name from products where prod_name like '_ ton anvil';
使用通配符技巧
正如所见,MySQL的通配符很有用。但这种功能是有代价的:通配符搜索的处理一般要比其他搜索所花时间更长。
- 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
- 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始出,搜索起来是最慢的。
- 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
8.用正则表达式进行搜索
使用正则表达式来更好的控制数据过滤。
// 基本字符过滤
// 检索列 prod_name 包含文本 1000 的所有行
select prod_name from products where prod_name regexp '1000';
// ( . )点匹配任意一个字符
select prod_name from products where prod_name regexp '.000';
// or 进行匹配 , 匹配其中之一
select prod_name from products where prod_name regexp '1000|2000';
// [] 匹配几个字符之一
// 事实上,正则表达式[123]为[1|2|3]的缩写,也可以使用后者
// 匹配范围 [1-9] 匹配1到9开头
select prod_name from products where prod_name regexp '[1-9] Ton';
// 匹配特殊字符 要用 转义
// 为了匹配特殊字符,必须用 \ 为前导。\- 表示查找- ,\. 表示查找.
select vend_name from vendors where vend_name regexp '\.';
空白元字符
元字符 | 说明 |
---|---|
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 制表 |
\v | 纵向制表 |
匹配 为了匹配反斜杠( )字符本身,需要使用 \
多数正则表达式实现使用单个反斜杠转义字符,以便能使用这些字符本身。
但MySQL要求两个反斜杠(MySQL自己解释一个,正则表达式库解释另外一个)。
匹配字符类
类 1 | 说明 |
---|---|
[ :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: ] | 任意大写字母 |
[ :xdigit: ] | 任意十六进制数字(同[a-fA-F0-9]) |
匹配多个实例
目前为止使用的所有正则表达式都试图匹配单次出现。
有时候需要对匹配的数目进行更强的控制。
例如,你可能想寻找一个单词并且还能够适应一个尾随的s,等等
重复元字符
元字符 | 说明 |
---|---|
* | 0 个或多个匹配 |
+ | 1 个或多个匹配( 等于{1,} ) |
? | 0 个或1个匹配( 等于{0,1} ) |
{n} | 指定数目的匹配 |
{n,} | 不少于指定数目的匹配 |
{n,m} | 指定数目的范围( m 不超过255 ) |
// [0-9] 匹配任意数字,sticks? 匹配 stick 和 sticks
// 没有 ? , 匹配 stick 和 sticks 会非常困难
select prod_name from products where prod_name regexp '\([0-9] sticks?\)';
定位符
目前为止的所有例子都是匹配一个串中的任意的文本。为了匹配特定位置的文本,需要使用定位符
定位元字符
元字符 | 说明 |
---|---|
^ | 文本的开始 |
$ | 文本的结尾 |
[ [ :<: ] ] | 词的开始 |
[ [ :>: ] ] | 词的结尾 |
// 只有开始为任意数字或 . 才会匹配
select prod_name from products where prod_name regexp '^[0-9\.]';
like 与 regexp 的区别
select prod_name from products where prod_name like '1000';
select prod_name from products where prod_name regexp '1000';
执行上述两条语句,会发现第一条语句不返回数据,而第二条语句返回数据。为什么呢?
这是因为 like 匹配整个列,而匹配的文本在列值中出现,like 将不会找到它
而 regexp 在列值内进行匹配,如果被匹配的文本在列值中出现,regexp 将会找到它
而当 regexp 使用定位符 ^ 开始表达式 $ 结束表达式
regexp 的作用就和 like 一样了
// 简单的正则表达式测试
select 'hello' regexp '[0-9]';
这个例子显然将返回 0 (因为文本 hello 中没有关键字)
9.创建计算字段
字段(field)基本上与列( column )的意思相同,不过数据库列一般称为列。
拼接( concatenate )将值联结到一起构成单个值。
- 如果想在一个字段中既显示公司名,又显示公司地址,但这两个信息一般包含在不同的表列中。
- 列数据是大小写混合的,但报表程序需要把所有数据按大写表示出来
- 需要根据表数据进行总数、平均数计算或其他计算。
在上述例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据;而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。
这就是计算字段发挥作用的所在了。
// concat()函数,拼接两个列
select concat(vend_name,'(',vend_country,')') from vendors;
输出:ACME ( USA )
从前面输出中可以看到,select语句拼接地址字段工作得很好。
但此新计算列的名字是什么呢?实际上它没有名字,它只是一个值。
但是,一个未命名的列不能用于客户机应用中,因为客户机没有办法引用它。
别名(alias)是一个字段或值得替换名。别名用 AS 关键字赋予。
// 将计算好的值赋予 vend_name
select concat(vend_name,'(',vend_country,')') as vend_title from vendors;
// 执行算术计算
// 汇总物品得价格(单价乘以订购数量)赋予别名 expanded_price
select prod_id , quantity , item_price , quantity*item_price as expanded_price from orderitems;
10.使用数据处理函数
SQL支持利用函数来处理数据。
常用的文本处理函数
函数 | 说明 |
---|---|
left() | 返回串左边的字符 |
length() | 返回串的长度 |
locate() | 找出串的一个子串 |
lower() | 将串转换为小写 |
ltrim() | 去掉串左面的空格 |
right() | 返回串右边的字符 |
rtrim() | 去掉串右边的空格 |
soundex() | 返回串的SOUNDEX值 |
substring() | 返回子串的字符 |
upper() | 将串转换为大写 |
// SOUNDEX()函数 SOUNDEX是将一个将任何文本串转换为描述其语音表示的字母数字模式的算法。
// SOUNDEX考虑了类似发音字符和音节,使得能对串进行发音比较而不是字母比较。
// 使用SOUNDEX()函数搜索,它匹配所有发音类似于 Y . Lie 的联系名
select cust_name , cust_contact from customers where cust_contact = 'Y . Lie';
// 查询出 Y Lee
日期和时间处理函数
函数 | 说明 |
---|---|
AddDate() | 增加一个日期(天、周等) |
AddTime() | 增加一个日期(时、分等) |
CurDate() | 返回当前日期 |
CurTime() | 返回当前时间 |
Date() | 返回日期时间的日期部分 |
DateDiff() | 计算两个日期之差 |
Date_Add() | 高度灵活的日期运用函数 |
Date_Format() | 返回一个格式化的日期或时间串 |
Day() | 返回一个日期的天数部分 |
DayOfWeek() | 对于一个日期,返回对应的星期几 |
Hour() | 返回一个时间的小时部分 |
Minute() | 返回一个时间的分钟部分 |
Month() | 返回一个时间的月份部分 |
Now() | 返回当前日期和时间 |
Second() | 返回一个日期的秒部分 |
Time() | 返回一个日期时间的时间部分 |
Year() | 返回一个日期的年份部分 |
// 日期比较
select cust_id , order_num form orders where Date(order_date) = ' 2015-09-01';
常用数值处理函数
函数 | 说明 |
---|---|
Abs() | 返回一个数的绝对值 |
Cos() | 返回一个角度的余弦 |
Exp() | 返回一个数的指数值 |
Mod() | 返回除操作的余数 |
Pi() | 返回圆周率 |
Rand() | 返回一个随机数 |
Sin() | 返回一个角度的正弦 |
Sqrt() | 返回一个数的平方根 |
Tan() | 返回一个角度的正切 |
11.汇总数据
聚集函数( aggregate function )运行在行组上,计算和返回单个值的函数。
SQL聚集函数
函数 | 说明 |
---|---|
AVG() | 返回某列的平均值 |
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
SUM() | 返回某列值之和 |
12.分组数据
分组允许把数据分为多个逻辑组,以便能对每个组进行聚集计算。
理解分组最好的办法是看一个例子:
// 返回每个供应商能提供的产品数目
select vend_id , count(*) as num_prods from products group by vend_id;
vend_id | num_prods |
---|---|
1001 | 3 |
1002 | 2 |
1005 | 7 |
在具体使用 GROUP BY 子句前,需要知道一些重要的规定。
- GROUP BY子句可能包含任意数目的列。这使得能对分组进行嵌套,为数据分组提供更细致的控制。
- 如果在 GROUP BY 子句中嵌套了分组,数据将在最后规定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。
- GROUP BY 子句中列出的每个列都必须是检索列或有效的表达式(但不是能是聚集函数)。如果在 SELECT 中使用表达式,则必须在 GROUP BY 子句中指定相同的表达式。不能使用别名。
- 除聚集计算语句外,SELECT 语句中的每个列都必须在 GROUP BY 子句中给出。
- 如果分组列中具有 NULL 值,则 NULL 将作为一个分组返回。如果列中有多行 NULL值,它们将分为一组。
- GROUP BY 子句 必须出现在 WHERE 子句后, ORDER BY 子句之前。
// 使用 ROLLUP 关键字,可以得到每个分组以及每个分组汇总级别(针对每个分组)的值
select vend_id , count(*) as num_prods from products group by vend_id with rollup;
过滤分组
除了能用 GROUP BY 分组数据外, MySQL 还允许过滤分组,规定包括那些分组,排除那些分组。
但是 WHERE 不能完成任务,因为 WHERE 过滤指定的是行而不是分组。
事实上,WHERE 没有分组的概念。
MySQL 为此提供了另外的子句,那就是 having 。
事实上,目前为止所学过的所有类型的 WHERE 子句都可以用 HAVING 来代替。
唯一的差别是 WHERE 过滤行,而 HAVING 过滤分组。
// 过滤两个订单以下的分组
select cust_id , count(*) as order from orders group by cust_id having count(*) >= 2;
HAVING 和 WHERE 的差别
这里有另一种理解方法,WHERE在数据分组前进行过滤,HAVING 在数据分组后进行过滤。
WHERE 排除的行不包括分组中,从而影响 HAVING 子句中基于这些值过滤的分组。
// 过滤订单价钱小于10元,两个订单以下的分组
SELECT vend_id , COUNT(*) as num_prods from products where prod_price >= 10 GROUP BY vend_id HAVING COUNT(*) >= 2;
不要忘记 order by 一般在使用 group by 子句时,应该也给出 order by 子句。这是保证数据正确排序的唯一方法。千万不要仅依赖 group by 排序数据。
// 排序按照 group by 指定的 order_num排序
select order_num , SUM(quantity*item_price) as ordertotal from orderitems group by order_num having SUM(quantity*item_price) >= 50;
// 这时候想用 ordertotal 订单价格排序,使用 order by 就好了
select order_num , SUM(quantity*item_price) as ordertotal from orderitems group by order_num having SUM(quantity*item_price) >= 50 order by ordertotal;
select 子句使用必须遵循的次序
子句 | 说明 | 是否必须使用 |
---|---|---|
select | 要返回的列或表达式 | 是 |
from | 从中检索数据的表 | 仅在从表选择数据时使用 |
where | 行级过滤 | 否 |
group by | 分组说明 | 仅在按组计算聚集时使用 |
having | 组级过滤 | 否 |
order by | 输出排序顺序 | 否 |
limit | 要检索的行数 | 否 |
13.使用子查询
子查询(subquery),即嵌套在其他查询中的查询。
订单存储在两个表中。
对于包含订单号、客户ID、订单日期的每个订单,order表存储一行。
各订单的物品存储在相关的 orderitems 表中。
orders 表不存储客户信息。它只存储客户的ID。
实际的客户信息存储在 customers 表中。
- 检索包含物品 TNT2的所有订单的编号。
SELECT order_num FROM orderitems WHERE prod_id = 'TNT2';
order_num |
---|
20005 |
20007 |
- 检索具有前一步列出的订单编号的所有客户的ID。
SELECT cust_id FROM orders WHERE order_num IN (20005,20007);
cust_id |
---|
10001 |
10004 |
- 检索前一步骤返回的所有客户ID的客户信息。
SELECT cust_name,cust_contact FROM customers WHERE cust_id IN (10001,10004);
cust_name | cust_contact |
---|---|
Coyote Inc. | Y Lee |
Yosemite | Y Sam |
上述每个步骤都可以单独作为一个查询来执行。可以把一条 SELECT 语句返回的结果用于另一条 SELECT 语句的 WHERE 子句。
也可以使用另一条 SELECT 语句的 WHERE 子句。
SELECT cust_name,cust_contact FORM customers WHERE cust_id IN (
SELECT cust_id
FROM orders
WHERE order_num IN (
SELECT order_num
FROM orderitems
WHERE prod_id = 'TNT2'
)
);
格式化SQL 包含子查询的 SELECT 语句难以阅读和调试,特别是它们较为复杂时更是如此。如上所示把子查询分解为多行并且适当地进行缩进,能极大地简化子查询的使用。
13.使用子查询
列必须匹配 在 WHERE 子句中使用子查询,应该保证 SELECT 语句具有与 WHERE 子句中相同数目的列。通常子查询将返回单个列并且与单个列匹配,但如果需要也可以使用多个列。
作为计算字段使用子查询
SELECT cust_name,
cust_state,
(SELECT COUNT(*)
FROM orders
WHERE order.cust_id = customers.cust_id) AS order
FROM customers
ORDER BY cust_name;
子查询中的 WHERE 子句与前面的 WHERE子句稍有不同,因为它使用了完全限定列名。
子查询SQL 与当前 customers表中检索 cust_id。
这种类型叫做相关子查询。
相关子查询(correlated subquery)涉及外部查询的子查询。
14.联结表
关联表的设计就是要把保证把信息分解成多个表,一类数据一个表。表通过某些常用的值相互关联。
外键(foreign key)外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
这样有个好处 可伸缩性好,数据无重复。
可伸缩性(scale)能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好(scale well)。
正如所述,分解数据为多个表能更有效地存储,更方便地处理,并且具有更大的可伸缩性。但这些好处是有代价的。
创建联结
SELECT vend_name,prod_name,prod_price
FROM vendors,products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend_name,prod_name;
完全限定列名 在引用的列可能出现二义性时,必须使用完全限定列名(用一个点分隔的表名和列名)。如果引入一个没有表名限制的具有二义性的列名,MySQL将返回错误。