跳到主要内容

SQL 语法基础

问题

SQL 的基本语法有哪些?SELECT、INSERT、UPDATE、DELETE 怎么用?

答案

SQL(Structured Query Language,结构化查询语言) 是操作关系型数据库的标准语言。不管你用的是 MySQL、PostgreSQL 还是 Oracle,SQL 的核心语法都是通用的。

SQL 就像是你和数据库之间的"对话语言"——你用 SQL 告诉数据库:我要什么数据、我要改什么数据、我要删什么数据。


SQL 的四大类型

类型全称作用常用语句
DDLData Definition Language定义数据库结构CREATEALTERDROPTRUNCATE
DMLData Manipulation Language操作数据INSERTUPDATEDELETE
DQLData Query Language查询数据SELECT
DCLData Control Language权限控制GRANTREVOKE

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 的区别?

答案

对比DELETETRUNCATEDROP
类型DMLDDLDDL
删除范围可指定条件删除全部数据删除表+数据
事务可回滚不可回滚不可回滚
速度慢(逐行)快(直接重建)最快
自增值保持重置无意义
触发器会触发不触发不触发
表结构保留保留删除

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 有什么区别?什么时候用哪个?

答案

对比UNIONUNION 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; -- 发现问题就回滚
安全建议
  1. 永远在 UPDATE/DELETE 前先写 SELECT 验证
  2. 使用事务(BEGIN/COMMIT/ROLLBACK)包裹关键操作
  3. 设置 sql_safe_updates=1,禁止没有 WHERE 条件的 UPDATE/DELETE
  4. 大批量操作时分批执行,避免长事务

相关链接