50条SQL常见语句,轻松掌握90%+标准SQL及主流数据库方言

2026年01月20日/ 浏览 13

这份SQL速查手册,从基础到进阶,我们用50条SQL常见语句覆盖了各类数据库操作。不仅包含SQL标准语法,我们还特别标注了各主流数据库的差异点,仅供参考。

一、基础操作(CRUD)

1、创建表(含:主键、外键、默认值、约束)

-- [通用]创建学生表(含主键、非空、默认值、外键) CREATE TABLE students ( id INT PRIMARY KEY AUTO_INCREMENT, -- [MySQL]自增主键 -- id SERIAL PRIMARY KEY, -- [PostgreSQL]SERIAL=自增整型 -- id INT IDENTITY(1,1) PRIMARY KEY, -- [SQL Server] -- id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- [Oracle 12c+] name VARCHAR(100) NOT NULL, -- 姓名非空 email VARCHAR(255) UNIQUE, -- 邮箱唯一 gender CHAR(1) CHECK (gender IN (M,F)), -- 性别检查约束 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 默认当前时间 -- created_at TIMESTAMP DEFAULT NOW(), -- [PostgreSQL] -- created_at DATETIME2 DEFAULT GETDATE(), -- [SQL Server] -- created_at TIMESTAMP DEFAULT SYSDATE, -- [Oracle] class_id INT, FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE -- 级联删除 -- [Oracle]我们需先创建约束名:CONSTRAINT fk_class FOREIGN KEY ... );

解析

主键:唯一标识行,自动创建索引。外键:保证引用完整性,支持级联操作。默认值:插入时,若未指定,则自动填充。检查约束:限制字段取值范围。

进阶用法

-- [PostgreSQL]生成列(计算列) ALTER TABLE students ADD COLUMN full_name TEXT GENERATED ALWAYS AS (name || ( || email || )) STORED; -- [SQL Server]计算列 ALTER TABLE students ADD full_name AS (name + ( + email + )); -- [MySQL 8.0+]生成列 ALTER TABLE students ADD COLUMN full_name VARCHAR(300) AS (CONCAT(name, (, email, ))) STORED;

2、插入数据(单行、多行、子查询、ON DUPLICATE KEY)

-- 单行插入 INSERT INTO students (name, email, gender, class_id) VALUES (张三, zhang@example.com, M, 1); -- 多行插入(标准SQL) INSERT INTO students (name, email, gender, class_id) VALUES (李四, li@example.com, F, 2), (王五, wang@example.com, M, 1); -- 从其他表插入(子查询) INSERT INTO students (name, email, gender, class_id) SELECT name, email, gender, 1 FROM temp_students WHERE status = active; -- [MySQL]存在则更新(UPSERT) INSERT INTO students (id, name, email) VALUES (1, 张三, zhang@new.com) ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email); -- [PostgreSQL]UPSERT(ON CONFLICT) INSERT INTO students (id, name, email) VALUES (1, 张三, zhang@new.com) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email; -- [SQL Server]MERGE实现UPSERT MERGE students AS target USING (SELECT 1 as id, 张三 as name, zhang@new.com as email) AS source ON target.id = source.id WHEN MATCHED THEN UPDATE SET name = source.name, email = source.email WHEN NOT MATCHED THEN INSERT (id, name, email) VALUES (source.id, source.name, source.email);

解析

ON DUPLICATE KEY UPDATE是MySQL特有。ON CONFLICT是PostgreSQL 9.5+标准。MERGE是SQL Server/Oracle标准语法,功能最全。

进阶用法

-- [PostgreSQL]返回插入的ID(用于后续关联) INSERT INTO students (...) VALUES (...) RETURNING id; -- [MySQL]获取自增ID SELECT LAST_INSERT_ID(); -- [SQL Server]OUTPUT子句 INSERT INTO students (...) OUTPUT INSERTED.id VALUES (...);

3、查询基础(SELECT+WHERE+ORDER BY+LIMIT)

-- 基础查询+条件+排序+分页 SELECT id, name, email, created_at FROM students WHERE class_id = 1 AND gender = M AND created_at >= 2025-01-01 ORDER BY created_at DESC LIMIT 10 OFFSET 20; -- [MySQL/PostgreSQL] -- [SQL Server]分页 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY created_at DESC) as rn FROM students WHERE class_id = 1 ) t WHERE rn BETWEEN 21 AND 30; -- [Oracle 12c+]分页 SELECT * FROM students WHERE class_id = 1 ORDER BY created_at DESC OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

解析

LIMIT OFFSET:MySQL/PostgreSQL标准,简单高效。ROW_NUMBER():SQL Server/Oracle传统分页。OFFSET FETCH:Oracle 12c+/SQL Server 2012+标准语法。

进阶用法

-- 随机取3条 SELECT * FROM students ORDER BY RAND() LIMIT 3; -- [MySQL] SELECT * FROM students ORDER BY RANDOM() LIMIT 3; -- [PostgreSQL] SELECT TOP 3 * FROM students ORDER BY NEWID(); -- [SQL Server] -- 条件搜索(⚠️ 我们需注意:安全防注入) -- 在应用层,我们应使用参数绑定: -- SELECT ... WHERE name = ? AND class_id = ?

4、更新数据(UPDATE+JOIN+条件)

-- 基础更新 UPDATE students SET email = new@example.com, updated_at = NOW() WHERE id = 1; -- [MySQL/PostgreSQL]关联更新(我们使用JOIN) UPDATE students s JOIN classes c ON s.class_id = c.id SET s.email = CONCAT(s.name, @, c.school, .edu) WHERE c.school = 清华; -- [SQL Server]关联更新 UPDATE s SET email = s.name + @ + c.school + .edu FROM students s JOIN classes c ON s.class_id = c.id WHERE c.school = 清华; -- [Oracle]关联更新(我们使用子查询) UPDATE students s SET email = ( SELECT s.name || @ || c.school || .edu FROM classes c WHERE c.id = s.class_id ) WHERE EXISTS ( SELECT 1 FROM classes c WHERE c.id = s.class_id AND c.school = 清华 );

解析

MySQL/PostgreSQL支持UPDATE ... FROM ...或UPDATE ... JOIN ...SQL Server使用UPDATE alias SET ... FROM ... JOIN ...Oracle需使用子查询或MERGE

进阶用法

-- 限制更新行数(防误操作) UPDATE students SET status = inactive WHERE ... LIMIT 1; -- [MySQL] -- 返回被更新的行(PostgreSQL) UPDATE students SET ... WHERE ... RETURNING *; -- 条件更新(CASE WHEN) UPDATE students SET grade = CASE WHEN score >= 90 THEN A WHEN score >= 80 THEN B ELSE C END;

5、删除数据(DELETE+JOIN+TRUNCATE)

-- 删除指定行 DELETE FROM students WHERE id = 1; -- [MySQL/PostgreSQL]关联删除 DELETE s FROM students s JOIN classes c ON s.class_id = c.id WHERE c.name = 已解散班级; -- [SQL Server] DELETE s FROM students s JOIN classes c ON s.class_id = c.id WHERE c.name = 已解散班级; -- [Oracle](只能子查询) DELETE FROM students WHERE class_id IN ( SELECT id FROM classes WHERE name = 已解散班级 ); -- 快速清空表(不记录日志,不可回滚) TRUNCATE TABLE students; -- [Oracle]需加COMMIT;

解析

DELETE可加WHERE,可回滚,记录日志。TRUNCATE快速清空,重置自增ID(MySQL/PostgreSQL),不可回滚。

进阶用法

-- 删除重复数据(保留最小ID) DELETE s1 FROM students s1 JOIN students s2 WHERE s1.email = s2.email AND s1.id > s2.id; -- [PostgreSQL]我们使用CTE删除 WITH duplicates AS ( SELECT id, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) as rn FROM students ) DELETE FROM students WHERE id IN (SELECT id FROM duplicates WHERE rn > 1); -- 软删除(推荐生产环境) ALTER TABLE students ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE; UPDATE students SET is_deleted = TRUE WHERE ...; -- 查询时加:WHERE is_deleted = FALSE

二、复杂JOIN/子查询

6、INNER/LEFT/RIGHT/FULL JOIN

-- 内连接(只返回匹配行) SELECT s.name, c.name as class_name FROM students s INNER JOIN classes c ON s.class_id = c.id; -- 左连接(返回左表所有+匹配右表) SELECT s.name, c.name as class_name FROM students s LEFT JOIN classes c ON s.class_id = c.id; -- 右连接(返回右表所有+匹配左表) SELECT s.name, c.name as class_name FROM students s RIGHT JOIN classes c ON s.class_id = c.id; -- 全外连接(左右表所有行)[PostgreSQL/SQL Server/Oracle] SELECT s.name, c.name as class_name FROM students s FULL OUTER JOIN classes c ON s.class_id = c.id; -- [MySQL]不支持FULL JOIN,需我们用UNION模拟 SELECT ... FROM students s LEFT JOIN classes c ... UNION SELECT ... FROM students s RIGHT JOIN classes c ... WHERE s.id IS NULL;

解析

INNER JOIN:交集。LEFT JOIN:左表为主。FULL JOIN:并集(MySQL需模拟)。

进阶用法

-- 自连接(查询同一班级的学生对) SELECT s1.name as student1, s2.name as student2 FROM students s1 JOIN students s2 ON s1.class_id = s2.class_id AND s1.id < s2.id; -- 我们使用USING简化(字段名相同) SELECT name, class_name FROM students JOIN classes USING (class_id); -- 我们需注意:字段名必须相同

7、子查询(标量、行、表、关联子查询)

-- 标量子查询(返回单值) SELECT name, (SELECT COUNT(*) FROM orders WHERE user_id = u.id) as order_count FROM users u; -- 行子查询(返回一行多列) SELECT * FROM products WHERE (category_id, price) = ( SELECT category_id, MAX(price) FROM products GROUP BY category_id LIMIT 1 ); -- 表子查询(派生表) SELECT u.name, o.total FROM users u JOIN ( SELECT user_id, SUM(amount) as total FROM orders GROUP BY user_id ) o ON u.id = o.user_id; -- 关联子查询(依赖外层) SELECT name, salary FROM employees e1 WHERE salary > ( SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id -- 依赖外层 e1 );

解析

标量子查询:用于SELECT/WHERE,必须返回一行一列。行子查询:用于WHERE,返回一行多列。表子查询:FROM子句中,可JOIN。关联子查询:性能较差,尽量改写为JOIN。

进阶用法

-- 我们用EXISTS替代IN(性能更好) SELECT * FROM students s WHERE EXISTS ( SELECT 1 FROM enrollments e WHERE e.student_id = s.id AND e.course_id = 101 ); -- 我们用NOT EXISTS查没有报名学生 SELECT * FROM students s WHERE NOT EXISTS ( SELECT 1 FROM enrollments e WHERE e.student_id = s.id );

8、UNION/UNION ALL/INTERSECT/EXCEPT

-- 合并结果集(去重) SELECT name FROM students WHERE class_id = 1 UNION SELECT name FROM teachers WHERE dept = 数学; -- 合并不去重(性能更好) SELECT name FROM students WHERE class_id = 1 UNION ALL SELECT name FROM teachers WHERE dept = 数学; -- 交集(PostgreSQL/SQL Server/Oracle) SELECT product_id FROM orders_2024 INTERSECT SELECT product_id FROM orders_2025; -- 差集(PostgreSQL/SQL Server/Oracle) SELECT product_id FROM orders_2024 EXCEPT SELECT product_id FROM orders_2025; -- [MySQL]模拟INTERSECT SELECT DISTINCT o1.product_id FROM orders_2024 o1 JOIN orders_2025 o2 ON o1.product_id = o2.product_id; -- [MySQL]模拟EXCEPT SELECT DISTINCT product_id FROM orders_2024 WHERE product_id NOT IN (SELECT product_id FROM orders_2025 WHERE product_id IS NOT NULL);

解析

UNION:自动去重,排序。UNION ALL:保留重复,更快。INTERSECT/EXCEPT:集合运算,MySQL不支持。

进阶用法

-- 多层UNION(注意括号) (SELECT ... UNION SELECT ...) UNION SELECT ...; -- 指定列别名(第一个SELECT定义结构) (SELECT name as label FROM students) UNION (SELECT dept_name as label FROM departments);

9、WITH子句(CTE - Common Table Expression)

-- 简单CTE(提升可读性) WITH high_score_students AS ( SELECT student_id, AVG(score) as avg_score FROM exam_results GROUP BY student_id HAVING AVG(score) > 90 ) SELECT s.name, h.avg_score FROM students s JOIN high_score_students h ON s.id = h.student_id; -- 多层CTE WITH cte1 AS (SELECT ...), cte2 AS (SELECT ... FROM cte1 WHERE ...), cte3 AS (SELECT ... FROM cte2 JOIN ...) SELECT * FROM cte3;

解析

CTE提升SQL可读性和模块化。可递归。所有主流数据库支持(MySQL 8.0+)。

进阶用法

-- CTE用于UPDATE/DELETE(PostgreSQL/SQL Server) WITH cte AS ( SELECT id FROM students WHERE class_id = 999 ) DELETE FROM enrollments WHERE student_id IN (SELECT id FROM cte); -- [MySQL 8.0+]也支持 WITH cte AS (...) UPDATE ... SET ... WHERE id IN (SELECT id FROM cte);

10、相关子查询优化(改写为JOIN/窗口函数)

-- 原始:我们查询每个学生最新订单 SELECT s.name, (SELECT o.order_date FROM orders o WHERE o.user_id = s.id ORDER BY o.order_date DESC LIMIT 1) as last_order FROM students s; -- 优化1:改写为LATERAL JOIN(PostgreSQL/MySQL 8.0+) SELECT s.name, o.order_date as last_order FROM students s JOIN LATERAL ( SELECT order_date FROM orders WHERE user_id = s.id ORDER BY order_date DESC LIMIT 1 ) o ON true; -- 优化2:我们使用窗口函数(推荐) WITH ranked_orders AS ( SELECT user_id, order_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) as rn FROM orders ) SELECT s.name, r.order_date as last_order FROM students s JOIN ranked_orders r ON s.id = r.user_id AND r.rn = 1;

解析

相关子查询性能差(N+1问题)。优先改写为JOIN+窗口函数或LATERAL。

进阶用法

-- 我们要查询高于部门平均工资的员工(窗口函数版) SELECT name, salary, dept_avg FROM ( SELECT name, salary, dept_id, AVG(salary) OVER (PARTITION BY dept_id) as dept_avg FROM employees ) t WHERE salary > dept_avg;

三、聚合与分组

11、GROUP BY+HAVING+多级聚合

-- 我们要查询每个班级平均分>85的学生人数和最高分 SELECT c.name as class_name, COUNT(*) as student_count, MAX(e.score) as max_score FROM classes c JOIN students s ON c.id = s.class_id JOIN exam_results e ON s.id = e.student_id GROUP BY c.name HAVING AVG(e.score) > 85 -- HAVING过滤分组后结果 ORDER BY student_count DESC;

解析

GROUP BY按字段分组。HAVING是分组后的条件(WHERE是分组前)。可嵌套聚合函数:MAX(AVG(...))需用子查询或窗口函数。

进阶用法

-- 分组后,保留原始明细(PostgreSQL/SQL Server/Oracle) SELECT *, COUNT(*) OVER (PARTITION BY class_id) as class_size, AVG(score) OVER (PARTITION BY class_id) as class_avg FROM exam_results; -- [MySQL 8.0+]同样支持窗口函数

12、ROLLUP/CUBE/GROUPING SETS(多维聚合)

-- [PostgreSQL/SQL Server/Oracle]ROLLUP:生成小计+总计 SELECT dept, gender, COUNT(*) as cnt, AVG(salary) as avg_salary FROM employees GROUP BY ROLLUP(dept, gender); -- 生成(dept,gender), (dept,null), (null,null) -- CUBE:所有组合 GROUP BY CUBE(dept, gender); -- (dept,gender), (dept,null), (null,gender), (null,null) -- GROUPING SETS:自定义组合 GROUP BY GROUPING SETS ((dept), (gender), ()); -- [MySQL]不支持,我们需用UNION模拟 SELECT dept, gender, COUNT(*), AVG(salary) FROM employees GROUP BY dept, gender UNION ALL SELECT dept, NULL, COUNT(*), AVG(salary) FROM employees GROUP BY dept UNION ALL SELECT NULL, NULL, COUNT(*), AVG(salary) FROM employees;

解析

ROLLUP:层级小计(如:年→月→日)。CUBE:所有维度组合。GROUPING SETS:自由组合。

进阶用法

-- 判断是否为汇总行(PostgreSQL/SQL Server) SELECT dept, gender, GROUPING(dept) as is_dept_total, -- 1=汇总行 GROUPING(gender) as is_gender_total, COUNT(*) FROM employees GROUP BY ROLLUP(dept, gender);

13、DISTINCT聚合与FILTER子句

-- 计算去重后的数量 SELECT COUNT(DISTINCT user_id) as unique_users, SUM(amount) as total_amount FROM orders; -- [PostgreSQL/SQL Server]FILTER子句(条件聚合) SELECT COUNT(*) FILTER (WHERE status = completed) as completed_orders, COUNT(*) FILTER (WHERE status = cancelled) as cancelled_orders, AVG(score) FILTER (WHERE score > 0) as avg_positive_score FROM orders; -- [MySQL]我们用CASE WHEN模拟 SELECT SUM(CASE WHEN status = completed THEN 1 ELSE 0 END) as completed_orders, AVG(CASE WHEN score > 0 THEN score END) as avg_positive_score FROM orders;

解析

COUNT(DISTINCT ...)去重计数。FILTER是标准SQL,更简洁高效。MySQL需用CASE WHEN+聚合函数模拟。

14、STRING_AGG/GROUP_CONCAT/LISTAGG(字符串聚合)

-- [PostgreSQL]字符串聚合 SELECT class_id, STRING_AGG(name, , ORDER BY name) as student_names FROM students GROUP BY class_id; -- [MySQL] SELECT class_id, GROUP_CONCAT(name ORDER BY name SEPARATOR , ) as student_names FROM students GROUP BY class_id; -- [SQL Server] SELECT class_id, STRING_AGG(name, , ) WITHIN GROUP (ORDER BY name) as student_names FROM students GROUP BY class_id; -- [Oracle] SELECT class_id, LISTAGG(name, , ) WITHIN GROUP (ORDER BY name) as student_names FROM students GROUP BY class_id;

解析

各数据库语法不同,但功能一致。支持排序、分隔符。我们要注意长度限制(Oracle默认4000字符)。

进阶用法

-- [PostgreSQL]去重聚合 STRING_AGG(DISTINCT name, , ) -- [Oracle 21c+]去重 LISTAGG(DISTINCT name, , ) -- [MySQL]去重 GROUP_CONCAT(DISTINCT name ...)

15、自定义聚合函数(PostgreSQL/Oracle)

-- [PostgreSQL]创建自定义聚合:乘积 CREATE AGGREGATE product(numeric) ( SFUNC = numeric_mul, -- 累积函数 STYPE = numeric, -- 状态类型 INITCOND = 1 -- 初始值 ); SELECT product(price) FROM products WHERE category = Electronics; -- [Oracle]我们使用MODEL或PL/SQL(Procedural Language/SQL)来实现 -- 通常用EXP(SUM(LN(x)))近似(仅正数) SELECT EXP(SUM(LN(price))) FROM products WHERE price > 0;

解析

PostgreSQL支持自定义聚合。Oracle/MySQL一般用数学变换或存储过程。

四、窗口函数

16、ROW_NUMBER()/RANK()/DENSE_RANK()

-- 按部门分组,按工资排序 SELECT name, dept, salary, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) as rn, -- 1,2,3,4(无并列) RANK() OVER (PARTITION BY dept ORDER BY salary DESC) as rnk, -- 1,2,2,4(跳号) DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) as drnk -- 1,2,2,3(不跳号) FROM employees;

解析

ROW_NUMBER():严格排序,无重复序号。RANK():并列排名,后续跳号(1,2,2,4)。DENSE_RANK():并列排名,不跳号(1,2,2,3)。

进阶用法

-- 我们要查询各部门工资前3名 WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) as rn FROM employees ) SELECT * FROM ranked WHERE rn <= 3;

17、LEAD()/LAG()(前后行访问)

-- 我们要查询学生成绩及与前一名的分差 SELECT name, score, LAG(score, 1) OVER (ORDER BY score DESC) as prev_score, score - LAG(score, 1) OVER (ORDER BY score DESC) as score_diff FROM exam_results; -- LEAD:获取下一行 LEAD(score, 1, 0) OVER (...) -- 第三个参数是默认值(无下一行时)

解析

LAG(n):前第n行。LEAD(n):后第n行。常用在时间序列分析(环比、差值)。

18、NTILE()/PERCENT_RANK()/CUME_DIST()

-- 我们要将学生按成绩分为4档(优、良、中、差) SELECT name, score, NTILE(4) OVER (ORDER BY score DESC) as quartile FROM students; -- 百分位排名(0~1) PERCENT_RANK() OVER (ORDER BY score) -- 累计分布(<=当前值的比例) CUME_DIST() OVER (ORDER BY score)

解析

NTILE(N):分N桶,用在分组评级。PERCENT_RANK():相对排名(最小=0,最大=1)。CUME_DIST():累计分布函数。

19、SUM()/AVG() OVER(累计/移动平均)

-- 累计销售额 SELECT order_date, amount, SUM(amount) OVER (ORDER BY order_date) as running_total FROM sales; -- 移动平均(最近3天) SELECT order_date, amount, AVG(amount) OVER ( ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) as moving_avg_3d FROM sales;

解析

ROWS BETWEEN ...是定义窗口范围。PRECEDING/FOLLOWING/CURRENT ROW。

进阶用法

-- 按月累计 SUM(amount) OVER ( PARTITION BY YEAR(order_date), MONTH(order_date) ORDER BY order_date )

20、FIRST_VALUE()/LAST_VALUE()/NTH_VALUE()

-- 我们要查询每个部门最高工资者姓名 SELECT name, dept, salary, FIRST_VALUE(name) OVER ( PARTITION BY dept ORDER BY salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) as top_earner FROM employees; -- LAST_VALUE默认只到当前行,需我们指定范围 LAST_VALUE(name) OVER ( ORDER BY salary ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING )

解析

FIRST_VALUE/LAST_VALUE获取窗口内首尾值。注意:LAST_VALUE默认范围是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,需我们显式指定完整范围。

21、窗口函数性能优化

-- 优化:分区字段加索引 CREATE INDEX idx_emp_dept ON employees(dept); -- 我们要避免在ORDER BY使用表达式 -- 慢:ORDER BY UPPER(name) -- 快:ORDER BY name(我们需同时创建函数索引) -- [PostgreSQL]函数索引 CREATE INDEX idx_upper_name ON employees(UPPER(name)); -- 我们使用物化CTE减少重复计算 WITH sales_summary AS MATERIALIZED ( SELECT product_id, SUM(amount) as total FROM sales GROUP BY product_id ) SELECT ..., ROW_NUMBER() OVER (ORDER BY total DESC) ... FROM sales_summary;

五、递归CTE

22、递归查询组织架构(树形结构)

-- 我们要查询某员工的所有下属(向下递归) WITH RECURSIVE subordinates AS ( -- 初始成员(锚点) SELECT id, name, manager_id, 0 as level FROM employees WHERE id = 1 -- CEO UNION ALL -- 递归成员 SELECT e.id, e.name, e.manager_id, s.level + 1 FROM employees e JOIN subordinates s ON e.manager_id = s.id ) SELECT * FROM subordinates;

解析

WITH RECURSIVE:PostgreSQL/MySQL 8.0+/SQLite。SQL Server/Oracle用WITH ...(无需RECURSIVE)。必须有终止条件(如:层级限制)。

23、查询路径(祖先路径/分隔符路径)

-- 生成路径:CEO>Manager>Employee WITH RECURSIVE hierarchy AS ( SELECT id, name, manager_id, name as path, 0 as level FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.id, e.name, e.manager_id, CONCAT(h.path, > , e.name), -- [MySQL/PostgreSQL] -- h.path + > + e.name, -- [SQL Server] h.level + 1 FROM employees e JOIN hierarchy h ON e.manager_id = h.id ) SELECT id, name, path, level FROM hierarchy;

解析

适用于菜单、分类、评论回复等树形结构。路径可用于前端展示或权限匹配。

24、递归限制与优化

-- 我们要限制递归深度(防止死循环) WITH RECURSIVE cte AS ( SELECT ..., 1 as depth FROM ... WHERE ... UNION ALL SELECT ..., depth + 1 FROM ... JOIN cte WHERE depth < 10 -- ⚠️ 关键点 ) SELECT * FROM cte; -- [PostgreSQL]我们要防止递归查询执行过久(安全兜底) SET statement_timeout = 30s; -- SET LOCAL statement_timeout = 30s; -- 推荐使用LOCAL限定作用域 -- 优化:我们在manager_id字段加索引 CREATE INDEX idx_emp_manager ON employees(manager_id);

六、JSON/XML/数组处理

25、JSON查询与更新(MySQL/PostgreSQL)

-- [MySQL]查询JSON字段 SELECT name, JSON_EXTRACT(profile, $.age) as age, profile->$.city as city, -- 简写 profile->>$.city as city_text -- 去引号 FROM users; -- [PostgreSQL]查询JSONB SELECT name, profile->age as age, -- 返回jsonb profile->>city as city_text -- 返回text -- 更新JSON字段 UPDATE users SET profile = JSON_SET(profile, $.age, 30) WHERE id = 1; -- [MySQL] UPDATE users SET profile = profile || {"age": 30}::jsonb -- [PostgreSQL] WHERE id = 1;

解析

MySQL:JSON_EXTRACT, ->, ->>, JSON_SETPostgreSQL:->, ->>, ||(合并), #>(路径)

26、JSON聚合与生成

-- [MySQL]生成JSON数组 SELECT class_id, JSON_ARRAYAGG(JSON_OBJECT(name, name, score, score)) as students FROM exam_results GROUP BY class_id; -- [PostgreSQL] SELECT class_id, JSONB_AGG(JSONB_BUILD_OBJECT(name, name, score, score)) as students FROM exam_results GROUP BY class_id; -- 从表生成JSON对象 SELECT JSON_OBJECT(id, id, name, name) FROM students LIMIT 1; -- {"id": 1, "name": "张三"}

27、数组操作(PostgreSQL)

-- [PostgreSQL]数组字段 CREATE TABLE tags ( id SERIAL PRIMARY KEY, name TEXT, keywords TEXT[] -- 字符串数组 ); -- 查询包含某关键词 SELECT * FROM tags WHERE 数据库 = ANY(keywords); -- 数组长度 SELECT name, ARRAY_LENGTH(keywords, 1) as tag_count FROM tags; -- 展开数组(转行) SELECT name, UNNEST(keywords) as keyword FROM tags; -- 聚合为数组 SELECT class_id, ARRAY_AGG(name) as student_names FROM students GROUP BY class_id;

解析

ANY/ALL用于数组比较。UNNEST展开数组为多行。ARRAY_AGG行转数组。

28、XML处理(SQL Server/Oracle)

-- [SQL Server]查询XML SELECT T.c.value((name/text())[1], VARCHAR(100)) as name, T.c.value((age/text())[1], INT) as age FROM users_xml CROSS APPLY xml_data.nodes(/user) T(c); -- [Oracle] SELECT EXTRACTVALUE(xml_data, /user/name) as name, EXTRACTVALUE(xml_data, /user/age) as age FROM users_xml; -- 更新XML UPDATE users_xml SET xml_data.modify(replace value of (/user/age/text())[1] with "30") WHERE id = 1;

29、JSON/XML性能优化

-- [MySQL]我们对JSON字段创建虚拟列+索引 ALTER TABLE users ADD COLUMN city VARCHAR(100) GENERATED ALWAYS AS (profile->>$.city) STORED; CREATE INDEX idx_user_city ON users(city); -- [PostgreSQL]我们对JSONB创建GIN索引 CREATE INDEX idx_profile_gin ON users USING GIN(profile); -- [PostgreSQL]特定路径索引 CREATE INDEX idx_profile_city ON users((profile->>city));

七、事务控制与并发控制

30、事务控制(BEGIN/COMMIT/ROLLBACK)

-- 标准事务 BEGIN; UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT; -- 回滚 BEGIN; ... ROLLBACK; -- [PostgreSQL/SQL Server]SAVEPOINT SAVEPOINT before_update; ... ROLLBACK TO SAVEPOINT before_update; RELEASE SAVEPOINT before_update;

31、隔离级别(READ COMMITTED/SERIALIZABLE...)

-- [PostgreSQL] BEGIN ISOLATION LEVEL SERIALIZABLE; ... -- [MySQL] SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; -- [SQL Server] SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; BEGIN TRAN; -- 查看当前隔离级别 SHOW TRANSACTION ISOLATION LEVEL; -- [PostgreSQL/MySQL] DBCC USEROPTIONS; -- [SQL Server]

隔离级别

(1)READ UNCOMMITTED

:脏读。

(2)READ COMMITTED

(默认):不可重复读。

(3)REPEATABLE READ

(MySQL默认):幻读。

(4)SERIALIZABLE:完全隔离。

32、锁机制(FOR UPDATE/LOCK IN SHARE MODE)

-- [PostgreSQL/MySQL]行级锁(悲观锁) SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- [MySQL]共享锁 SELECT * FROM accounts WHERE id = 1 LOCK IN SHARE MODE; -- [SQL Server] SELECT * FROM accounts WITH (UPDLOCK) WHERE id = 1; -- [Oracle] SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

解析

FOR UPDATE:排他锁,防止其他事务修改。用在“先查后改”场景(如:库存扣减)。

33、乐观锁(版本号/时间戳)

-- 表结构 ALTER TABLE products ADD COLUMN version INT DEFAULT 0; -- 更新时检查版本 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 100 AND version = 5; -- ⚠️ 关键点:检查旧版本 -- 检查影响行数,0表示并发冲突 -- 应用层重试或报错

解析

无锁,靠版本号检测冲突。适合读多写少场景。

34、死锁检测与处理

-- [PostgreSQL]查看锁 SELECT * FROM pg_locks; -- [MySQL]查看事务 SHOW ENGINE INNODB STATUS; -- [SQL Server] SELECT * FROM sys.dm_tran_locks; -- 避免死锁原则: -- 1、按固定顺序访问表/行 -- 2、减少事务粒度 -- 3、设置锁超时:SET lock_timeout = 5000; -- 5秒

八、索引优化/执行计划分析

35、索引类型与创建

-- B-Tree索引(默认) CREATE INDEX idx_student_class ON students(class_id); -- 唯一索引 CREATE UNIQUE INDEX idx_email ON students(email); -- 复合索引 CREATE INDEX idx_name_class ON students(name, class_id); -- [PostgreSQL]部分索引(条件索引) CREATE INDEX idx_active_users ON users(email) WHERE is_active = true; -- [MySQL]前缀索引 CREATE INDEX idx_name_prefix ON students(name(10)); -- [PostgreSQL/MySQL 8.0+]降序索引 CREATE INDEX idx_created_desc ON students(created_at DESC);

36、执行计划分析(EXPLAIN)

-- [PostgreSQL/MySQL] EXPLAIN ANALYZE SELECT * FROM students WHERE class_id = 1; -- [SQL Server] SET STATISTICS IO ON; SELECT * FROM students WHERE class_id = 1; -- 我们主要看: -- - type: index/ref/ALL(全表扫描) -- - rows: 预估行数 -- - cost: 代价 -- - actual time: 实际耗时

37、覆盖索引(Covering Index)

-- 查询只访问索引,不回表 CREATE INDEX idx_covering ON students(class_id, name, email); -- 以下查询可覆盖: SELECT name, email FROM students WHERE class_id = 1; -- [SQL Server]INCLUDE语法 CREATE INDEX idx_cover ON students(class_id) INCLUDE (name, email);

38、索引失效场景

-- 失效:左模糊 SELECT * FROM students WHERE name LIKE %三; -- 失效:函数操作 SELECT * FROM students WHERE UPPER(name) = ZHANG; -- 失效:类型转换 SELECT * FROM students WHERE class_id = 1; -- class_id是INT -- 失效:OR条件无索引 SELECT * FROM students WHERE name = 张三 OR email = ...; -- 优化:函数索引/表达式索引 CREATE INDEX idx_upper_name ON students((UPPER(name))); -- [PostgreSQL]

39、索引监控与维护

-- [PostgreSQL]查看索引使用率 SELECT schemaname, tablename, indexname, idx_scan as scans FROM pg_stat_user_indexes; -- 重建索引(碎片整理) REINDEX INDEX idx_student_class; -- [PostgreSQL] ALTER INDEX idx_student_class REBUILD; -- [SQL Server] -- [MySQL]分析表 ANALYZE TABLE students;

九、元数据查询/动态SQL/管理

40、查询元数据(表结构/索引/约束)

-- [PostgreSQL] SELECT column_name, data_type FROM information_schema.columns WHERE table_name = students; -- [MySQL] DESCRIBE students; -- 或 SHOW CREATE TABLE students; -- [SQL Server] EXEC sp_columns students; -- [Oracle] SELECT column_name, data_type FROM user_tab_columns WHERE table_name = STUDENTS;

41、动态SQL(拼接执行)

-- [PostgreSQL]使用EXECUTE DO $$ DECLARE table_name TEXT := students; sql TEXT; BEGIN sql := SELECT COUNT(*) FROM || quote_ident(table_name); EXECUTE sql; END $$; -- [SQL Server] DECLARE @sql NVARCHAR(MAX) = SELECT * FROM + @table_name; EXEC sp_executesql @sql; -- [MySQL]PREPARE SET @sql = CONCAT(SELECT * FROM , @table_name); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

⚠️ 安全警告:动态SQL易导致SQL注入!我们务必使用参数绑定或转义函数(如:quote_ident)。

42、数据库备份与恢复

# [PostgreSQL]备份 pg_dump -U user -d dbname -f backup.sql # 恢复 psql -U user -d dbname -f backup.sql # [MySQL] mysqldump -u user -p dbname > backup.sql mysql -u user -p dbname < backup.sql # [SQL Server] (T-SQL) BACKUP DATABASE dbname TO DISK = path.bak; RESTORE DATABASE dbname FROM DISK = path.bak;

43、性能监控(慢查询/连接数)

-- [MySQL]慢查询日志 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; -- 秒 -- [PostgreSQL]日志配置(postgresql.conf) log_min_duration_statement = 1000 -- 1秒 -- 我们查看当前连接 SHOW PROCESSLIST; -- [MySQL] SELECT * FROM pg_stat_activity; -- [PostgreSQL] sp_who2; -- [SQL Server]

44、数据迁移(ETL/COPY/BULK INSERT)

-- [PostgreSQL]快速导入 COPY students FROM /path/data.csv WITH CSV HEADER; -- [MySQL] LOAD DATA INFILE /path/data.csv INTO TABLE students FIELDS TERMINATED BY , LINES TERMINATED BY \n IGNORE 1 ROWS; -- [SQL Server] BULK INSERT students FROM path\data.csv WITH (FORMAT=CSV);

45、分区表(大表优化)

-- [PostgreSQL 10+] CREATE TABLE sales ( id SERIAL, sale_date DATE, amount DECIMAL ) PARTITION BY RANGE (sale_date); CREATE TABLE sales_2025 PARTITION OF sales FOR VALUES FROM (2025-01-01) TO (2026-01-01); -- [MySQL 8.0+]RANGE分区示例 CREATE TABLE sales ( id INT AUTO_INCREMENT, sale_date DATE NOT NULL, amount DECIMAL(10,2), region VARCHAR(50), PRIMARY KEY (id, sale_date) -- ⚠️ 分区键必须包含在主键中 ) PARTITION BY RANGE (YEAR(sale_date)) ( PARTITION p2024 VALUES LESS THAN (2025), PARTITION p2025 VALUES LESS THAN (2026), PARTITION p2026 VALUES LESS THAN (2027), PARTITION pmax VALUES LESS THAN MAXVALUE ); -- [PostgreSQL 12+]HASH分区(负载均衡) CREATE TABLE user_logs ( id BIGSERIAL, user_id INT, log_time TIMESTAMP ) PARTITION BY HASH (user_id); CREATE TABLE user_logs_p0 PARTITION OF user_logs FOR VALUES WITH (MODULUS 4, REMAINDER 0); CREATE TABLE user_logs_p1 PARTITION OF user_logs FOR VALUES WITH (MODULUS 4, REMAINDER 1); CREATE TABLE user_logs_p2 PARTITION OF user_logs FOR VALUES WITH (MODULUS 4, REMAINDER 2); CREATE TABLE user_logs_p3 PARTITION OF user_logs FOR VALUES WITH (MODULUS 4, REMAINDER 3); -- 分区维护:添加/删除分区 -- [PostgreSQL] CREATE TABLE sales_2026 PARTITION OF sales FOR VALUES FROM (2026-01-01) TO (2027-01-01); DROP TABLE sales_2024; -- 删除旧分区(秒级) -- [MySQL] ALTER TABLE sales ADD PARTITION ( PARTITION p2027 VALUES LESS THAN (2028) ); ALTER TABLE sales DROP PARTITION p2024;

解析

分区类型:RANGE(时间)、LIST(枚举)、HASH(散列)、KEY(MySQL)。优势:提升查询性能(分区裁剪)、简化数据管理(按分区删除/备份)。注意:MySQL主键必须包含分区字段。PostgreSQL支持默认分区(DEFAULT)。我们查询时带上分区键,才能触发“分区裁剪”。

进阶用法

-- [PostgreSQL]分区表+索引(每个子表独立索引) CREATE INDEX idx_sales_date ON sales USING BRIN(sale_date); -- 适合时序数据 -- [MySQL]分区+二级索引 CREATE INDEX idx_region ON sales(region); -- 全局索引(MySQL 8.0+支持本地索引)

十、物化视图

46、物化视图(Materialized View),即预计算加速

-- [PostgreSQL]我们创建物化视图(需手动刷新) CREATE MATERIALIZED VIEW mv_monthly_sales AS SELECT DATE_TRUNC(month, sale_date) as month, region, SUM(amount) as total_sales, COUNT(*) as order_count FROM sales GROUP BY 1, 2 ORDER BY 1 DESC; -- 刷新数据(全量) REFRESH MATERIALIZED VIEW mv_monthly_sales; -- [PostgreSQL 9.4+]并发刷新(不影响查询) REFRESH MATERIALIZED VIEW CONCURRENTLY mv_monthly_sales; -- 需唯一索引 -- [Oracle]自动刷新 CREATE MATERIALIZED VIEW mv_sales BUILD IMMEDIATE REFRESH FAST ON COMMIT -- 我们提交时自动刷新(需物化视图日志) AS SELECT ...; -- [SQL Server]用索引视图(Indexed View)模拟 CREATE VIEW vw_monthly_sales WITH SCHEMABINDING AS SELECT YEAR(sale_date) as yr, MONTH(sale_date) as mth, region, SUM(amount) as total, COUNT_BIG(*) as cnt -- ⚠️ COUNT_BIG是一个必须满足的要求 FROM dbo.sales GROUP BY YEAR(sale_date), MONTH(sale_date), region; -- 创建唯一聚集索引 → 成为“索引视图” CREATE UNIQUE CLUSTERED INDEX idx_mv_sales ON vw_monthly_sales(yr, mth, region); -- [MySQL]无原生物化视图 → 我们用定时任务+临时表替代

解析

物化视图=预计算+存储结果,适合复杂聚合、报表场景。PostgreSQL:手动刷新,支持并发。Oracle:支持自动刷新(FAST/COMPLETE)。SQL Server:索引视图=物化视图,但限制多(SCHEMABINDING、COUNT_BIG等)。MySQL:无原生支持,需我们用程序或事件调度模拟。

性能优化

-- [PostgreSQL]为物化视图建索引 CREATE INDEX idx_mv_sales_region ON mv_monthly_sales(region); -- 我们查询时直接查物化视图 SELECT * FROM mv_monthly_sales WHERE region = North AND month >= 2025-01-01;

十一、临时表/CTE/变量

47、临时表 vs CTE vs 表变量

-- 1、CTE(逻辑抽象,不存数据) WITH regional_sales AS ( SELECT region, SUM(amount) as total FROM sales GROUP BY region ) SELECT * FROM regional_sales WHERE total > 10000; -- 2、临时表(会话级/事务级) -- [PostgreSQL/MySQL/SQL Server] CREATE TEMP TABLE tmp_top_customers AS SELECT customer_id, SUM(amount) as total FROM orders GROUP BY customer_id HAVING SUM(amount) > 5000; -- [SQL Server]表变量(内存优先,小数据) DECLARE @TopCustomers TABLE ( customer_id INT, total DECIMAL(10,2) ); INSERT INTO @TopCustomers SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id HAVING SUM(amount) > 5000; -- 3、全局临时表(跨会话,少用) -- [SQL Server] 以##开头 CREATE TABLE ##GlobalTemp (id INT); -- [PostgreSQL]无全局临时表 → 用普通表+应用层管理

我们对比一下

类型

作用域

持久性

索引支持

适用场景

CTE

语句内

逻辑分层、递归

临时表

会话/事务

会话结束自动删

中间结果、复杂多步处理

表变量

批处理内

批处理结束删

⚠️有限

小数据、替代临时表

物化CTE

语句内

无(PostgreSQL 12+可物化)

重复引用子查询

进阶用法

-- [PostgreSQL 12+]物化CTE(避免重复计算) WITH RECURSIVE cte AS MATERIALIZED ( SELECT ... UNION ALL SELECT ... ) SELECT * FROM cte JOIN ...; -- [SQL Server]为临时表加索引 CREATE INDEX idx_tmp_cust ON #TopCustomers(customer_id);

48、变量与参数绑定(防止SQL注入)

-- [PostgreSQL]在DO块中使用变量 DO $$ DECLARE v_threshold INT := 1000; v_count INT; BEGIN SELECT COUNT(*) INTO v_count FROM orders WHERE amount > v_threshold; RAISE NOTICE Orders > %: %, v_threshold, v_count; END $$; -- [MySQL]用户变量(@开头) SET @min_amount = 1000; SELECT COUNT(*) INTO @order_count FROM orders WHERE amount > @min_amount; SELECT @order_count; -- [SQL Server]局部变量 DECLARE @MinAmount DECIMAL(10,2) = 1000; DECLARE @OrderCount INT; SELECT @OrderCount = COUNT(*) FROM orders WHERE amount > @MinAmount; PRINT Count: + CAST(@OrderCount AS VARCHAR); -- ⚠️ 动态SQL必须参数绑定 -- [PostgreSQL] EXECUTE SELECT COUNT(*) FROM orders WHERE amount > $1 USING v_threshold; -- [MySQL] SET @sql = SELECT COUNT(*) FROM orders WHERE amount > ?; PREPARE stmt FROM @sql; EXECUTE stmt USING @min_amount; DEALLOCATE PREPARE stmt; -- [SQL Server] DECLARE @SQL NVARCHAR(MAX) = NSELECT COUNT(*) FROM orders WHERE amount > @amt; EXEC sp_executesql @SQL, N@amt DECIMAL(10,2), @amt = @MinAmount;

⚠️ 安全第一

我们永远不要拼接用户输入到SQL字符串!我们要使用USING(PostgreSQL)、?(MySQL)、sp_executesql(SQL Server)进行参数绑定。

十二、审计/脱敏/安全

49、数据脱敏与审计日志

-- 1、查询时脱敏(动态脱敏) SELECT name, CONCAT(LEFT(phone, 3), ****, RIGHT(phone, 4)) as phone_masked, -- 138****1234 CONCAT(LEFT(email, 2), ***, SUBSTRING(email, LOCATE(@, email))) as email_masked -- ab***@gmail.com FROM users; -- [PostgreSQL]创建视图自动脱敏 CREATE VIEW users_masked AS SELECT id, name, REGEXP_REPLACE(phone, (\\d{3})\\d{4}(\\d{4}), \\1****\\2) as phone, REGEXP_REPLACE(email, (.{2}).+(@.+), \\1***\\2) as email FROM users; -- 2、审计日志(谁在什么时候改了什么?) -- [PostgreSQL]使用触发器+审计表 CREATE TABLE audit_log ( id SERIAL PRIMARY KEY, table_name TEXT, operation TEXT, -- INSERT/UPDATE/DELETE user_name TEXT DEFAULT CURRENT_USER, op_time TIMESTAMP DEFAULT NOW(), old_data JSONB, new_data JSONB ); CREATE OR REPLACE FUNCTION log_user_changes() RETURNS TRIGGER AS $$ BEGIN IF TG_OP = UPDATE THEN INSERT INTO audit_log(table_name, operation, old_data, new_data) VALUES (users, TG_OP, TO_JSONB(OLD), TO_JSONB(NEW)); RETURN NEW; ELSIF TG_OP = DELETE THEN INSERT INTO audit_log(table_name, operation, old_data) VALUES (users, TG_OP, TO_JSONB(OLD)); RETURN OLD; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER tr_audit_users AFTER UPDATE OR DELETE ON users FOR EACH ROW EXECUTE FUNCTION log_user_changes(); -- [MySQL]用通用审计插件或触发器类似实现

解析

动态脱敏:适合前端展示、测试环境。审计日志:满足合规要求(GDPR、等保),记录数据变更历史。PostgreSQL触发器:强大灵活,支持JSONB记录完整前后镜像。

50、权限控制与角色管理

-- 1、创建角色 CREATE ROLE analyst; -- [PostgreSQL/SQL Server/Oracle] CREATE ROLE developer; -- 2、授权(最小权限原则) -- [PostgreSQL] GRANT SELECT ON sales, products TO analyst; GRANT SELECT, INSERT, UPDATE ON orders TO developer; GRANT USAGE ON SCHEMA public TO analyst; -- [MySQL] GRANT SELECT ON mydb.sales TO analyst@%; GRANT SELECT, INSERT, UPDATE ON mydb.orders TO developer@localhost; -- 3、列级权限(PostgreSQL/SQL Server) GRANT SELECT (name, email) ON users TO analyst; -- 只允许查部分列 -- 4、行级安全(RLS)— [PostgreSQL 9.5+] -- 启用行级安全 ALTER TABLE sales ENABLE ROW LEVEL SECURITY; -- 创建策略:用户只能看自己区域的数据 CREATE POLICY sales_region_policy ON sales FOR SELECT USING (region = current_setting(app.current_region)); -- 设置会话变量 SET app.current_region = North; SELECT * FROM sales; -- 只返回region=North的数据 -- [SQL Server]用安全谓词(Security Predicate)+策略实现类似功能 -- 5、查看权限 -- [PostgreSQL] \dp sales -- psql命令 -- 或 SELECT * FROM information_schema.table_privileges WHERE table_name = sales; -- [MySQL] SHOW GRANTS FOR analyst@%;

安全规范

最小权限:我们别乱给权限,只给必要权限。角色管理:我们通过角色分配权限,而非直接给用户。行级安全(RLS):实现数据隔离(如:多租户、部门数据隔离)。定期审计:我们定期检查权限分配是否合理。

picture loss