SQL语法基础教程
本文档详细讲解SQL的基础语法和概念。
一、SQL语句分类
1. DDL - 数据定义语言(Data Definition Language)
作用:定义和管理数据库对象(表、视图、索引等)
| 命令 | 说明 | 示例 |
|---|---|---|
| CREATE | 创建 | CREATE TABLE 表名 (...) |
| ALTER | 修改 | ALTER TABLE 表名 ADD (列名 类型) |
| DROP | 删除 | DROP TABLE 表名 |
| TRUNCATE | 清空 | TRUNCATE TABLE 表名 |
特点:
DDL操作会自动提交(COMMIT)
不能回滚(ROLLBACK)
修改表结构时要特别小心
2. DML - 数据操纵语言(Data Manipulation Language)
作用:操作表中的数据
| 命令 | 说明 | 示例 |
|---|---|---|
| SELECT | 查询 | SELECT * FROM 表名 |
| INSERT | 插入 | INSERT INTO 表名 VALUES (...) |
| UPDATE | 更新 | UPDATE 表名 SET 列=值 WHERE 条件 |
| DELETE | 删除 | DELETE FROM 表名 WHERE 条件 |
特点:
DML操作需要手动COMMIT才能生效
可以ROLLBACK撤销
本实验主要是DML中的SELECT查询
3. DCL - 数据控制语言(Data Control Language)
作用:控制用户权限
| 命令 | 说明 | 示例 |
|---|---|---|
| GRANT | 授权 | GRANT SELECT ON 表名 TO 用户 |
| REVOKE | 撤销 | REVOKE SELECT ON 表名 FROM 用户 |
二、SELECT语句完整语法
基本结构
SELECT [DISTINCT] 列名
FROM 表名
[WHERE 条件]
[GROUP BY 列名]
[HAVING 条件]
[ORDER BY 列名 [ASC|DESC]]执行顺序(重要!)
1. FROM --> 确定查询哪个表
2. WHERE --> 过滤行(分组前)
3. GROUP BY --> 分组
4. HAVING --> 过滤组(分组后)
5. SELECT --> 选择显示的列
6. ORDER BY --> 排序
7. DISTINCT --> 去重为什么顺序重要?
WHERE在GROUP BY之前,所以不能使用聚合函数
HAVING在GROUP BY之后,可以使用聚合函数
SELECT的别名在ORDER BY中可用,但在WHERE中不可用
示例理解:
SELECT 出版单位, AVG(单价) AS 平均单价
FROM 书目
WHERE 单价 > 20 -- ✅ 可以:WHERE在SELECT前执行
-- WHERE 平均单价 > 30 -- ❌ 错误:平均单价别名还不存在
GROUP BY 出版单位
HAVING AVG(单价) > 30 -- ✅ 可以:HAVING在GROUP BY后
ORDER BY 平均单价 DESC; -- ✅ 可以:ORDER BY在SELECT后,别名已存在三、基础语法详解
1. SELECT - 选择列
-- 选择所有列
SELECT * FROM 书目;
-- 选择指定列
SELECT 书名, 作者 FROM 书目;
-- 使用别名
SELECT 书名 AS 图书名称, 作者 AS 作者姓名 FROM 书目;
-- AS可以省略
SELECT 书名 图书名称, 作者 作者姓名 FROM 书目;
-- 计算列
SELECT 书名, 单价, 单价 * 0.8 AS 折扣价 FROM 书目;DISTINCT去重:
-- 查询所有不同的出版单位
SELECT DISTINCT 出版单位 FROM 书目;
-- 查询所有不同的(出版单位, 图书分类号)组合
SELECT DISTINCT 出版单位, 图书分类号 FROM 书目;2. FROM - 指定表
-- 单表
FROM 书目
-- 表别名
FROM 书目 s -- s是别名
FROM 书目 AS s -- AS可加可不加为什么要用表别名?
简化书写:
s.书名比书目.书名简洁多表查询时必需:区分同名列
自连接时必需:同一个表要用不同别名
3. WHERE - 过滤行
基本比较运算符:
| 运算符 | 说明 | 示例 |
|---|---|---|
| = | 等于 | WHERE 单价 = 30 |
| != 或 <> | 不等于 | WHERE 单价 <> 30 |
| > | 大于 | WHERE 单价 > 30 |
| >= | 大于等于 | WHERE 单价 >= 30 |
| < | 小于 | WHERE 单价 < 30 |
| <= | 小于等于 | WHERE 单价 <= 30 |
逻辑运算符:
| 运算符 | 说明 | 示例 |
|---|---|---|
| AND | 且(都要满足) | WHERE 单价 > 20 AND 单价 < 40 |
| OR | 或(满足一个即可) | WHERE 出版单位='人民出版社' OR 出版单位='作家出版社' |
| NOT | 非(取反) | WHERE NOT 出版单位='人民出版社' |
范围查询:
-- BETWEEN...AND... (包含边界值)
WHERE 单价 BETWEEN 20 AND 40
-- 等价于
WHERE 单价 >= 20 AND 单价 <= 40
-- IN (...) 在列表中
WHERE 出版单位 IN ('人民出版社', '作家出版社')
-- 等价于
WHERE 出版单位='人民出版社' OR 出版单位='作家出版社'模糊查询:
-- LIKE 模糊匹配
-- % : 匹配任意长度的任意字符(包括0个)
-- _ : 匹配单个任意字符
WHERE 书名 LIKE '红%' -- 以"红"开头
WHERE 书名 LIKE '%梦' -- 以"梦"结尾
WHERE 书名 LIKE '%数据库%' -- 包含"数据库"
WHERE 书名 LIKE '红_梦' -- "红"和"梦"之间有一个字符
WHERE 书名 LIKE '__' -- 恰好两个字符的书名NULL值判断:
-- 判断是否为NULL
WHERE 归还日期 IS NULL -- ✅ 正确
WHERE 归还日期 = NULL -- ❌ 错误(永远为FALSE)-- 判断是否不为NULL
WHERE 归还日期 IS NOT NULL -- ✅ 正确
WHERE 归还日期 != NULL -- ❌ 错误(永远为FALSE)为什么不能用 = NULL?
NULL表示"未知"
"未知"不等于任何值,包括"未知"本身
NULL = NULL 的结果是NULL(不是TRUE也不是FALSE)
只有IS NULL能正确判断NULL
4. ORDER BY - 排序
-- 单列排序
ORDER BY 单价 ASC -- 升序(从小到大),ASC可省略
ORDER BY 单价 DESC -- 降序(从大到小)
ORDER BY 单价 -- 默认升序-- 多列排序
ORDER BY 单价 DESC, 书名 ASC
-- 先按单价降序,单价相同再按书名升序-- 使用列的位置(不推荐,可读性差)
SELECT 书名, 单价 FROM 书目
ORDER BY 2 DESC; -- 按第2列(单价)降序-- 使用别名
SELECT 书名, 单价 AS 价格 FROM 书目
ORDER BY 价格 DESC; -- 使用别名排序NULL值的排序:
ORDER BY 归还日期 -- NULL值排在最前面
ORDER BY 归还日期 NULLS LAST -- NULL值排在最后面5. 聚合函数
五大聚合函数:
-- COUNT(*) : 统计行数(包括NULL)
SELECT COUNT(*) FROM 借阅; -- 总借阅次数-- COUNT(列) : 统计非NULL值的数量
SELECT COUNT(归还日期) FROM 借阅; -- 已归还的次数-- SUM(列) : 求和
SELECT SUM(单价) FROM 书目; -- 所有书的单价总和-- AVG(列) : 平均值
SELECT AVG(单价) FROM 书目; -- 平均单价-- MAX(列) : 最大值
SELECT MAX(单价) FROM 书目; -- 最贵的书价格-- MIN(列) : 最小值
SELECT MIN(单价) FROM 书目; -- 最便宜的书价格COUNT(*)和COUNT(列)的区别:
-- 假设表中有5行,其中2行的归还日期为NULLSELECT COUNT(*) FROM 借阅; -- 结果:5(包括NULL)
SELECT COUNT(归还日期) FROM 借阅; -- 结果:3(只统计非NULL)
SELECT COUNT(DISTINCT 借书证号) FROM 借阅; -- 统计不同的借书证号数量注意事项:
聚合函数忽略NULL值(COUNT(*)除外)
聚合函数不能用在WHERE中(要用HAVING)
SELECT中有聚合函数时,其他列要么也是聚合函数,要么在GROUP BY中
6. GROUP BY - 分组
基本语法:
SELECT 分组列, 聚合函数(列)
FROM 表名
GROUP BY 分组列;示例:
-- 统计每个出版社的书目数量
SELECT 出版单位, COUNT(*) AS 书目数量
FROM 书目
GROUP BY 出版单位;-- 统计每个出版社的书目数量和平均单价
SELECT 出版单位, COUNT(*) AS 书目数量,AVG(单价) AS 平均单价
FROM 书目
GROUP BY 出版单位;GROUP BY的规则:
规则1:SELECT中非聚合列必须在GROUP BY中
-- ❌ 错误:书名不在GROUP BY中
SELECT 出版单位, 书名, COUNT(*)
FROM 书目
GROUP BY 出版单位;-- ✅ 正确:书名加入GROUP BY
SELECT 出版单位, 书名, COUNT(*)
FROM 书目
GROUP BY 出版单位, 书名;为什么?
GROUP BY将数据分组,每组返回一行
如果书名不在GROUP BY中,数据库不知道该显示该组的哪个书名
规则2:GROUP BY中的列可以不在SELECT中
-- ✅ 正确:按出版单位分组,但不显示出版单位
SELECT COUNT(*) AS 总数量
FROM 书目
GROUP BY 出版单位;7. HAVING - 过滤分组
HAVING vs WHERE:
| 特性 | WHERE | HAVING |
|---|---|---|
| 作用对象 | 行(原始数据) | 组(分组后的结果) |
| 执行时机 | GROUP BY之前 | GROUP BY之后 |
| 能否使用聚合函数 | 否 | 是 |
示例对比:
-- 查询单价>20的书,按出版社分组,显示平均单价>30的出版社SELECT 出版单位, AVG(单价) AS 平均单价
FROM 书目
WHERE 单价 > 20 -- WHERE: 过滤行(分组前)
GROUP BY 出版单位
HAVING AVG(单价) > 30 -- HAVING: 过滤组(分组后)
ORDER BY 平均单价 DESC;-- 执行流程:
-- 1. FROM 书目
-- 2. WHERE 单价 > 20 (先过滤掉单价<=20的书)
-- 3. GROUP BY 出版单位 (对剩余的书按出版社分组)
-- 4. HAVING AVG(单价) > 30 (过滤掉平均单价<=30的组)
-- 5. SELECT
-- 6. ORDER BY常见错误:
-- ❌ 错误:WHERE中不能用聚合函数
SELECT 出版单位, AVG(单价)
FROM 书目
WHERE AVG(单价) > 30 -- 错误!WHERE不能用AVG
GROUP BY 出版单位;-- ✅ 正确:应该用HAVING
SELECT 出版单位, AVG(单价)
FROM 书目
GROUP BY 出版单位
HAVING AVG(单价) > 30;四、JOIN连接查询
1. 内连接(INNER JOIN)
语法:
SELECT 列名
FROM 表1
INNER JOIN 表2 ON 连接条件;
-- INNER可以省略,直接写JOIN特点:
只返回两表都有匹配的记录
相当于两表的交集
示例:
-- 查询借阅信息(只显示有借阅记录的读者)
SELECT d.姓名, s.书名, j.借书日期
FROM 借阅 j
INNER JOIN 读者 d ON j.借书证号 = d.借书证号
INNER JOIN 图书 t ON j.图书编号 = t.图书编号
INNER JOIN 书目 s ON t.ISBN = s.ISBN;2. 左外连接(LEFT JOIN)
语法:
SELECT 列名
FROM 表1
LEFT JOIN 表2 ON 连接条件;特点:
返回左表所有记录
右表没有匹配时显示NULL
示例:
-- 查询所有读者的借阅次数(包括没借过书的)
SELECT d.姓名,COUNT(j.借阅流水号) AS 借阅次数
FROM 读者 d
LEFT JOIN 借阅 j ON d.借书证号 = j.借书证号
GROUP BY d.姓名;-- 结果会包括借阅次数为0的读者LEFT JOIN vs INNER JOIN:
-- INNER JOIN:只显示有借阅记录的读者
FROM 读者 d
INNER JOIN 借阅 j ON d.借书证号 = j.借书证号
-- 结果:只有借过书的读者-- LEFT JOIN:显示所有读者
FROM 读者 d
LEFT JOIN 借阅 j ON d.借书证号 = j.借书证号
-- 结果:所有读者,没借过书的借阅次数显示为0或NULL3. 右外连接(RIGHT JOIN)
语法:
SELECT 列名
FROM 表1
RIGHT JOIN 表2 ON 连接条件;特点:
返回右表所有记录
左表没有匹配时显示NULL
不常用(可以调换表顺序用LEFT JOIN替代)
4. 全外连接(FULL JOIN)
语法:
SELECT 列名
FROM 表1
FULL JOIN 表2 ON 连接条件;特点:
返回两表所有记录
相当于LEFT JOIN + RIGHT JOIN
Oracle支持,MySQL不支持
五、子查询
1. 标量子查询(返回单个值)
-- 查询单价最高的图书
SELECT * FROM 书目
WHERE 单价 = (SELECT MAX(单价) FROM 书目);-- 子查询返回一个值:最高单价2. 列子查询(返回一列值)
-- 查询借过书的读者
SELECT * FROM 读者
WHERE 借书证号 IN (SELECT DISTINCT 借书证号 FROM 借阅);-- 子查询返回一列值:所有借过书的借书证号IN vs EXISTS:
-- 使用IN
WHERE 借书证号 IN (SELECT 借书证号 FROM 借阅)-- 使用EXISTS(性能通常更好)
WHERE EXISTS (SELECT 1 FROM 借阅 j WHERE j.借书证号 = d.借书证号)3. 表子查询(返回一个表)
-- 查询借阅次数>2的读者
SELECT * FROM (SELECT 借书证号, COUNT(*) AS 借阅次数FROM 借阅GROUP BY 借书证号
) WHERE 借阅次数 > 2;-- 子查询返回一个临时表六、常用函数
1. 字符串函数
-- 连接字符串
SELECT 书名 || '(' || 作者 || ')' AS 完整信息 FROM 书目;-- 转换大小写
SELECT UPPER(书名), LOWER(书名) FROM 书目;-- 截取字符串
SELECT SUBSTR(书名, 1, 2) FROM 书目; -- 截取前2个字符-- 字符串长度
SELECT LENGTH(书名) FROM 书目;2. 数值函数
-- 四舍五入
SELECT ROUND(单价, 1) FROM 书目; -- 保留1位小数-- 向上取整
SELECT CEIL(单价) FROM 书目;-- 向下取整
SELECT FLOOR(单价) FROM 书目;-- 截断小数
SELECT TRUNC(单价, 1) FROM 书目; -- 保留1位小数,不四舍五入3. 日期函数
-- 当前日期时间
SELECT SYSDATE FROM DUAL;-- 日期相减(结果是天数)
SELECT SYSDATE - 借书日期 AS 已借天数 FROM 借阅;-- 日期加减天数
SELECT 借书日期 + 30 AS 应还日期 FROM 借阅;-- 加减月份
SELECT ADD_MONTHS(借书日期, 1) AS 一个月后 FROM 借阅;-- 提取年月日
SELECT EXTRACT(YEAR FROM 借书日期) AS 年份,EXTRACT(MONTH FROM 借书日期) AS 月份,EXTRACT(DAY FROM 借书日期) AS 日期
FROM 借阅;-- 日期格式化
SELECT TO_CHAR(借书日期, 'YYYY-MM-DD') AS 格式化日期 FROM 借阅;4. NULL处理函数
-- NVL(值1, 值2) : 如果值1为NULL,返回值2
SELECT NVL(罚金, 0) AS 罚金 FROM 罚款分类;-- NVL2(值1, 值2, 值3) : 如果值1非NULL返回值2,否则返回值3
SELECT NVL2(归还日期, '已还', '未还') AS 状态 FROM 借阅;-- COALESCE(值1, 值2, 值3, ...) : 返回第一个非NULL值
SELECT COALESCE(归还日期, 借书日期, SYSDATE) FROM 借阅;5. CASE表达式
-- 简单CASE
SELECT 书名,CASE 是否借出WHEN '是' THEN '已借出'WHEN '否' THEN '可借'ELSE '未知'END AS 状态
FROM 图书;-- 搜索CASE(更常用)
SELECT 书名,单价,CASE WHEN 单价 < 20 THEN '低价'WHEN 单价 < 40 THEN '中价'ELSE '高价'END AS 价格等级
FROM 书目;七、常见错误及解决
错误1:SELECT的列不在GROUP BY中
-- ❌ 错误
SELECT 出版单位, 书名, COUNT(*)
FROM 书目
GROUP BY 出版单位;-- 错误信息:ORA-00979: not a GROUP BY expression-- ✅ 解决:把书名加入GROUP BY
SELECT 出版单位, 书名, COUNT(*)
FROM 书目
GROUP BY 出版单位, 书名;错误2:WHERE中使用聚合函数
-- ❌ 错误
SELECT 出版单位, AVG(单价)
FROM 书目
WHERE AVG(单价) > 30
GROUP BY 出版单位;-- 错误信息:ORA-00934: group function is not allowed here-- ✅ 解决:用HAVING代替WHERE
SELECT 出版单位, AVG(单价)
FROM 书目
GROUP BY 出版单位
HAVING AVG(单价) > 30;错误3:NULL值判断错误
-- ❌ 错误
SELECT * FROM 借阅
WHERE 归还日期 = NULL;-- 查不到任何结果(因为NULL=NULL的结果是NULL,不是TRUE)-- ✅ 解决:用IS NULL
SELECT * FROM 借阅
WHERE 归还日期 IS NULL;错误4:字符串忘记加引号
-- ❌ 错误
SELECT * FROM 书目
WHERE 书名 = 红楼梦;-- 错误信息:ORA-00904: "红楼梦": invalid identifier-- ✅ 解决:字符串要用单引号
SELECT * FROM 书目
WHERE 书名 = '红楼梦';错误5:日期格式错误
-- ❌ 错误
INSERT INTO 借阅 VALUES (1, '20051001', '2001231', '2010-09-19', NULL, NULL, NULL);-- 可能报错或插入错误的日期-- ✅ 解决:使用TO_DATE函数
INSERT INTO 借阅 VALUES (1, '20051001', '2001231', TO_DATE('2010-09-19', 'YYYY-MM-DD'), NULL, NULL, NULL
);八、学习建议
1. 从简单到复杂
第一步:单表查询(SELECT、WHERE、ORDER BY)
第二步:聚合统计(COUNT、SUM、AVG、GROUP BY)
第三步:多表连接(JOIN)
第四步:子查询
第五步:复杂综合查询
2. 多练习、多思考
不要只看代码,要动手写
理解为什么这么写
尝试用不同方法解决同一个问题
3. 理解执行流程
记住SELECT语句的执行顺序
理解每个子句的作用
知道为什么有些语法不能用
4. 善用注释
在复杂查询中添加注释
说明查询的目的和逻辑
方便日后维护
九、快速参考
SQL关键字速查
| 关键字 | 作用 | 位置 |
|---|---|---|
| SELECT | 选择列 | 开头 |
| FROM | 指定表 | SELECT后 |
| WHERE | 过滤行 | FROM后 |
| GROUP BY | 分组 | WHERE后 |
| HAVING | 过滤组 | GROUP BY后 |
| ORDER BY | 排序 | 最后 |
| JOIN | 连接表 | FROM中 |
| AND/OR | 逻辑运算 | WHERE/HAVING中 |
| IN | 在列表中 | WHERE中 |
| LIKE | 模糊查询 | WHERE中 |
| IS NULL | 判断NULL | WHERE中 |
运算符优先级
括号
()比较运算
=, !=, >, <, >=, <=NOT
AND
OR
建议:使用括号明确优先级,提高可读性
希望这份教程能帮助你更好地理解SQL语法!继续加油!💪
