SQL 语法基础
问题
SQL 的基本语法有哪些?SELECT、INSERT、UPDATE、DELETE 怎么用?
答案
SQL(Structured Query Language,结构化查询语言) 是操作关系型数据库的标准语言。不管你用的是 MySQL、PostgreSQL 还是 Oracle,SQL 的核心语法都是通用的。
SQL 就像是你和数据库之间的"对话语言"——你用 SQL 告诉数据库:我要什么数据、我要改什么数据、我要删什么数据。
SQL 的四大类型
| 类型 | 全称 | 作用 | 常用语句 |
|---|---|---|---|
| DDL | Data Definition Language | 定义数据库结构 | CREATE、ALTER、DROP、TRUNCATE |
| DML | Data Manipulation Language | 操作数据 | INSERT、UPDATE、DELETE |
| DQL | Data Query Language | 查询数据 | SELECT |
| DCL | Data Control Language | 权限控制 | GRANT、REVOKE |
DDL:定义数据库和表
创建数据库和表
-- 创建数据库
CREATE DATABASE my_shop;
-- 使用数据库
USE my_shop;
-- 创建表
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自增
name VARCHAR(100) NOT NULL, -- 商品名,不能为空
price DECIMAL(10, 2) DEFAULT 0.00, -- 价格,默认 0
stock INT DEFAULT 0, -- 库存
category VARCHAR(50), -- 分类
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 创建时间
);
修改表结构
-- 添加列
ALTER TABLE products ADD COLUMN description TEXT;
-- 修改列的类型
ALTER TABLE products MODIFY COLUMN name VARCHAR(200);
-- 删除列
ALTER TABLE products DROP COLUMN description;
-- 重命名列(MySQL 8.0+)
ALTER TABLE products RENAME COLUMN stock TO quantity;
删除表
-- 删除表(连表结构一起删除,不可恢复!)
DROP TABLE products;
-- 清空表数据(保留表结构,速度比 DELETE 快)
TRUNCATE TABLE products;
DROP vs TRUNCATE vs DELETE
DROP TABLE:删除表 + 数据,整张表消失TRUNCATE TABLE:清空所有数据,保留表结构,自增 ID 重置,不可回滚DELETE FROM table:逐行删除数据,可以加 WHERE 条件,可回滚
DML:增删改数据
INSERT 插入数据
-- 插入单条数据(指定所有列)
INSERT INTO products (name, price, stock, category)
VALUES ('iPhone 15', 5999.00, 100, '手机');
-- 插入单条数据(省略列名,必须按列顺序提供所有值)
INSERT INTO products
VALUES (NULL, 'MacBook Pro', 12999.00, 50, '电脑', NOW());
-- 批量插入(性能比逐条插入好很多)
INSERT INTO products (name, price, stock, category) VALUES
('AirPods Pro', 1899.00, 200, '配件'),
('iPad Air', 4599.00, 80, '平板'),
('Apple Watch', 2999.00, 150, '手表');
批量插入性能提示
一次 INSERT 插入多行,比单条循环 INSERT 快 10 倍以上。原因是每条 INSERT 都需要一次网络往返 + 一次事务提交,批量插入只需要一次。
UPDATE 更新数据
-- 更新单条数据
UPDATE products SET price = 5499.00 WHERE id = 1;
-- 更新多个字段
UPDATE products SET price = 5499.00, stock = 150 WHERE id = 1;
-- 基于条件批量更新
UPDATE products SET stock = stock - 1 WHERE category = '手机' AND stock > 0;
UPDATE 必须加 WHERE!
忘记 WHERE 会更新所有行,这是生产环境最常见的"事故"之一:
-- ❌ 危险操作!会把所有商品的价格都改为 0
UPDATE products SET price = 0;
-- ✅ 安全操作:只修改指定记录
UPDATE products SET price = 0 WHERE id = 1;
DELETE 删除数据
-- 删除单条数据
DELETE FROM products WHERE id = 1;
-- 按条件删除
DELETE FROM products WHERE stock = 0 AND category = '手机';
-- ❌ 危险!删除所有数据
DELETE FROM products;
DQL:查询数据(最重要)
SELECT 是 SQL 用得最多的语句。一条完整的 SELECT 语句由多个子句组成:
SELECT [DISTINCT] 列名 -- 1. 选择哪些列
FROM 表名 -- 2. 从哪张表
[JOIN 表名 ON 条件] -- 3. 关联其他表
[WHERE 条件] -- 4. 过滤行
[GROUP BY 列名] -- 5. 分组
[HAVING 条件] -- 6. 过滤分组
[ORDER BY 列名 [ASC|DESC]] -- 7. 排序
[LIMIT 数量 OFFSET 偏移]; -- 8. 分页
SQL 执行顺序
关键知识点
SQL 的书写顺序和执行顺序不一样!这是面试高频考点。
这意味着:
WHERE不能使用SELECT中定义的别名(因为 WHERE 先执行)HAVING可以使用聚合函数(因为 GROUP BY 已经执行完了)ORDER BY可以使用SELECT中的别名
基础查询示例
-- 查询所有列
SELECT * FROM products;
-- 查询指定列
SELECT name, price FROM products;
-- 使用别名
SELECT name AS 商品名, price AS 价格 FROM products;
-- 去重
SELECT DISTINCT category FROM products;
-- 条件查询
SELECT * FROM products WHERE price > 3000;
-- 多条件(AND、OR)
SELECT * FROM products WHERE price > 3000 AND category = '手机';
SELECT * FROM products WHERE category = '手机' OR category = '平板';
-- IN 查询(等价于多个 OR)
SELECT * FROM products WHERE category IN ('手机', '平板', '电脑');
-- BETWEEN 范围查询
SELECT * FROM products WHERE price BETWEEN 2000 AND 5000;
-- LIKE 模糊查询
SELECT * FROM products WHERE name LIKE '%Apple%'; -- 包含 Apple
SELECT * FROM products WHERE name LIKE 'i%'; -- 以 i 开头
-- NULL 判断(不能用 = NULL)
SELECT * FROM products WHERE category IS NULL;
SELECT * FROM products WHERE category IS NOT NULL;
排序与分页
-- 单列排序(默认升序 ASC)
SELECT * FROM products ORDER BY price;
-- 降序
SELECT * FROM products ORDER BY price DESC;
-- 多列排序:先按分类升序,再按价格降序
SELECT * FROM products ORDER BY category ASC, price DESC;
-- 分页:取第 2 页(每页 10 条)
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 10;
-- 简写形式(MySQL):LIMIT 偏移, 数量
SELECT * FROM products ORDER BY id LIMIT 10, 10;
聚合函数与分组
聚合函数对一组行计算出单个结果值:
| 函数 | 说明 | 示例 |
|---|---|---|
COUNT() | 计数 | COUNT(*) 所有行数 |
SUM() | 求和 | SUM(price) 价格总和 |
AVG() | 平均值 | AVG(price) 平均价格 |
MAX() | 最大值 | MAX(price) 最高价 |
MIN() | 最小值 | MIN(price) 最低价 |
-- 总商品数
SELECT COUNT(*) AS total FROM products;
-- 每个分类的商品数量和平均价格
SELECT
category,
COUNT(*) AS count,
AVG(price) AS avg_price,
MAX(price) AS max_price
FROM products
GROUP BY category;
-- HAVING:过滤分组结果(WHERE 只能过滤行,不能过滤组)
SELECT
category,
COUNT(*) AS count
FROM products
GROUP BY category
HAVING count > 5; -- 只要商品数 > 5 的分类
WHERE vs HAVING
WHERE:在分组之前过滤行,不能使用聚合函数HAVING:在分组之后过滤组,可以使用聚合函数
-- ✅ WHERE 过滤行:价格 > 1000 的商品中,每个分类的数量
SELECT category, COUNT(*)
FROM products
WHERE price > 1000
GROUP BY category;
-- ✅ HAVING 过滤组:每个分类中,数量 > 3 的分类
SELECT category, COUNT(*) AS cnt
FROM products
GROUP BY category
HAVING cnt > 3;
-- ❌ 错误:WHERE 不能用聚合函数
SELECT category, COUNT(*)
FROM products
WHERE COUNT(*) > 3
GROUP BY category;
DCL:权限控制
-- 创建用户
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password123';
-- 授予权限:给 app_user 对 my_shop 数据库的查询和插入权限
GRANT SELECT, INSERT ON my_shop.* TO 'app_user'@'localhost';
-- 撤销权限
REVOKE INSERT ON my_shop.* FROM 'app_user'@'localhost';
-- 查看权限
SHOW GRANTS FOR 'app_user'@'localhost';
常见面试问题
Q1: SQL 的执行顺序是什么?
答案:
SQL 的执行顺序与书写顺序不同,实际执行顺序为:
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT
面试中常考的陷阱:
-- 为什么这个 SQL 会报错?
SELECT category, COUNT(*) AS cnt
FROM products
WHERE cnt > 5 -- ❌ 错误!WHERE 在 SELECT 之前执行,此时 cnt 还不存在
GROUP BY category;
-- 正确写法:用 HAVING
SELECT category, COUNT(*) AS cnt
FROM products
GROUP BY category
HAVING cnt > 5; -- ✅ HAVING 在 SELECT 之后执行
Q2: TRUNCATE、DELETE、DROP 的区别?
答案:
| 对比 | DELETE | TRUNCATE | DROP |
|---|---|---|---|
| 类型 | DML | DDL | DDL |
| 删除范围 | 可指定条件 | 删除全部数据 | 删除表+数据 |
| 事务 | 可回滚 | 不可回滚 | 不可回滚 |
| 速度 | 慢(逐行) | 快(直接重建) | 最快 |
| 自增值 | 保持 | 重置 | 无意义 |
| 触发器 | 会触发 | 不触发 | 不触发 |
| 表结构 | 保留 | 保留 | 删除 |
Q3: COUNT(*) 和 COUNT(1) 和 COUNT(column) 有什么区别?
答案:
COUNT(*):统计所有行数,包括 NULL 值COUNT(1):等价于COUNT(*),性能无差异(MySQL 已做优化)COUNT(column):统计该列中非 NULL 的行数
-- 假设 users 表有 5 行,其中 email 有 2 行为 NULL
SELECT COUNT(*) FROM users; -- 结果:5
SELECT COUNT(1) FROM users; -- 结果:5
SELECT COUNT(email) FROM users; -- 结果:3(忽略 NULL)
Q4: CHAR 和 VARCHAR 有什么区别?
答案:
| 对比 | CHAR(n) | VARCHAR(n) |
|---|---|---|
| 存储方式 | 固定长度,不足补空格 | 可变长度,按实际长度存储 |
| 存储空间 | 始终占 n 字节 | 实际长度 + 1~2 字节长度前缀 |
| 检索速度 | 更快(固定偏移) | 稍慢 |
| 适用场景 | 长度固定的数据(如 MD5 哈希、国家代码) | 长度不固定的数据(如姓名、地址) |
-- 存储 'abc':
-- CHAR(10) 实际存储:'abc '(补 7 个空格,占 10 字节)
-- VARCHAR(10) 实际存储:'abc'(占 3 + 1 = 4 字节)
Q5: LIKE 中 % 和 _ 的区别?
答案:
%:匹配零个或多个任意字符_:匹配恰好一个任意字符
SELECT * FROM products WHERE name LIKE 'i%'; -- 以 i 开头(iPhone、iPad…)
SELECT * FROM products WHERE name LIKE '%Pro'; -- 以 Pro 结尾
SELECT * FROM products WHERE name LIKE '%Apple%'; -- 包含 Apple
SELECT * FROM products WHERE name LIKE 'i____'; -- 以 i 开头且总长为 5 的字符串
性能提醒
LIKE '%keyword'(前面有 % 的模糊查询)无法使用索引,会导致全表扫描。如果需要全文搜索,建议使用全文索引或 Elasticsearch。
Q6: UNION 和 UNION ALL 有什么区别?什么时候用哪个?
答案:
| 对比 | UNION | UNION ALL |
|---|---|---|
| 去重 | ✅ 自动去重 | ❌ 保留全部 |
| 性能 | 慢(需要排序去重) | 快 |
| 排序 | 隐含排序 | 不排序 |
-- UNION:自动去重(结果:1, 2, 3, 4, 5)
SELECT id FROM table_a
UNION
SELECT id FROM table_b;
-- UNION ALL:保留全部(可能有重复)
SELECT id FROM table_a
UNION ALL
SELECT id FROM table_b;
原则:如果确定没有重复,或者不需要去重,始终用 UNION ALL,性能好得多。
Q7: EXISTS 和 IN 有什么区别?
答案:
-- IN:先执行子查询,将结果集存入临时表,然后逐行匹配
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
-- EXISTS:对外层每一行,执行一次子查询,返回 TRUE/FALSE
SELECT * FROM users u WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
性能选择:
- 外表大、子查询结果集小 → 用
IN - 外表小、子查询涉及大表 → 用
EXISTS - 实际中 MySQL 优化器通常会自动转换,差异不大
Q8: CASE WHEN 怎么用?给个实际例子
答案:
CASE WHEN 是 SQL 中的条件表达式,相当于编程语言中的 if-else:
-- 简单 CASE:匹配值
SELECT name,
CASE category
WHEN '手机' THEN '📱 手机'
WHEN '电脑' THEN '💻 电脑'
ELSE '📦 其他'
END AS category_label
FROM products;
-- 搜索 CASE:匹配条件(更灵活)
SELECT name, price,
CASE
WHEN price >= 10000 THEN '高端'
WHEN price >= 3000 THEN '中端'
WHEN price >= 1000 THEN '入门'
ELSE '低价'
END AS price_level
FROM products;
-- 用 CASE WHEN 做行转列
SELECT
user_id,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_total,
SUM(CASE WHEN status = 'cancelled' THEN amount ELSE 0 END) AS cancelled_total
FROM orders
GROUP BY user_id;
Q9: 子查询有哪几种类型?
答案:
| 类型 | 返回结果 | 示例 |
|---|---|---|
| 标量子查询 | 单个值 | WHERE price > (SELECT AVG(price) FROM products) |
| 列子查询 | 一列多值 | WHERE id IN (SELECT user_id FROM orders) |
| 行子查询 | 一行多列 | WHERE (a, b) = (SELECT x, y FROM ...) |
| 表子查询 | 多行多列 | FROM (SELECT ... ) AS t |
| 相关子查询 | 引用外表列 | WHERE price > (SELECT AVG(price) FROM products p2 WHERE p2.category = p1.category) |
Q10: 如何安全地执行 UPDATE 和 DELETE?
答案:
生产环境操作原则:
-- 1. 先用 SELECT 确认影响范围
SELECT * FROM products WHERE category = '手机' AND stock = 0;
-- 2. 确认无误后再执行
DELETE FROM products WHERE category = '手机' AND stock = 0;
-- 3. 使用事务包裹,便于回滚
START TRANSACTION;
UPDATE products SET price = price * 0.9 WHERE category = '手机';
-- 检查结果...
-- COMMIT; -- 确认无误后提交
-- ROLLBACK; -- 发现问题就回滚
安全建议
- 永远在 UPDATE/DELETE 前先写 SELECT 验证
- 使用事务(BEGIN/COMMIT/ROLLBACK)包裹关键操作
- 设置
sql_safe_updates=1,禁止没有 WHERE 条件的 UPDATE/DELETE - 大批量操作时分批执行,避免长事务