SQL 语句语法详解(完整学习手册)
以 SQL Server(Transact-SQL / T-SQL)为主线,兼顾通用标准 SQL语法,覆盖从基础查询到高级特性、事务、索引、视图、存储过程、函数、触发器、动态 SQL 等。绝大多数语法在不同数据库中通用,差异处会特别标注。
目录
文章目录
- [SQL 语句语法详解(完整学习手册)](#SQL 语句语法详解(完整学习手册))
-
- 目录
- [一、SQL 概述与分类](#一、SQL 概述与分类)
- 二、数据库与表的基本操作(DDL)
-
- [2.1 数据库](#2.1 数据库)
- [2.2 创建表](#2.2 创建表)
- [2.3 常见数据类型](#2.3 常见数据类型)
- [2.4 修改表结构(ALTER)](#2.4 修改表结构(ALTER))
- [2.5 删除 / 清空表](#2.5 删除 / 清空表)
- 三、数据操纵(DML)
-
- [3.1 INSERT 插入](#3.1 INSERT 插入)
- [3.2 UPDATE 更新](#3.2 UPDATE 更新)
- [3.3 DELETE 删除](#3.3 DELETE 删除)
- 四、基础查询(SELECT)
-
- [4.1 基本语法](#4.1 基本语法)
- [4.2 查询列](#4.2 查询列)
- 五、条件过滤与逻辑运算
-
- [5.1 比较运算符](#5.1 比较运算符)
- [5.2 逻辑运算符](#5.2 逻辑运算符)
- [5.3 范围 BETWEEN](#5.3 范围 BETWEEN)
- [5.4 集合 IN / NOT IN](#5.4 集合 IN / NOT IN)
- [5.5 模糊匹配 LIKE](#5.5 模糊匹配 LIKE)
- [5.6 空值判断 IS NULL](#5.6 空值判断 IS NULL)
- [5.7 EXISTS](#5.7 EXISTS)
- 六、排序、分页与去重
-
- [6.1 排序 ORDER BY](#6.1 排序 ORDER BY)
- [6.2 返回记录数 / 分页](#6.2 返回记录数 / 分页)
- 七、聚合函数与分组
-
- [7.1 聚合函数](#7.1 聚合函数)
- [7.2 GROUP BY 分组](#7.2 GROUP BY 分组)
- [7.3 HAVING 分组后过滤](#7.3 HAVING 分组后过滤)
- 八、多表连接(JOIN)
-
- [8.1 连接类型](#8.1 连接类型)
- [8.2 连接示例](#8.2 连接示例)
- [8.3 传统写法(隐式连接)](#8.3 传统写法(隐式连接))
- 九、子查询与集合运算
-
- [9.1 子查询](#9.1 子查询)
- [9.2 集合运算](#9.2 集合运算)
- 十、常用函数
-
- [10.1 数学函数](#10.1 数学函数)
- [10.2 聚合与空值处理](#10.2 聚合与空值处理)
- [10.3 类型转换](#10.3 类型转换)
- 十一、字符串与日期处理
-
- [11.1 字符串函数](#11.1 字符串函数)
- [11.2 日期函数](#11.2 日期函数)
- 十二、约束与完整性
-
- [12.1 约束类型](#12.1 约束类型)
- [12.2 示例](#12.2 示例)
- [12.3 给已有表加约束](#12.3 给已有表加约束)
- 十三、索引
-
- [13.1 索引类型](#13.1 索引类型)
- [13.2 创建索引](#13.2 创建索引)
- [13.3 注意事项](#13.3 注意事项)
- 十四、视图
- 十五、事务与锁
- 十六、存储过程与函数
-
- [16.1 存储过程](#16.1 存储过程)
- [16.2 用户自定义函数(UDF)](#16.2 用户自定义函数(UDF))
- 十七、触发器
- 十八、游标
- [十九、动态 SQL](#十九、动态 SQL)
- 二十、最佳实践与常见坑
-
- [20.1 最佳实践](#20.1 最佳实践)
- [20.2 常见坑](#20.2 常见坑)
- [附:SQL 关键字速查表](#附:SQL 关键字速查表)
一、SQL 概述与分类
SQL(Structured Query Language,结构化查询语言)是关系型数据库的标准语言,主要有四大子分类:
| 分类 | 全称 | 作用 | 关键字示例 |
|---|---|---|---|
| DDL | Data Definition Language | 定义/修改数据库结构 | CREATE ALTER DROP TRUNCATE |
| DML | Data Manipulation Language | 操作数据 | SELECT INSERT UPDATE DELETE |
| DCL | Data Control Language | 权限控制 | GRANT REVOKE |
| TCL | Transaction Control Language | 事务控制 | BEGIN COMMIT ROLLBACK SAVEPOINT |
有些分类将
SELECT单独归为 DQL(Data Query Language,数据查询语言)。
二、数据库与表的基本操作(DDL)
2.1 数据库
sql
-- 创建数据库
CREATE DATABASE MyDB;
-- 创建数据库(指定字符集与排序规则)
CREATE DATABASE MyDB
COLLATE Chinese_PRC_CI_AS; -- SQL Server
-- 查看所有数据库
SELECT name FROM sys.databases; -- SQL Server
SHOW DATABASES; -- MySQL
-- 切换 / 使用数据库
USE MyDB; -- SQL Server / MySQL
-- 删除数据库(慎用)
DROP DATABASE MyDB;
2.2 创建表
sql
CREATE TABLE Students (
StudentID INT NOT NULL, -- 学号
Name NVARCHAR(50) NOT NULL, -- 姓名
Gender CHAR(1) NULL, -- 性别 M/F
Age TINYINT NULL, -- 年龄
Birthday DATE NULL, -- 生日
Score DECIMAL(5,2) DEFAULT 0, -- 成绩(两位小数)
CreateTime DATETIME DEFAULT GETDATE(),
PRIMARY KEY (StudentID) -- 主键
);
2.3 常见数据类型
| 类别 | 类型 | 说明 |
|---|---|---|
| 整数 | TINYINT / SMALLINT / INT / BIGINT |
1/2/4/8 字节整数 |
| 小数 | DECIMAL(p,s) / NUMERIC(p,s) |
精确小数,p 总位数、s 小数位 |
| 浮点 | FLOAT / REAL |
近似值 |
| 字符 | CHAR(n) / VARCHAR(n) |
定长 / 变长(非 Unicode) |
| 字符 | NCHAR(n) / NVARCHAR(n) |
Unicode 定长 / 变长 |
| 文本 | TEXT / NTEXT / VARCHAR(MAX) / NVARCHAR(MAX) |
大文本 |
| 日期 | DATE / TIME / DATETIME / DATETIME2 / SMALLDATETIME |
日期时间 |
| 二进制 | BINARY / VARBINARY / IMAGE |
二进制数据 |
| 布尔 | BIT(SQL Server)/ BOOLEAN(PostgreSQL) |
0/1 或 true/false |
| 其他 | UNIQUEIDENTIFIER(GUID)/ XML / JSON |
特殊类型 |
2.4 修改表结构(ALTER)
sql
-- 添加列
ALTER TABLE Students ADD Email NVARCHAR(100);
-- 修改列数据类型
ALTER TABLE Students ALTER COLUMN Name NVARCHAR(100); -- SQL Server
-- 删除列
ALTER TABLE Students DROP COLUMN Email;
-- 重命名列(SQL Server)
EXEC sp_rename 'Students.Name', 'FullName', 'COLUMN';
2.5 删除 / 清空表
sql
-- 删除表结构 + 数据
DROP TABLE Students;
-- 清空表数据(保留结构,不可回滚,速度快)
TRUNCATE TABLE Students;
-- 清空表数据(可回滚,逐行删除)
DELETE FROM Students;
TRUNCATE与DELETE区别:TRUNCATE不记录逐行日志、不可回滚、不会触发触发器、会重置自增列;DELETE反之。
三、数据操纵(DML)
3.1 INSERT 插入
sql
-- 插入全部列
INSERT INTO Students VALUES (1, '张三', 'M', 20, '2004-01-01', 88.5, GETDATE());
-- 插入指定列
INSERT INTO Students (StudentID, Name, Gender) VALUES (2, '李四', 'F');
-- 一次插入多行
INSERT INTO Students (StudentID, Name, Gender, Age)
VALUES
(3, '王五', 'M', 21),
(4, '赵六', 'F', 22),
(5, '孙七', 'M', 19);
-- 从另一张表复制插入
INSERT INTO Students_backup (StudentID, Name)
SELECT StudentID, Name FROM Students WHERE Age > 20;
-- 插入并返回自增 ID(SQL Server)
INSERT INTO Students (Name) OUTPUT INSERTED.StudentID VALUES ('小明');
3.2 UPDATE 更新
sql
-- 更新单列
UPDATE Students SET Score = 90 WHERE StudentID = 1;
-- 更新多列
UPDATE Students SET Age = 21, Score = 95 WHERE StudentID = 1;
-- 通过子查询更新
UPDATE Students
SET Score = (SELECT AVG(Score) FROM Students)
WHERE StudentID = 2;
务必带上 WHERE,否则会更新全表!
3.3 DELETE 删除
sql
-- 删除满足条件的行
DELETE FROM Students WHERE StudentID = 5;
-- 删除所有行(保留结构)
DELETE FROM Students;
四、基础查询(SELECT)
4.1 基本语法
sql
-- 语法顺序(注意与执行顺序不同)
SELECT [DISTINCT] 列名列表
FROM 表名
[WHERE 条件]
[GROUP BY 分组列]
[HAVING 分组后条件]
[ORDER BY 排序列 [ASC|DESC]]
[LIMIT / TOP / OFFSET...];
逻辑执行顺序 (理解 SQL 的关键):
FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/TOP
4.2 查询列
sql
-- 查询所有列
SELECT * FROM Students;
-- 查询指定列
SELECT Name, Age FROM Students;
-- 列别名(AS 可省略)
SELECT Name AS 姓名, Age AS 年龄 FROM Students;
-- 计算列
SELECT Name, Score * 1.1 AS 加分后成绩 FROM Students;
-- 常量列
SELECT Name, '学生' AS 类型 FROM Students;
-- 去重
SELECT DISTINCT Gender FROM Students;
五、条件过滤与逻辑运算
5.1 比较运算符
= <>或!= > >= < <=
sql
SELECT * FROM Students WHERE Age >= 20;
SELECT * FROM Students WHERE Gender <> 'M';
5.2 逻辑运算符
AND OR NOT
sql
SELECT * FROM Students WHERE Age >= 20 AND Gender = 'M';
SELECT * FROM Students WHERE Age < 18 OR Age > 30;
SELECT * FROM Students WHERE NOT (Gender = 'M');
优先级:
NOT>AND>OR,必要时用括号明确。
5.3 范围 BETWEEN
sql
-- 等价于 Age >= 18 AND Age <= 25
SELECT * FROM Students WHERE Age BETWEEN 18 AND 25;
SELECT * FROM Students WHERE Birthday BETWEEN '2000-01-01' AND '2005-12-31';
5.4 集合 IN / NOT IN
sql
SELECT * FROM Students WHERE StudentID IN (1, 2, 3);
SELECT * FROM Students WHERE Gender NOT IN ('M');
5.5 模糊匹配 LIKE
| 通配符 | 含义 |
|---|---|
% |
任意长度字符(含 0 个) |
_ |
单个字符 |
[abc] |
方括号内任一字符 |
[^abc] / [!abc] |
不在方括号内的任意字符 |
sql
SELECT * FROM Students WHERE Name LIKE '张%'; -- 以"张"开头
SELECT * FROM Students WHERE Name LIKE '%三'; -- 以"三"结尾
SELECT * FROM Students WHERE Name LIKE '%小%'; -- 包含"小"
SELECT * FROM Students WHERE Name LIKE '王_'; -- "王"开头且共两字
SELECT * FROM Students WHERE Name LIKE '[张李]%'; -- 张或李开头
SELECT * FROM Students WHERE Name LIKE '[^张]%'; -- 非张开头
5.6 空值判断 IS NULL
sql
SELECT * FROM Students WHERE Email IS NULL;
SELECT * FROM Students WHERE Email IS NOT NULL;
NULL不能直接用=或<>比较,必须用IS NULL。NULL与任何值运算结果都是NULL。
5.7 EXISTS
sql
-- 存在相关记录则返回
SELECT * FROM Students s
WHERE EXISTS (SELECT 1 FROM Orders o WHERE o.StudentID = s.StudentID);
六、排序、分页与去重
6.1 排序 ORDER BY
sql
SELECT * FROM Students ORDER BY Age ASC; -- 升序(默认)
SELECT * FROM Students ORDER BY Age DESC; -- 降序
SELECT * FROM Students ORDER BY Gender, Age DESC; -- 多列排序
6.2 返回记录数 / 分页
sql
-- SQL Server:TOP
SELECT TOP 10 * FROM Students ORDER BY Age DESC;
-- MySQL / PostgreSQL:LIMIT
SELECT * FROM Students ORDER BY Age DESC LIMIT 10;
-- MySQL 分页:LIMIT 偏移, 数量
SELECT * FROM Students ORDER BY StudentID LIMIT 10, 10; -- 第11~20行
-- SQL Server 分页:OFFSET...FETCH(SQL Server 2012+)
SELECT * FROM Students ORDER BY StudentID
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
-- 通用分页:ROW_NUMBER() 窗口函数
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY StudentID) AS rn
FROM Students
) t WHERE rn BETWEEN 11 AND 20;
七、聚合函数与分组
7.1 聚合函数
| 函数 | 说明 |
|---|---|
COUNT(*) |
统计行数(含 NULL) |
COUNT(列) |
统计非空值个数 |
SUM(列) |
求和 |
AVG(列) |
平均值 |
MAX(列) / MIN(列) |
最大值 / 最小值 |
sql
SELECT COUNT(*) AS 总人数 FROM Students;
SELECT AVG(Score) AS 平均分 FROM Students;
SELECT MAX(Score) AS 最高分, MIN(Score) AS 最低分 FROM Students;
7.2 GROUP BY 分组
sql
-- 按性别统计人数
SELECT Gender, COUNT(*) AS 人数
FROM Students
GROUP BY Gender;
-- 按性别统计平均分
SELECT Gender, AVG(Score) AS 平均分
FROM Students
GROUP BY Gender;
规则:
SELECT中出现的非聚合列,必须出现在GROUP BY中。
7.3 HAVING 分组后过滤
HAVING 过滤分组后的结果(WHERE 过滤分组前的行)。HAVING 中可使用聚合函数。
sql
-- 查询平均分高于 85 的性别分组
SELECT Gender, AVG(Score) AS 平均分
FROM Students
GROUP BY Gender
HAVING AVG(Score) > 85;
WHERE与HAVING区别:WHERE作用于原始行、不能用聚合函数;HAVING作用于分组结果、可用聚合函数。
八、多表连接(JOIN)
8.1 连接类型
| 类型 | 说明 |
|---|---|
INNER JOIN |
内连接,只返回两表匹配的行 |
LEFT JOIN |
左外连接,返回左表全部 + 右表匹配(无匹配为 NULL) |
RIGHT JOIN |
右外连接,返回右表全部 + 左表匹配 |
FULL JOIN |
全外连接,返回两表所有(MySQL 不支持) |
CROSS JOIN |
交叉连接,笛卡尔积 |
示意图(左表 A、右表 B):
| 连接 | 结果集 |
|---|---|
| INNER | A ∩ B 的匹配行 |
| LEFT | A 全部 + 匹配的 B |
| RIGHT | B 全部 + 匹配的 A |
| FULL | A ∪ B 全部 |
| CROSS | A × B 全部组合 |
8.2 连接示例
假设有 Students(学生)和 Orders(订单)两张表,通过 StudentID 关联。
sql
-- 内连接(有订单的学生)
SELECT s.Name, o.OrderID, o.Amount
FROM Students s
INNER JOIN Orders o ON s.StudentID = o.StudentID;
-- 左连接(所有学生,含无订单的)
SELECT s.Name, o.OrderID
FROM Students s
LEFT JOIN Orders o ON s.StudentID = o.StudentID;
-- 右连接
SELECT s.Name, o.OrderID
FROM Students s
RIGHT JOIN Orders o ON s.StudentID = o.StudentID;
-- 全连接(SQL Server / PostgreSQL)
SELECT s.Name, o.OrderID
FROM Students s
FULL JOIN Orders o ON s.StudentID = o.StudentID;
-- 交叉连接(笛卡尔积)
SELECT * FROM Students CROSS JOIN Classes;
-- 多表连接
SELECT s.Name, c.ClassName, o.Amount
FROM Students s
INNER JOIN Classes c ON s.ClassID = c.ClassID
INNER JOIN Orders o ON s.StudentID = o.StudentID;
-- 自连接(同一张表关联,如员工与上级)
SELECT e1.Name AS 员工, e2.Name AS 上级
FROM Employees e1
LEFT JOIN Employees e2 ON e1.ManagerID = e2.EmployeeID;
8.3 传统写法(隐式连接)
sql
-- 等价于 INNER JOIN(不推荐,可读性差)
SELECT s.Name, o.OrderID
FROM Students s, Orders o
WHERE s.StudentID = o.StudentID;
九、子查询与集合运算
9.1 子查询
sql
-- 标量子查询(返回单个值)
SELECT Name FROM Students
WHERE Score = (SELECT MAX(Score) FROM Students);
-- 多行子查询(IN)
SELECT Name FROM Students
WHERE StudentID IN (SELECT StudentID FROM Orders);
-- EXISTS 相关子查询
SELECT s.Name
FROM Students s
WHERE EXISTS (SELECT 1 FROM Orders o WHERE o.StudentID = s.StudentID);
-- 派生表 / 子查询作为表
SELECT * FROM (
SELECT Gender, AVG(Score) AS avg_score FROM Students GROUP BY Gender
) t WHERE avg_score > 80;
9.2 集合运算
sql
-- UNION:合并并去重
SELECT Name FROM StudentsA
UNION
SELECT Name FROM StudentsB;
-- UNION ALL:合并不去重(性能更好)
SELECT Name FROM StudentsA
UNION ALL
SELECT Name FROM StudentsB;
-- INTERSECT:交集
SELECT StudentID FROM StudentsA
INTERSECT
SELECT StudentID FROM StudentsB;
-- EXCEPT / MINUS:差集(前者有而后者无)
SELECT StudentID FROM StudentsA
EXCEPT -- SQL Server / PostgreSQL
SELECT StudentID FROM StudentsB;
UNION去重、会排序,性能稍低;UNION ALL不去重、更快。
十、常用函数
10.1 数学函数
sql
ABS(-5) -- 5 绝对值
CEILING(3.2) -- 4 向上取整(MySQL 用 CEIL)
FLOOR(3.8) -- 3 向下取整
ROUND(3.14159, 2)-- 3.14 四舍五入
POWER(2, 10) -- 1024 幂
SQRT(16) -- 4 平方根
RAND() -- 0~1 随机数
10.2 聚合与空值处理
sql
-- 空值替换
ISNULL(Score, 0) -- SQL Server
COALESCE(Score, 0) -- 标准 SQL,返回第一个非空值
NVL(Score, 0) -- Oracle
IFNULL(Score, 0) -- MySQL
-- 条件判断(CASE)
SELECT Name,
CASE
WHEN Score >= 90 THEN '优秀'
WHEN Score >= 60 THEN '及格'
ELSE '不及格'
END AS 等级
FROM Students;
-- CASE 简单表达式
SELECT Name,
CASE Gender
WHEN 'M' THEN '男'
WHEN 'F' THEN '女'
ELSE '未知'
END AS 性别
FROM Students;
10.3 类型转换
sql
CAST(Score AS VARCHAR(10)) -- 标准
CONVERT(VARCHAR(10), Score) -- SQL Server
CAST('2024-01-01' AS DATE) -- 字符串转日期
CAST(StudentID AS VARCHAR) + Name -- 数值转字符串拼接
十一、字符串与日期处理
11.1 字符串函数
sql
-- 长度
LEN(Name) -- SQL Server
LENGTH(Name) / CHAR_LENGTH(Name) -- MySQL / PostgreSQL
-- 拼接
CONCAT('Hello', ' ', Name) -- 标准
'Hello ' + Name -- SQL Server 用 +
-- 大小写
UPPER(Name) / LOWER(Name)
-- 截取
SUBSTRING(Name, 1, 2) -- SQL Server:位置1起取2个字符(1-based)
SUBSTR(Name, 1, 2) -- MySQL / Oracle
-- 去除空格
LTRIM(Name) / RTRIM(Name) / TRIM(Name)
-- 替换
REPLACE(Name, '张', '李')
-- 查找位置
CHARINDEX('张', Name) -- SQL Server(返回1-based位置)
INSTR(Name, '张') -- MySQL / Oracle
LOCATE('张', Name) -- MySQL
-- 左右补位
LEFT(Name, 2) / RIGHT(Name, 2)
11.2 日期函数
sql
GETDATE() -- SQL Server:当前日期时间
GETDATE() -- MySQL:NOW() / CURRENT_TIMESTAMP
-- 提取部分
YEAR(Birthday) / MONTH(Birthday) / DAY(Birthday) -- SQL Server
DATEADD(YEAR, 1, Birthday) -- 加 1 年
DATEDIFF(DAY, Birthday, GETDATE()) -- 相差天数
-- MySQL 等价
DATE_ADD(Birthday, INTERVAL 1 YEAR)
DATEDIFF(NOW(), Birthday)
-- 格式化
CONVERT(VARCHAR(10), GETDATE(), 120) -- SQL Server:yyyy-mm-dd
DATE_FORMAT(Birthday, '%Y-%m-%d') -- MySQL
TO_CHAR(Birthday, 'YYYY-MM-DD') -- Oracle / PostgreSQL
十二、约束与完整性
12.1 约束类型
| 约束 | 说明 |
|---|---|
PRIMARY KEY |
主键,唯一 + 非空 |
FOREIGN KEY |
外键,参照完整性 |
UNIQUE |
唯一(允许一个 NULL) |
NOT NULL |
非空 |
CHECK |
检查约束 |
DEFAULT |
默认值 |
12.2 示例
sql
CREATE TABLE Orders (
OrderID INT PRIMARY KEY, -- 主键
StudentID INT NOT NULL,
Amount DECIMAL(10,2) CHECK (Amount > 0), -- 检查约束
Status NVARCHAR(20) DEFAULT '待处理', -- 默认值
CreateTime DATETIME DEFAULT GETDATE(),
-- 外键
CONSTRAINT FK_Orders_Students
FOREIGN KEY (StudentID) REFERENCES Students(StudentID),
-- 唯一约束
CONSTRAINT UQ_OrderNo UNIQUE (OrderID)
);
-- 表级主键 / 复合主键
CREATE TABLE Enrollment (
StudentID INT,
ClassID INT,
PRIMARY KEY (StudentID, ClassID) -- 复合主键
);
12.3 给已有表加约束
sql
ALTER TABLE Students ADD CONSTRAINT PK_Students PRIMARY KEY (StudentID);
ALTER TABLE Students ADD CONSTRAINT UQ_Email UNIQUE (Email);
ALTER TABLE Students ADD CONSTRAINT CK_Age CHECK (Age >= 0 AND Age <= 150);
ALTER TABLE Orders ADD CONSTRAINT FK_Orders_Students
FOREIGN KEY (StudentID) REFERENCES Students(StudentID);
-- 删除约束
ALTER TABLE Orders DROP CONSTRAINT FK_Orders_Students;
十三、索引
索引用于加速查询,如同书的目录。
13.1 索引类型
| 类型 | 说明 |
|---|---|
| 聚集索引(Clustered) | 每表只能一个,数据物理按索引顺序存储 |
| 非聚集索引(Non-clustered) | 可多个,指向数据的指针 |
| 唯一索引(Unique) | 值唯一 |
| 覆盖索引 / 组合索引 | 多列组成的索引 |
13.2 创建索引
sql
-- 普通索引
CREATE INDEX idx_students_name ON Students(Name);
-- 唯一索引
CREATE UNIQUE INDEX idx_students_email ON Students(Email);
-- 组合索引
CREATE INDEX idx_students_name_age ON Students(Name, Age);
-- 删除索引
DROP INDEX idx_students_name ON Students; -- SQL Server
DROP INDEX idx_students_name; -- MySQL
13.3 注意事项
- 经常出现在
WHERE、JOIN、ORDER BY中的列适合建索引。 - 索引过多会降低
INSERT/UPDATE/DELETE性能(需维护索引)。 - 区分度低的列(如 Gender)建索引收益小。
- 遵循「最左前缀」原则使用组合索引。
- 避免在索引列上使用函数或计算(
WHERE YEAR(Birthday)=2004无法走索引)。
十四、视图
视图(View)是虚拟表,封装查询逻辑,简化复杂查询、增强安全。
sql
-- 创建视图
CREATE VIEW v_学生成绩 AS
SELECT s.StudentID, s.Name, s.Gender, s.Score,
CASE WHEN s.Score >= 60 THEN '及格' ELSE '不及格' END AS 状态
FROM Students s;
-- 使用视图(如同查表)
SELECT * FROM v_学生成绩 WHERE Score > 80;
-- 修改视图
ALTER VIEW v_学生成绩 AS
SELECT StudentID, Name, Score FROM Students;
-- 删除视图
DROP VIEW v_学生成绩;
SQL Server 可对简单视图做 DML 操作;复杂视图(含 JOIN、聚合)默认不可更新。
十五、事务与锁
事务(Transaction)是原子性操作的集合,满足 ACID 特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
sql
BEGIN TRANSACTION; -- 开始事务(SQL Server)
-- START TRANSACTION; -- MySQL
UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1;
UPDATE Accounts SET Balance = Balance + 100 WHERE AccountID = 2;
IF @@ERROR <> 0 -- 判断是否有错误(SQL Server)
ROLLBACK TRANSACTION; -- 回滚
ELSE
COMMIT TRANSACTION; -- 提交
-- 保存点
BEGIN TRANSACTION;
UPDATE ... ;
SAVE TRANSACTION sp1; -- 设置保存点
UPDATE ... ;
ROLLBACK TRANSACTION sp1; -- 回滚到保存点
COMMIT;
MySQL 使用 COMMIT / ROLLBACK,并可用 SET autocommit = 0 关闭自动提交。
隔离级别
| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ✓ | ✓ | ✓ |
| READ COMMITTED | ✗ | ✓ | ✓ |
| REPEATABLE READ | ✗ | ✗ | ✓ |
| SERIALIZABLE | ✗ | ✗ | ✗ |
sql
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- SQL Server / MySQL
十六、存储过程与函数
16.1 存储过程
存储过程是预编译的 SQL 代码块,可接收参数、返回结果。
sql
-- 创建存储过程(SQL Server)
CREATE PROCEDURE usp_GetStudentsByAge
@minAge TINYINT,
@maxAge TINYINT
AS
BEGIN
SELECT * FROM Students
WHERE Age BETWEEN @minAge AND @maxAge;
END;
-- 调用
EXEC usp_GetStudentsByAge 18, 25;
-- 带输出参数
CREATE PROCEDURE usp_GetCount
@gender CHAR(1),
@total INT OUTPUT
AS
BEGIN
SELECT @total = COUNT(*) FROM Students WHERE Gender = @gender;
END;
DECLARE @cnt INT;
EXEC usp_GetCount 'M', @cnt OUTPUT;
SELECT @cnt AS 男生人数;
-- 删除
DROP PROCEDURE usp_GetStudentsByAge;
MySQL 存储过程语法:
sql
DELIMITER $$
CREATE PROCEDURE usp_GetByAge(IN minAge INT, IN maxAge INT)
BEGIN
SELECT * FROM Students WHERE Age BETWEEN minAge AND maxAge;
END $$
DELIMITER ;
CALL usp_GetByAge(18, 25);
16.2 用户自定义函数(UDF)
sql
-- 标量函数
CREATE FUNCTION fn_GetLevel(@score DECIMAL(5,2))
RETURNS NVARCHAR(10)
AS
BEGIN
DECLARE @level NVARCHAR(10);
SET @level = CASE
WHEN @score >= 90 THEN '优秀'
WHEN @score >= 60 THEN '及格'
ELSE '不及格' END;
RETURN @level;
END;
-- 调用(需带架构名)
SELECT Name, dbo.fn_GetLevel(Score) AS 等级 FROM Students;
-- 表值函数
CREATE FUNCTION fn_StudentsByGender(@gender CHAR(1))
RETURNS TABLE
AS
RETURN (SELECT * FROM Students WHERE Gender = @gender);
SELECT * FROM dbo.fn_StudentsByGender('M');
函数中不能有
INSERT/UPDATE/DELETE(修改数据的操作),存储过程中可以。
十七、触发器
触发器在表发生 INSERT/UPDATE/DELETE 时自动执行。SQL Server 用 inserted / deleted 虚拟表,MySQL 用 NEW / OLD。
sql
-- SQL Server:审计日志触发器
CREATE TABLE Students_Audit (
AuditID INT IDENTITY PRIMARY KEY,
StudentID INT,
Action NVARCHAR(10),
ActionTime DATETIME DEFAULT GETDATE()
);
CREATE TRIGGER trg_Students_Insert
ON Students
AFTER INSERT
AS
BEGIN
INSERT INTO Students_Audit (StudentID, Action)
SELECT StudentID, 'INSERT' FROM inserted;
END;
-- UPDATE 触发器
CREATE TRIGGER trg_Students_Update
ON Students
AFTER UPDATE
AS
BEGIN
INSERT INTO Students_Audit (StudentID, Action)
SELECT StudentID, 'UPDATE' FROM inserted;
END;
-- 删除触发器
DROP TRIGGER trg_Students_Insert;
MySQL 触发器:
sql
CREATE TRIGGER trg_Students_insert
AFTER INSERT ON Students
FOR EACH ROW
BEGIN
INSERT INTO audit_log VALUES (NEW.StudentID, 'INSERT', NOW());
END;
触发器会带来隐式副作用、难以调试,应谨慎使用,能用约束/应用层逻辑替代时优先替代。
十八、游标
游标用于逐行处理结果集(一般能用集合操作就不要用游标,性能差)。
sql
-- SQL Server 游标
DECLARE @id INT, @name NVARCHAR(50);
DECLARE cur CURSOR FOR SELECT StudentID, Name FROM Students WHERE Age > 20;
OPEN cur;
FETCH NEXT FROM cur INTO @id, @name;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT '处理学生:' + CAST(@id AS VARCHAR) + ' ' + @name;
FETCH NEXT FROM cur INTO @id, @name;
END;
CLOSE cur;
DEALLOCATE cur;
十九、动态 SQL
动态 SQL 在运行时拼接并执行 SQL 字符串,常用于参数可变的查询、动态建表等。
sql
-- SQL Server:EXEC
DECLARE @sql NVARCHAR(MAX);
DECLARE @tableName NVARCHAR(50) = 'Students';
SET @sql = 'SELECT * FROM ' + @tableName;
EXEC(@sql);
-- SQL Server:sp_executesql(支持参数化,防注入)
DECLARE @sql NVARCHAR(MAX);
SET @sql = 'SELECT * FROM Students WHERE Age >= @age';
EXEC sp_executesql @sql, N'@age INT', @age = 20;
-- MySQL:PREPARE
SET @sql = 'SELECT * FROM Students WHERE Age >= ?';
PREPARE stmt FROM @sql;
SET @age = 20;
EXECUTE stmt USING @age;
DEALLOCATE PREPARE stmt;
务必防范 SQL 注入 :优先使用参数化查询(
sp_executesql/ 预编译语句),不要直接拼接用户输入。
二十、最佳实践与常见坑
20.1 最佳实践
-
禁止
SELECT *,明确列出所需列。 -
UPDATE/DELETE 必须带 WHERE ,先
SELECT验证再操作。 -
使用参数化查询,防范 SQL 注入。
-
合理使用索引,避免全表扫描。
-
用
EXPLAIN/ 执行计划分析慢查询 :sqlEXPLAIN SELECT * FROM Students WHERE Age > 20; -- MySQL -- SQL Server:SET STATISTICS IO ON / 查看执行计划 -
事务尽量短小,避免长事务锁表。
-
命名规范:表名、列名语义化,统一大小写/下划线风格。
20.2 常见坑
NULL陷阱 :WHERE Score = NULL永远查不到,要用IS NULL;NULL参与聚合会被忽略;COUNT(列)不计 NULL,COUNT(*)计。=简单比较忽略了 NULL :NOT IN子查询若含 NULL 可能返回空结果。- 隐式类型转换 :字符串比较数字可能导致索引失效(如
WHERE Phone = 13800138000当 Phone 是字符串)。 BETWEEN含边界 :BETWEEN 1 AND 10包含 1 和 10。OFFSET/FETCH必须先ORDER BY。GROUP BY后不能用WHERE过滤聚合结果 ,要用HAVING。LIKE '%xx%'无法走索引(前导通配符)。- 自增列被
DELETE不会回补 ,TRUNCATE会重置。 - 表别名一旦使用,全程必须用别名 :
SELECT s.Name FROM Students s WHERE Students.Age > 20会报错。
附:SQL 关键字速查表
| 关键字 | 用途 |
|---|---|
SELECT |
查询数据 |
FROM |
指定来源表 |
WHERE |
行级过滤 |
GROUP BY |
分组 |
HAVING |
分组过滤 |
ORDER BY |
排序 |
LIMIT / TOP / OFFSET |
限制/分页 |
JOIN |
连接表 |
UNION |
合并结果集 |
INSERT / UPDATE / DELETE |
增删改 |
CREATE / ALTER / DROP |
结构定义 |
DISTINCT |
去重 |
AS |
别名 |
CASE WHEN |
条件表达式 |
BEGIN / COMMIT / ROLLBACK |
事务控制 |
GRANT / REVOKE |
权限 |