CS-Notes/notes/SQL 语法.md

784 lines
19 KiB
Java
Raw Normal View History

2020-11-17 00:32:18 +08:00
# SQL 语法
2020-11-05 00:30:05 +08:00
<!-- GFM-TOC -->
2020-11-17 00:32:18 +08:00
* [SQL 语法](#sql-语法)
* [基础](#一基础)
* [创建表](#二创建表)
* [修改表](#三修改表)
* [插入](#四插入)
* [更新](#五更新)
* [删除](#六删除)
* [查询](#七查询)
* [DISTINCT](#distinct)
* [LIMIT](#limit)
* [排序](#八排序)
* [过滤](#九过滤)
* [通配符](#十通配符)
* [十一计算字段](#十一计算字段)
* [十二函数](#十二函数)
* [汇总](#汇总)
* [文本处理](#文本处理)
* [日期和时间处理](#日期和时间处理)
* [数值处理](#数值处理)
* [十三分组](#十三分组)
* [十四子查询](#十四子查询)
* [十五连接](#十五连接)
* [内连接](#内连接)
* [自连接](#自连接)
* [自然连接](#自然连接)
* [外连接](#外连接)
* [十六组合查询](#十六组合查询)
* [十七视图](#十七视图)
* [十八存储过程](#十八存储过程)
* [十九游标](#十九游标)
* [二十触发器](#二十触发器)
* [二十一事务管理](#二十一事务管理)
* [二十二字符集](#二十二字符集)
* [二十三权限管理](#二十三权限管理)
* [参考资料](#参考资料)
2020-11-05 00:30:05 +08:00
<!-- GFM-TOC -->
2020-11-17 00:32:18 +08:00
## 基础
2020-11-05 00:30:05 +08:00
模式定义了数据如何存储存储什么样的数据以及数据如何分解等信息数据库和表都有模式
主键的值不允许修改也不允许复用不能将已经删除的主键值赋给新数据行的主键
SQLStructured Query Language)标准 SQL ANSI 标准委员会管理从而称为 ANSI SQL各个 DBMS 都有自己的实现 PL/SQLTransact-SQL
SQL 语句不区分大小写但是数据库表名列名和值是否区分依赖于具体的 DBMS 以及配置
SQL 支持以下三种注释
```sql
2020-11-17 00:32:18 +08:00
## 注释
2020-11-05 00:30:05 +08:00
SELECT *
FROM mytable; -- 注释
/* 注释1
注释2 */
```
数据库创建与使用
```sql
CREATE DATABASE test;
USE test;
```
2020-11-17 00:32:18 +08:00
## 创建表
2020-11-05 00:30:05 +08:00
```sql
CREATE TABLE mytable (
# int 类型不为空自增
id INT NOT NULL AUTO_INCREMENT,
# int 类型不可为空默认值为 1不为空
col1 INT NOT NULL DEFAULT 1,
# 变长字符串类型最长为 45 个字符可以为空
col2 VARCHAR(45) NULL,
# 日期类型可为空
col3 DATE NULL,
# 设置主键为 id
PRIMARY KEY (`id`));
```
2020-11-17 00:32:18 +08:00
## 修改表
2020-11-05 00:30:05 +08:00
添加列
```sql
ALTER TABLE mytable
ADD col CHAR(20);
```
删除列
```sql
ALTER TABLE mytable
DROP COLUMN col;
```
删除表
```sql
DROP TABLE mytable;
```
2020-11-17 00:32:18 +08:00
## 插入
2020-11-05 00:30:05 +08:00
普通插入
```sql
INSERT INTO mytable(col1, col2)
VALUES(val1, val2);
```
插入检索出来的数据
```sql
INSERT INTO mytable1(col1, col2)
SELECT col1, col2
FROM mytable2;
```
将一个表的内容插入到一个新表
```sql
CREATE TABLE newtable AS
SELECT * FROM mytable;
```
2020-11-17 00:32:18 +08:00
## 更新
2020-11-05 00:30:05 +08:00
```sql
UPDATE mytable
SET col = val
WHERE id = 1;
```
2020-11-17 00:32:18 +08:00
## 删除
2020-11-05 00:30:05 +08:00
```sql
DELETE FROM mytable
WHERE id = 1;
```
**TRUNCATE TABLE** 可以清空表也就是删除所有行
```sql
TRUNCATE TABLE mytable;
```
使用更新和删除操作时一定要用 WHERE 子句不然会把整张表的数据都破坏可以先用 SELECT 语句进行测试防止错误删除
2020-11-17 00:32:18 +08:00
## 查询
2020-11-05 00:30:05 +08:00
2020-11-17 00:32:18 +08:00
### DISTINCT
2020-11-05 00:30:05 +08:00
相同值只会出现一次它作用于所有列也就是说所有列的值都相同才算相同
```sql
SELECT DISTINCT col1, col2
FROM mytable;
```
2020-11-17 00:32:18 +08:00
### LIMIT
2020-11-05 00:30:05 +08:00
限制返回的行数可以有两个参数第一个参数为起始行 0 开始第二个参数为返回的总行数
返回前 5
```sql
SELECT *
FROM mytable
LIMIT 5;
```
```sql
SELECT *
FROM mytable
LIMIT 0, 5;
```
返回第 3 \~ 5
```sql
SELECT *
FROM mytable
LIMIT 2, 3;
```
2020-11-17 00:32:18 +08:00
## 排序
2020-11-05 00:30:05 +08:00
- **ASC** 升序默认
- **DESC** 降序
可以按多个列进行排序并且为每个列指定不同的排序方式
```sql
SELECT *
FROM mytable
ORDER BY col1 DESC, col2 ASC;
```
2020-11-17 00:32:18 +08:00
## 过滤
2020-11-05 00:30:05 +08:00
不进行过滤的数据非常大导致通过网络传输了多余的数据从而浪费了网络带宽因此尽量使用 SQL 语句来过滤不必要的数据而不是传输所有的数据到客户端中然后由客户端进行过滤
```sql
SELECT *
FROM mytable
WHERE col IS NULL;
```
下表显示了 WHERE 子句可用的操作符
| 操作符 | 说明 |
| :---: | :---: |
| = | 等于 |
| &lt; | 小于 |
| &gt; | 大于 |
| &lt;&gt; != | 不等于 |
| &lt;= !&gt; | 小于等于 |
| &gt;= !&lt; | 大于等于 |
| BETWEEN | 在两个值之间 |
| IS NULL | NULL |
应该注意到NULL 0空字符串都不同
**AND OR** 用于连接多个过滤条件优先处理 AND当一个过滤表达式涉及到多个 AND OR 可以使用 () 来决定优先级使得优先级关系更清晰
**IN** 操作符用于匹配一组值其后也可以接一个 SELECT 子句从而匹配子查询得到的一组值
**NOT** 操作符用于否定一个条件
2020-11-17 00:32:18 +08:00
## 通配符
2020-11-05 00:30:05 +08:00
通配符也是用在过滤语句中但它只能用于文本字段
2020-11-17 00:32:18 +08:00
- **%** 匹配 \>=0 个任意字符
2020-11-05 00:30:05 +08:00
- **\_** 匹配 ==1 个任意字符
- **[ ]** 可以匹配集合内的字符例如 [ab] 将匹配字符 a 或者 b用脱字符 ^ 可以对其进行否定也就是不匹配集合内的字符
使用 Like 来进行通配符匹配
```sql
SELECT *
FROM mytable
WHERE col LIKE '[^AB]%'; -- 不以 A B 开头的任意文本
```
不要滥用通配符通配符位于开头处匹配会非常慢
2020-11-17 00:32:18 +08:00
## 十一计算字段
2020-11-05 00:30:05 +08:00
在数据库服务器上完成数据的转换和格式化的工作往往比客户端上快得多并且转换和格式化后的数据量更少的话可以减少网络通信量
计算字段通常需要使用 **AS** 来取别名否则输出的时候字段名为计算表达式
```sql
SELECT col1 * col2 AS alias
FROM mytable;
```
**CONCAT()** 用于连接两个字段许多数据库会使用空格把一个值填充为列宽因此连接的结果会出现一些不必要的空格使用 **TRIM()** 可以去除首尾空格
```sql
SELECT CONCAT(TRIM(col1), '(', TRIM(col2), ')') AS concat_col
FROM mytable;
```
2020-11-17 00:32:18 +08:00
## 十二函数
2020-11-05 00:30:05 +08:00
各个 DBMS 的函数都是不相同的因此不可移植以下主要是 MySQL 的函数
2020-11-17 00:32:18 +08:00
### 汇总
2020-11-05 00:30:05 +08:00
| | |
| :---: | :---: |
| AVG() | 返回某列的平均值 |
| COUNT() | 返回某列的行数 |
| MAX() | 返回某列的最大值 |
| MIN() | 返回某列的最小值 |
| SUM() |返回某列值之和 |
AVG() 会忽略 NULL
使用 DISTINCT 可以汇总不同的值
```sql
SELECT AVG(DISTINCT col1) AS avg_col
FROM mytable;
```
2020-11-17 00:32:18 +08:00
### 文本处理
2020-11-05 00:30:05 +08:00
| 函数 | 说明 |
| :---: | :---: |
| LEFT() | 左边的字符 |
| RIGHT() | 右边的字符 |
| LOWER() | 转换为小写字符 |
| UPPER() | 转换为大写字符 |
| LTRIM() | 去除左边的空格 |
| RTRIM() | 去除右边的空格 |
| LENGTH() | 长度 |
| SOUNDEX() | 转换为语音值 |
其中 **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式
```sql
SELECT *
FROM mytable
WHERE SOUNDEX(col1) = SOUNDEX('apple')
```
2020-11-17 00:32:18 +08:00
### 日期和时间处理
2020-11-05 00:30:05 +08:00
- 日期格式YYYY-MM-DD
2020-11-17 00:32:18 +08:00
- 时间格式HH:\<zero-width space\>MM:SS
2020-11-05 00:30:05 +08:00
| | |
| :---: | :---: |
| ADDDATE() | 增加一个日期周等|
| ADDTIME() | 增加一个时间分等|
| CURDATE() | 返回当前日期 |
| CURTIME() | 返回当前时间 |
| DATE() |返回日期时间的日期部分|
| DATEDIFF() |计算两个日期之差|
| DATE_ADD() |高度灵活的日期运算函数|
| DATE_FORMAT() |返回一个格式化的日期或时间串|
| DAY()| 返回一个日期的天数部分|
| DAYOFWEEK() |对于一个日期返回对应的星期几|
| HOUR() |返回一个时间的小时部分|
| MINUTE() |返回一个时间的分钟部分|
| MONTH() |返回一个日期的月份部分|
| NOW() |返回当前日期和时间|
| SECOND() |返回一个时间的秒部分|
| TIME() |返回一个日期时间的时间部分|
| YEAR() |返回一个日期的年份部分|
```sql
mysql> SELECT NOW();
```
```
2018-4-14 20:25:11
```
2020-11-17 00:32:18 +08:00
### 数值处理
2020-11-05 00:30:05 +08:00
| 函数 | 说明 |
| :---: | :---: |
| SIN() | 正弦 |
| COS() | 余弦 |
| TAN() | 正切 |
| ABS() | 绝对值 |
| SQRT() | 平方根 |
| MOD() | 余数 |
| EXP() | 指数 |
| PI() | 圆周率 |
| RAND() | 随机数 |
2020-11-17 00:32:18 +08:00
## 十三分组
2020-11-05 00:30:05 +08:00
把具有相同的数据值的行放在同一组中
可以对同一分组数据使用汇总函数进行处理例如求分组数据的平均值等
指定的分组字段除了能按该字段进行分组也会自动按该字段进行排序
```sql
SELECT col, COUNT(*) AS num
FROM mytable
GROUP BY col;
```
GROUP BY 自动按分组字段进行排序ORDER BY 也可以按汇总字段来进行排序
```sql
SELECT col, COUNT(*) AS num
FROM mytable
GROUP BY col
ORDER BY num;
```
WHERE 过滤行HAVING 过滤分组行过滤应当先于分组过滤
```sql
SELECT col, COUNT(*) AS num
FROM mytable
WHERE col > 2
GROUP BY col
HAVING num >= 2;
```
分组规定
- GROUP BY 子句出现在 WHERE 子句之后ORDER BY 子句之前
- 除了汇总字段外SELECT 语句中的每一字段都必须在 GROUP BY 子句中给出
- NULL 的行会单独分为一组
- 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型
2020-11-17 00:32:18 +08:00
## 十四子查询
2020-11-05 00:30:05 +08:00
子查询中只能返回一个字段的数据
可以将子查询的结果作为 WHRER 语句的过滤条件
```sql
SELECT *
FROM mytable1
WHERE col1 IN (SELECT col2
FROM mytable2);
```
下面的语句可以检索出客户的订单数量子查询语句会对第一个查询检索出的每个客户执行一次
```sql
SELECT cust_name, (SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id)
AS orders_num
FROM Customers
ORDER BY cust_name;
```
2020-11-17 00:32:18 +08:00
## 十五连接
2020-11-05 00:30:05 +08:00
连接用于连接多个表使用 JOIN 关键字并且条件语句使用 ON 而不是 WHERE
连接可以替换子查询并且比子查询的效率一般会更快
可以用 AS 给列名计算字段和表名取别名给表名取别名是为了简化 SQL 语句以及连接相同表
2020-11-17 00:32:18 +08:00
### 内连接
2020-11-05 00:30:05 +08:00
内连接又称等值连接使用 INNER JOIN 关键字
```sql
SELECT A.value, B.value
FROM tablea AS A INNER JOIN tableb AS B
ON A.key = B.key;
```
可以不明确使用 INNER JOIN而使用普通查询并在 WHERE 中将两个表中要连接的列用等值方法连接起来
```sql
SELECT A.value, B.value
FROM tablea AS A, tableb AS B
WHERE A.key = B.key;
```
2020-11-17 00:32:18 +08:00
### 自连接
2020-11-05 00:30:05 +08:00
自连接可以看成内连接的一种只是连接的表是自身而已
一张员工表包含员工姓名和员工所属部门要找出与 Jim 处在同一部门的所有员工姓名
子查询版本
```sql
SELECT name
FROM employee
WHERE department = (
SELECT department
FROM employee
WHERE name = "Jim");
```
自连接版本
```sql
SELECT e1.name
FROM employee AS e1 INNER JOIN employee AS e2
ON e1.department = e2.department
AND e2.name = "Jim";
```
2020-11-17 00:32:18 +08:00
### 自然连接
2020-11-05 00:30:05 +08:00
自然连接是把同名列通过等值测试连接起来的同名列可以有多个
内连接和自然连接的区别内连接提供连接的列而自然连接自动连接所有同名列
```sql
SELECT A.value, B.value
FROM tablea AS A NATURAL JOIN tableb AS B;
```
2020-11-17 00:32:18 +08:00
### 外连接
2020-11-05 00:30:05 +08:00
外连接保留了没有关联的那些行分为左外连接右外连接以及全外连接左外连接就是保留左表没有关联的行
检索所有顾客的订单信息包括还没有订单信息的顾客
```sql
SELECT Customers.cust_id, Customer.cust_name, Orders.order_id
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
```
customers
| cust_id | cust_name |
| :---: | :---: |
| 1 | a |
| 2 | b |
| 3 | c |
orders
| order_id | cust_id |
| :---: | :---: |
|1 | 1 |
|2 | 1 |
|3 | 3 |
|4 | 3 |
结果
| cust_id | cust_name | order_id |
| :---: | :---: | :---: |
| 1 | a | 1 |
| 1 | a | 2 |
| 3 | c | 3 |
| 3 | c | 4 |
| 2 | b | Null |
2020-11-17 00:32:18 +08:00
## 十六组合查询
2020-11-05 00:30:05 +08:00
使用 **UNION** 来组合两个查询如果第一个查询返回 M 第二个查询返回 N 那么组合查询的结果一般为 M+N
每个查询必须包含相同的列表达式和聚集函数
默认会去除相同行如果需要保留相同行使用 UNION ALL
只能包含一个 ORDER BY 子句并且必须位于语句的最后
```sql
SELECT col
FROM mytable
WHERE col = 1
UNION
SELECT col
FROM mytable
WHERE col =2;
```
2020-11-17 00:32:18 +08:00
## 十七视图
2020-11-05 00:30:05 +08:00
视图是虚拟的表本身不包含数据也就不能对其进行索引操作
对视图的操作和对普通表的操作一样
视图具有如下好处
- 简化复杂的 SQL 操作比如复杂的连接
- 只使用实际表的一部分数据
- 通过只给用户访问视图的权限保证数据的安全性
- 更改数据格式和表示
```sql
CREATE VIEW myview AS
SELECT Concat(col1, col2) AS concat_col, col3*col4 AS compute_col
FROM mytable
WHERE col5 = val;
```
2020-11-17 00:32:18 +08:00
## 十八存储过程
2020-11-05 00:30:05 +08:00
存储过程可以看成是对一系列 SQL 操作的批处理
使用存储过程的好处
- 代码封装保证了一定的安全性
- 代码复用
- 由于是预先编译因此具有很高的性能
命令行中创建存储过程需要自定义分隔符因为命令行是以 ; 为结束符而存储过程中也包含了分号因此会错误把这部分分号当成是结束符造成语法错误
包含 inout inout 三种参数
给变量赋值都需要用 select into 语句
每次只能给一个变量赋值不支持集合的操作
```sql
delimiter //
create procedure myprocedure( out ret int )
begin
declare y int;
select sum(col1)
from mytable
into y;
select y*y into ret;
end //
delimiter ;
```
```sql
call myprocedure(@ret);
select @ret;
```
2020-11-17 00:32:18 +08:00
## 十九游标
2020-11-05 00:30:05 +08:00
在存储过程中使用游标可以对一个结果集进行移动遍历
游标主要用于交互式应用其中用户需要对数据集中的任意行进行浏览和修改
使用游标的四个步骤
1. 声明游标这个过程没有实际检索出数据
2. 打开游标
3. 取出数据
4. 关闭游标
```sql
delimiter //
create procedure myprocedure(out ret int)
begin
declare done boolean default 0;
declare mycursor cursor for
select col1 from mytable;
# 定义了一个 continue handler sqlstate '02000' 这个条件出现时会执行 set done = 1
declare continue handler for sqlstate '02000' set done = 1;
open mycursor;
repeat
fetch mycursor into ret;
select ret;
until done end repeat;
close mycursor;
end //
delimiter ;
```
2020-11-17 00:32:18 +08:00
## 二十触发器
2020-11-05 00:30:05 +08:00
触发器会在某个表执行以下语句时而自动执行DELETEINSERTUPDATE
触发器必须指定在语句执行之前还是之后自动执行之前执行使用 BEFORE 关键字之后执行使用 AFTER 关键字BEFORE 用于数据验证和净化AFTER 用于审计跟踪将修改记录到另外一张表中
INSERT 触发器包含一个名为 NEW 的虚拟表
```sql
CREATE TRIGGER mytrigger AFTER INSERT ON mytable
FOR EACH ROW SELECT NEW.col into @result;
SELECT @result; -- 获取结果
```
DELETE 触发器包含一个名为 OLD 的虚拟表并且是只读的
UPDATE 触发器包含一个名为 NEW 和一个名为 OLD 的虚拟表其中 NEW 是可以被修改的 OLD 是只读的
MySQL 不允许在触发器中使用 CALL 语句也就是不能调用存储过程
2020-11-17 00:32:18 +08:00
## 二十一事务管理
2020-11-05 00:30:05 +08:00
基本术语
- 事务transaction指一组 SQL 语句
- 回退rollback指撤销指定 SQL 语句的过程
- 提交commit指将未存储的 SQL 语句结果写入数据库表
- 保留点savepoint指事务处理中设置的临时占位符placeholder你可以对它发布回退与回退整个事务处理不同
不能回退 SELECT 语句回退 SELECT 语句也没意义也不能回退 CREATE DROP 语句
MySQL 的事务提交默认是隐式提交每执行一条语句就把这条语句当成一个事务然后进行提交当出现 START TRANSACTION 语句时会关闭隐式提交 COMMIT ROLLBACK 语句执行后事务会自动关闭重新恢复隐式提交
设置 autocommit 0 可以取消自动提交autocommit 标记是针对每个连接而不是针对服务器的
如果没有设置保留点ROLLBACK 会回退到 START TRANSACTION 语句处如果设置了保留点并且在 ROLLBACK 中指定该保留点则会回退到该保留点
```sql
START TRANSACTION
// ...
SAVEPOINT delete1
// ...
ROLLBACK TO delete1
// ...
COMMIT
```
2020-11-17 00:32:18 +08:00
## 二十二字符集
2020-11-05 00:30:05 +08:00
基本术语
- 字符集为字母和符号的集合
- 编码为某个字符集成员的内部表示
- 校对字符指定如何比较主要用于排序和分组
除了给表指定字符集和校对外也可以给列指定
```sql
CREATE TABLE mytable
(col VARCHAR(10) CHARACTER SET latin COLLATE latin1_general_ci )
DEFAULT CHARACTER SET hebrew COLLATE hebrew_general_ci;
```
可以在排序分组时指定校对
```sql
SELECT *
FROM mytable
ORDER BY col COLLATE latin1_general_ci;
```
2020-11-17 00:32:18 +08:00
## 二十三权限管理
2020-11-05 00:30:05 +08:00
MySQL 的账户信息保存在 mysql 这个数据库中
```sql
USE mysql;
SELECT user FROM user;
```
**创建账户**
新创建的账户没有任何权限
```sql
CREATE USER myuser IDENTIFIED BY 'mypassword';
```
**修改账户名**
```sql
RENAME USER myuser TO newuser;
```
**删除账户**
```sql
DROP USER myuser;
```
**查看权限**
```sql
SHOW GRANTS FOR myuser;
```
**授予权限**
账户用 username@host 的形式定义username@% 使用的是默认主机名
```sql
GRANT SELECT, INSERT ON mydatabase.* TO myuser;
```
**删除权限**
GRANT REVOKE 可在几个层次上控制访问权限
- 整个服务器使用 GRANT ALL REVOKE ALL
- 整个数据库使用 ON database.\*
- 特定的表使用 ON database.table
- 特定的列
- 特定的存储过程
```sql
REVOKE SELECT, INSERT ON mydatabase.* FROM myuser;
```
**更改密码**
必须使用 Password() 函数进行加密
```sql
SET PASSWROD FOR myuser = Password('new_password');
```
2020-11-17 00:32:18 +08:00
## 参考资料
2020-11-05 00:30:05 +08:00
- BenForta. SQL 必知必会 [M]. 人民邮电出版社, 2013.