SQL语句
SQL 是操作和查询关系型数据库的核心语言,以下是其主要知识点的系统总结,适合学习和复习使用:
SQL基础知识
一、基础语法
- 数据定义语言 (DDL)
 
CREATE: 创建数据库、表、索引等ALTER: 修改表结构(添加/删除列、修改数据类型)DROP: 删除数据库、表、索引TRUNCATE: 快速清空表数据(不可回滚)
- 数据操作语言 (DML)
 
INSERT INTO: 插入数据DELETE: 删除数据(可回滚)UPDATE: 修改数据SELECT: 数据查询(核心功能)
二、数据查询(SELECT 核心)
- 基础查询
 
SELECT * FROM tableSELECT column1, column2DISTINCT: 去重AS: 别名(列或表)
- 条件过滤
 
WHERE: 条件筛选- 运算符:
=,<>,>,<,BETWEEN,IN,LIKE(通配符%和_) - 逻辑运算符:
AND,OR,NOT 
- 排序与分页
 
ORDER BY column ASC/DESC- 分页语法:
 - MySQL: 
LIMIT n OFFSET m - SQL Server: 
OFFSET m ROWS FETCH NEXT n ROWS ONLY - Oracle: 
ROWNUM 
- 聚合与分组
 
- 聚合函数:
COUNT(),SUM(),AVG(),MAX(),MIN() GROUP BY: 按列分组HAVING: 分组后过滤(与WHERE区别:WHERE在聚合前过滤,HAVING在聚合后)
三、多表操作
- JOIN 连接
 
INNER JOIN: 返回匹配的行LEFT/RIGHT JOIN: 保留左/右表全部数据FULL OUTER JOIN: 返回所有匹配和不匹配的行CROSS JOIN: 笛卡尔积NATURAL JOIN: 自动按同名列连接(慎用)
- 子查询(嵌套查询)
 
- 标量子查询(返回单值,如 
SELECT (SELECT ...)) - 关联子查询(依赖外层查询,如 
EXISTS) IN,ANY,ALL,EXISTS的应用
四、数据控制
- 事务控制 (TCL)
 
COMMIT: 提交事务ROLLBACK: 回滚事务SAVEPOINT: 设置保存点- ACID 特性:原子性、一致性、隔离性、持久性
 
- 权限管理 (DCL)
 
GRANT: 授予权限(如GRANT SELECT ON table TO user)REVOKE: 撤销权限
五、高级功能
- 窗口函数
 
ROW_NUMBER(),RANK(),DENSE_RANK()OVER(PARTITION BY ... ORDER BY ...)
- 公共表表达式 (CTE)
 
WITH cte_name AS (SELECT ...): 临时结果集复用
- 索引优化
 
- 作用:加速查询(B-tree、Hash、全文索引)
 - 创建:
CREATE INDEX index_name ON table(column) - 注意:索引的维护成本和适用场景
 
- 存储过程 & 触发器
 
- 存储过程:预编译的SQL代码块(
CREATE PROCEDURE) - 触发器:自动执行的操作(如 
BEFORE INSERT) 
六、其他关键点
- 约束
 
PRIMARY KEY(主键)FOREIGN KEY(外键约束)UNIQUE(唯一值)NOT NULL(非空)CHECK(条件检查,如CHECK(age > 0))
- NULL 处理
 
IS NULL/IS NOT NULLCOALESCE(): 返回第一个非NULL值NULLIF(): 两值相等时返回NULL
- 性能优化
 
- 避免 
SELECT *,减少不必要的数据传输 - 使用 
EXPLAIN分析执行计划 - 警惕全表扫描和笛卡尔积
 
七、常见问题示例
- JOIN 与 WHERE 的执行顺序
 
 SELECT a.id, b.name   
  FROM table_a a  
LEFT JOIN table_b b ON a.id = b.a_id WHERE b.date > '2023-01-01';  
- 窗口函数排名
 
 SELECT name, salary,   
         RANK() OVER (ORDER BY salary DESC) as rank  
FROM employees;  
- 递归查询(树形结构)
 
 WITH RECURSIVE cte AS (  
SELECT id, parent_id FROM tree WHERE parent_id IS NULL UNION ALL SELECT t.id, t.parent_id FROM tree t JOIN cte ON t.parent_id = cte.id ) SELECT * FROM cte;  
掌握以上知识点后,可通过实际项目练习(如复杂查询优化、数据库设计)加深理解。注意不同数据库(MySQL、PostgreSQL、Oracle)的语法差异。
索引
在 SQL 中,有效创建和使用索引是优化查询性能的关键。以下是针对不同场景的索引设计策略、使用技巧以及避免全表扫描的实践方法:
一、有效创建索引的核心原则
- 选择需要索引的列
 
- 高频查询条件:为 
WHERE、JOIN、ORDER BY、GROUP BY中频繁出现的列创建索引。 - 高选择性列:列的取值越唯一(如主键、用户 ID),索引效果越好。
 - 避免过度索引:索引会占用存储并降低写入性能,只为必要的列创建。
 
- 复合索引的列顺序
 
- 最左前缀原则:复合索引 
(a, b, c)对a、a AND b、a AND b AND c生效,但对b或c单独查询无效。 - 高频列优先:将查询中使用频率更高的列放在复合索引左侧。
 - 排序优化:若需对某列排序(
ORDER BY),将该列放在复合索引中。 
- 索引类型选择
 
- B-tree 索引:默认类型,支持范围查询(
>、<、BETWEEN)和排序。 - Hash 索引:仅支持精确匹配(
=),适用于内存表或等值查询。 - 全文索引:针对大文本字段(如 
TEXT)的关键词搜索(如MATCH ... AGAINST)。 - 空间索引:用于地理数据(如 
GEOMETRY)。 
二、不同数据类型的索引优化
- 字符串类型(CHAR/VARCHAR/TEXT)
 
- 前缀索引:对长字符串(如 
VARCHAR(255))只索引前 N 个字符,节省空间。 
CREATE INDEX idx_name_prefix ON users (name(20)); -- 仅索引前 20 个字符  
``` - **全文索引**:适用于模糊搜索(如 `LIKE '%keyword%'`)。  
```sql  
CREATE FULLTEXT INDEX idx_content ON articles (content);  
- 数值类型(INT/FLOAT/DECIMAL)
 
- 直接使用 B-tree 索引,注意避免在查询中对字段进行运算。
 - 示例:
WHERE age + 1 > 20会导致索引失效,应改写为WHERE age > 19。 
- 日期与时间(DATE/DATETIME/TIMESTAMP)
 
- 常用于范围查询(如按时间段过滤),适合 B-tree 索引。
 - 避免函数操作:
 
-- 索引失效的写法  
SELECT * FROM orders WHERE YEAR(order_date) = 2023;  
-- 优化为范围查询  
SELECT * FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31';  
- 枚举类型(ENUM)或低基数列
 
- 基数低(如性别只有男/女)的列索引效果差,需谨慎使用。
 
- JSON/XML 类型
 
- 提取特定字段生成虚拟列,并为虚拟列创建索引(MySQL 支持):
 
ALTER TABLE products ADD COLUMN price DECIMAL(10,2) AS (JSON_EXTRACT(data, '$.price'));  
CREATE INDEX idx_price ON products (price);  
三、确保索引命中的关键技巧
- 避免索引失效的写法
 
- 在索引列上使用函数或计算:
 
-- 索引失效  
SELECT * FROM users WHERE UPPER(name) = 'ALICE';  
-- 优化:提前处理输入,保持列原始值  
SELECT * FROM users WHERE name = 'alice';  
``` - **隐式类型转换**:  
```sql  
-- 假设 user_id 是字符串类型,传入数字会导致索引失效  
SELECT * FROM users WHERE user_id = 123; -- 错误  
SELECT * FROM users WHERE user_id = '123'; -- 正确  
``` - **LIKE 左模糊匹配**:  
```sql  
-- 索引失效  
SELECT * FROM products WHERE name LIKE '%apple%';  
-- 仅右模糊可以使用索引  
SELECT * FROM products WHERE name LIKE 'apple%';  
- 利用覆盖索引(Covering Index)
 
- 索引包含查询所需的所有列,避免回表查询数据行。
 - 示例:
 
-- 创建覆盖索引  
CREATE INDEX idx_covering ON orders (user_id, total_amount);  
-- 查询只需访问索引  
SELECT user_id, total_amount FROM orders WHERE user_id = 100;  
- 强制使用索引(谨慎使用)
 
- 某些情况下优化器可能未选择最优索引,可手动指定:
 
SELECT * FROM users FORCE INDEX (idx_email) WHERE email = 'alice@example.com';  
四、避免全表扫描的实战方法
- 确保 WHERE 条件有索引
 
- 若查询未命中索引,会触发全表扫描。
 - 示例:
 
-- 无索引时全表扫描  
SELECT * FROM logs WHERE message LIKE 'error%';  
-- 添加索引后优化  
CREATE INDEX idx_message_prefix ON logs (message(50));  
- 避免 OR 连接多个条件
 
- 使用 
OR可能导致索引失效,可改写为UNION: 
-- 低效写法  
SELECT * FROM users WHERE age > 30 OR country = 'US';  
-- 优化为 UNION SELECT * FROM users WHERE age > 30      UNION   
    SELECT * FROM users WHERE country = 'US';  
- 分页查询优化
 
- 避免 
OFFSET过大时扫描大量数据,改用游标分页(基于有序列): 
-- 低效写法  
SELECT * FROM orders ORDER BY id LIMIT 1000 OFFSET 100000;  
-- 高效写法(记录上次查询的最后一个 id)  
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 1000;  
- 定期分析索引使用情况
 
- 使用数据库工具(如 MySQL 的 
EXPLAIN或SHOW INDEX)检查索引是否被命中: 
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';  
五、总结
- 索引是双刃剑:合理设计可提升查询速度,但过多索引会增加写入开销。
 - 理解数据分布:通过 
SELECT COUNT(DISTINCT column)/COUNT(*)计算列的选择性。 - 动态调整:随着数据量和查询模式变化,定期审查和优化索引。
 
通过以上策略,可以显著提高查询效率,减少全表扫描的发生。
B+树索引
1. B+ Tree 的核心结构
B+ Tree 是一种平衡多路搜索树,是 B Tree 的变种,广泛应用于数据库索引和文件系统。其核心特点如下:
1.1 B+ Tree 的层级结构
- 非叶子节点(索引节点)
 
- 仅存储键值(Key),不存储实际数据。
 - 每个节点包含 
n个键值和n+1个子节点指针,键值用于导航到子节点。 
- 叶子节点(数据节点)
 
- 存储键值和对应的数据(如数据行地址或完整数据)。
 - 所有叶子节点通过指针形成有序双向链表,支持高效的范围查询。
 
1.2 B+ Tree 的特性
- 平衡性:所有叶子节点位于同一层,保证查询稳定性。
 - 高扇出(Fan-out):每个节点可存储大量键值,降低树的高度。
 - 有序性:叶子节点链表天然支持顺序访问。
 
2. 为什么数据库选择 B+ Tree 做索引?
2.1 对比其他数据结构
- 哈希表
 
- 优点:等值查询 O(1) 时间复杂度。
 - 缺点:不支持范围查询,哈希冲突处理复杂。
 - B+ Tree 胜在支持范围查询和排序。
 
- 二叉搜索树(BST)
 
- 缺点:树高与数据量呈对数关系(O(log N)),数据量大时深度过高,导致磁盘 I/O 次数多。
 - B+ Tree 通过多路分支显著降低树高(如 3 层 B+ Tree 可存储千万级数据)。
 
- B Tree
 
- B Tree 的节点既存键值也存数据,而 B+ Tree 的数据仅存于叶子节点。
 - B+ Tree 的优势:
 - 非叶子节点不存数据,每个节点可容纳更多键值,进一步减少树高。
 - 叶子节点链表支持高效的范围查询和全表遍历。
 
2.2 适合磁盘存储的设计
- 磁盘 I/O 友好:
数据库数据通常存储在磁盘上,而磁盘访问以「块」为单位(如 4KB)。
B+ Tree 的节点大小通常设计为磁盘块大小,单次 I/O 可读取一个节点的全部键值,减少磁盘寻道次数。 - 示例:
假设每个节点存储 100 个键值,3 层 B+ Tree 可索引100^3 = 1,000,000条数据,仅需 3 次磁盘 I/O。 
2.3 高效的范围查询
- 叶子节点的双向链表允许快速遍历某个范围的数据(如 
WHERE id BETWEEN 100 AND 200)。 - B Tree 的范围查询需回溯父节点,效率低于 B+ Tree。
 
2.4 稳定的插入和删除性能
- 分裂与合并:B+ Tree 通过节点的分裂(插入时)和合并(删除时)维持平衡,操作复杂度为 O(log N)。
 - 与 LSM Tree 对比:LSM Tree 更适合写多读少场景(如日志系统),而 B+ Tree 在读多写少的 OLTP 场景中更优。
 
3. B+ Tree 的典型应用场景
- 数据库索引
 
- MySQL InnoDB 的主键索引(聚簇索引)直接存储数据行,二级索引存储主键值。
 - 范围查询(如 
ORDER BY、GROUP BY)依赖叶子节点的有序性。 
- 文件系统
 
- 如 NTFS、ReiserFS 使用 B+ Tree 管理文件和目录的元数据。
 
4. B+ Tree 的缺陷与应对
- 写放大问题
 
- 频繁的插入/删除可能导致节点分裂与合并,影响写入性能。
 - 优化:使用缓冲机制(如 InnoDB 的 Change Buffer)延迟写入操作。
 
- 内存依赖
 
- 若索引无法完全缓存到内存,频繁的磁盘 I/O 会降低性能。
 - 优化:通过增大内存或使用 SSD 提升 I/O 速度。
 
5. 总结:B+ Tree 的不可替代性
- 核心优势:
 
- 多路平衡设计减少磁盘 I/O;
 - 叶子节点链表支持高效范围查询;
 - 适合高并发随机读和顺序扫描。
 
- 现代数据库(如 MySQL、PostgreSQL)的默认选择,除非特定场景(如纯等值查询)才考虑哈希索引。
 
常用场景及SQL语句
以下是 20 个常见的 SQL 面试需求及实现语句,涵盖基本查询、聚合、连接、窗口函数等核心知识点,附详细注释:
1. 找出工资最高的前3名员工,并显示部门
WITH ranked_employees AS (  
 SELECT    e.name,   
    e.salary,   
    d.department_name,  
 ROW_NUMBER() OVER (ORDER BY e.salary DESC) AS rn FROM employees e JOIN departments d ON e.department_id = d.id)  
SELECT name, salary, department_name  
FROM ranked_employees  
WHERE rn <= 3;  
2. 统计每个部门的平均工资
SELECT   
  d.department_name,   
  AVG(e.salary) AS avg_salary  
FROM employees e  
JOIN departments d ON e.department_id = d.id  
GROUP BY d.department_name;  
3. 查找没有员工的部门
SELECT d.department_name  
FROM departments d  
LEFT JOIN employees e ON d.id = e.department_id  
WHERE e.id IS NULL;  
4. 找出工资高于部门平均工资的员工
SELECT   
  e.name,   
  e.salary,   
  d.department_name  
FROM employees e  
JOIN departments d ON e.department_id = d.id  
JOIN (  
 SELECT    department_id,   
    AVG(salary) AS avg_salary  
 FROM employees GROUP BY department_id) dept_avg ON e.department_id = dept_avg.department_id  
WHERE e.salary > dept_avg.avg_salary;  
5. 计算每个员工的工资排名(按部门分组)
SELECT   
  name,   
  department_name,  
 salary, RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dept_rankFROM employees e  
JOIN departments d ON e.department_id = d.id;  
6. 查询每个部门工资最高的员工
WITH dept_max AS (  
 SELECT    department_id,   
    MAX(salary) AS max_salary  
 FROM employees GROUP BY department_id)  
SELECT   
  e.name,   
  d.department_name,   
  e.salary  
FROM employees e  
JOIN departments d ON e.department_id = d.id  
JOIN dept_max dm ON e.department_id = dm.department_id AND e.salary = dm.max_salary;  
7. 找出入职时间最早的3名员工
SELECT   
  name,   
  hire_date  
FROM employees  
ORDER BY hire_date  
LIMIT 3;  -- MySQL  
-- SQL Server: SELECT TOP 3 ...  
-- Oracle: WHERE ROWNUM <= 3  
8. 查询工资高于经理的员工
SELECT   
  e.name AS employee_name,  
 e.salary AS employee_salary, m.name AS manager_name, m.salary AS manager_salaryFROM employees e  
JOIN employees m ON e.manager_id = m.id  
WHERE e.salary > m.salary;  
9. 统计每年入职的员工数量
SELECT   
  EXTRACT(YEAR FROM hire_date) AS hire_year,  
 COUNT(*) AS employee_countFROM employees  
GROUP BY hire_year  
ORDER BY hire_year;  
10. 删除重复记录(保留一条)
DELETE FROM employees  
WHERE id NOT IN (  
 SELECT MIN(id) FROM employees GROUP BY email  -- 假设 email 是唯一标识  
);  
11. 查询连续3天登录的用户
SELECT   
  user_id,  
 login_dateFROM (  
 SELECT    user_id,  
 login_date, LAG(login_date, 2) OVER (PARTITION BY user_id ORDER BY login_date) AS prev2_date FROM logins) t  
WHERE login_date = prev2_date + INTERVAL '2 days';  
12. 计算累计工资(按入职时间排序)
SELECT   
  name,  
 hire_date, salary, SUM(salary) OVER (ORDER BY hire_date) AS cumulative_salaryFROM employees;  
13. 查询每个部门的工资总和,并显示总占比
SELECT   
  department_name,  
 total_salary, total_salary / SUM(total_salary) OVER () AS salary_ratioFROM (  
 SELECT    d.department_name,  
 SUM(e.salary) AS total_salary FROM employees e JOIN departments d ON e.department_id = d.id GROUP BY d.department_name) dept_salary;  
14. 找出工资超过公司平均工资的部门
SELECT   
  department_name  
FROM (  
 SELECT    d.department_name,  
 AVG(e.salary) OVER () AS company_avg, AVG(e.salary) AS dept_avg FROM employees e JOIN departments d ON e.department_id = d.id GROUP BY d.department_name) t  
WHERE dept_avg > company_avg;  
15. 按工资区间统计员工数量(如 0-5000, 5001-10000)
SELECT   
  CASE   
    WHEN salary <= 5000 THEN '0-5000'  
 WHEN salary <= 10000 THEN '5001-10000' ELSE '10000+' END AS salary_range, COUNT(*) AS employee_countFROM employees  
GROUP BY salary_range;  
16. 查询每个部门工资第二高的员工
WITH ranked AS (  
 SELECT    department_id,  
 name, salary, DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rk FROM employees)  
SELECT   
  d.department_name,  
 r.name, r.salaryFROM ranked r  
JOIN departments d ON r.department_id = d.id  
WHERE rk = 2;  
17. 查询最近30天活跃的用户
SELECT   
  user_id,  
 last_active_dateFROM user_activity  
WHERE last_active_date >= CURRENT_DATE - INTERVAL '30 days';  
18. 将行数据转换为列(如按部门显示男女员工数量)
SELECT   
  department_name,  
 SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END) AS male_count, SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END) AS female_countFROM employees e  
JOIN departments d ON e.department_id = d.id  
GROUP BY department_name;  
19. 递归查询员工及其下属层级
WITH RECURSIVE employee_hierarchy AS (  
 SELECT    id,   
    name,   
    manager_id,  
 1 AS level FROM employees WHERE manager_id IS NULL  -- 假设顶层经理的 manager_id 为 NULL UNION ALL SELECT    e.id,   
    e.name,   
    e.manager_id,  
 eh.level + 1 FROM employees e JOIN employee_hierarchy eh ON e.manager_id = eh.id)  
SELECT * FROM employee_hierarchy;  
20. 分页查询(第4页,每页10条)
SELECT *  
FROM employees  
ORDER BY id  
LIMIT 10 OFFSET 30;  -- MySQL  
-- SQL Server: OFFSET 30 ROWS FETCH NEXT 10 ROWS ONLY  
-- Oracle: WHERE ROWNUM BETWEEN 31 AND 40  
提示
- 理解需求:明确问题中的过滤条件、排序规则和分组逻辑。
 - 优化性能:使用 
EXPLAIN分析执行计划,避免全表扫描。 - 注意语法差异:不同数据库(MySQL、PostgreSQL、Oracle)的分页、日期函数等可能不同。
 
SQL的测试
以下是SQL单元测试和集成测试的详细分步指南:
一、SQL单元测试
目标:验证单个SQL对象(存储过程、函数、视图、触发器等)的独立功能。
1. 选择测试框架
- 数据库专用工具:
- pgTAP(PostgreSQL):支持TAP协议的测试框架。
 - tsqlt(SQL Server):集成SQL Server的单元测试框架。
 - utPLSQL(Oracle):针对PL/SQL的测试框架。
 
 - 通用语言工具:
- Python:
unittest+pytest+ 数据库驱动(如psycopg2、sqlalchemy)。 - Java:
JUnit+Testcontainers+JDBC。 - Node.js:
Jest+node-postgres/mysql2。 
 - Python:
 
2. 测试用例设计
- 覆盖场景:
 - 正常输入验证(如正确参数返回预期结果)。
 - 边界条件(如空值、极值、超长字符串)。
 - 异常输入(如非法参数触发错误处理)。
 - 副作用验证(如触发器更新相关表)。
 
3. 实现步骤示例(以PostgreSQL + pgTAP为例)
步骤1:安装pgTAP
CREATE EXTENSION IF NOT EXISTS pgtap;  
步骤2:编写测试脚本
BEGIN;  
  
-- 测试存储过程:add_employee  
SELECT plan(2);  -- 计划运行2个测试  
  
-- 准备测试数据  
INSERT INTO departments (id, name) VALUES (1, 'IT');  
  
-- 测试1:添加员工并验证结果  
SELECT lives_ok(  
 $$CALL add_employee('Alice', 1, 50000)$$, '添加员工应成功'  
);  
  
-- 测试2:验证员工表是否插入新记录  
SELECT results_eq(  
 $$SELECT name FROM employees WHERE department_id = 1$$, ARRAY['Alice'], '员工表中应有Alice'  
);  
  
SELECT * FROM finish();  
ROLLBACK;  
步骤3:运行测试
pg_prove -d your_dbname tests/test_employees.sql```  
  
---  
  
#### **4. 最佳实践**  
- **隔离性**:每个测试用例在事务中运行,结束后回滚(`BEGIN`/`ROLLBACK`)。  
- **数据准备**:使用`INSERT`直接构造测试数据,或用`COPY`导入CSV。  
- **清理资源**:避免残留数据影响其他测试。  
  
---  
  
### **二、SQL集成测试**  
**目标**:验证多个SQL对象协同工作的正确性(如事务、跨表操作)。  
  
---  
  
#### **1. 测试场景设计**  
- **跨表事务**(如转账操作:账户A扣款,账户B加款)。  
- **复杂查询**(如多表JOIN后的聚合结果)。  
- **并发操作**(如同时读写时的锁机制)。  
  
---  
  
#### **2. 实现步骤示例(以Python + pytest为例)**  
**步骤1:安装依赖**  
```bash  
pip install pytest psycopg2-binary```  
  
**步骤2:编写测试用例**  
```python  
# test_transactions.py  
import pytest  
import psycopg2  
  
@pytest.fixture  
def db_connection():  
 conn = psycopg2.connect("dbname=testdb user=postgres") yield conn conn.close()  
def test_transfer_transaction(db_connection):  
 cursor = db_connection.cursor() # 初始化测试数据  
 cursor.execute("INSERT INTO accounts (id, balance) VALUES (1, 100), (2, 0)") db_connection.commit()  
 try: # 执行转账事务  
 cursor.execute("BEGIN") cursor.execute("UPDATE accounts SET balance = balance - 50 WHERE id = 1") cursor.execute("UPDATE accounts SET balance = balance + 50 WHERE id = 2") cursor.execute("COMMIT") except: cursor.execute("ROLLBACK")  
 # 验证结果  
 cursor.execute("SELECT balance FROM accounts WHERE id IN (1, 2) ORDER BY id") balances = cursor.fetchall() assert balances == [(50,), (50,)], "转账后余额应正确更新"  
步骤3:运行测试
pytest test_transactions.py -v```  
  
---  
  
#### **3. 高级技巧**  
- **测试容器化**:使用`Testcontainers`启动临时数据库。  
  ```python  
  from testcontainers.postgres import PostgresContainer  
  
 def test_with_container(): with PostgresContainer("postgres:13") as postgres: conn = psycopg2.connect(postgres.get_connection_url()) # 运行测试...  
 ```- **性能测试**:使用`EXPLAIN ANALYZE`验证查询计划。  
  ```sql  
  EXPLAIN ANALYZE SELECT * FROM large_table WHERE condition;  
三、持续集成(CI)集成
步骤1:配置CI文件(以GitHub Actions为例)
# .github/workflows/sql-tests.yml  
name: SQL Tests  
  
on: [push]  
  
jobs:  
 test: runs-on: ubuntu-latest services: postgres: image: postgres:13 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - 5432:5432 steps: - uses: actions/checkout@v2 - name: Install dependencies run: pip install pytest psycopg2-binary - name: Run tests env: DB_URL: postgresql://postgres:postgres@localhost:5432/postgres run: pytest -v  
四、工具推荐
| 工具 | 适用场景 | 特点 | 
|---|---|---|
| pgTAP | PostgreSQL单元测试 | 原生SQL语法,深度集成 | 
| tSQLt | SQL Server单元测试 | 可视化支持(SSMS插件) | 
| DBUnit | Java数据库测试 | XML/Excel管理测试数据 | 
| Great Expectations | 数据质量验证 | 支持复杂数据规则断言 | 
五、总结
- 单元测试:聚焦单一对象,快速反馈逻辑错误。
 - 集成测试:验证组件协作,确保数据一致性。
 - 自动化:通过CI/CD实现持续验证,降低回归风险。
 
存储过程
SQL 存储过程的定义与参数化详解
一、存储过程的定义
存储过程(Stored Procedure) 是预编译的 SQL 代码块,可接受输入参数、执行逻辑操作并返回结果。它通常用于封装复杂业务逻辑,提升代码复用性和安全性。
二、存储过程的基本语法(以常见数据库为例)
1. MySQL
DELIMITER //  
  
CREATE PROCEDURE procedure_name (  
 [IN | OUT | INOUT] parameter1 datatype, [IN | OUT | INOUT] parameter2 datatype)  
BEGIN  
 -- SQL 逻辑代码  
END //  
  
DELIMITER ;  
2. SQL Server
CREATE PROCEDURE procedure_name   
    @parameter1 datatype [ = default_value ],  
 @parameter2 datatype OUTPUTAS  
BEGIN  
 -- SQL 逻辑代码  
END  
3. PostgreSQL
CREATE OR REPLACE PROCEDURE procedure_name (  
 parameter1 datatype, INOUT parameter2 datatype DEFAULT NULL)  
LANGUAGE plpgsql  
AS $$  
BEGIN  
 -- SQL 逻辑代码  
END;  
$$;  
三、参数类型
| 类型 | 说明 | 
|---|---|
| IN | 输入参数(默认),调用时传入值,过程内部不可修改。 | 
| OUT | 输出参数,过程内部修改后返回给调用者。 | 
| INOUT | 输入输出参数,调用时传入初始值,过程可修改并返回新值。 | 
四、参数化存储过程示例
1. MySQL 示例:带输入参数的查询
DELIMITER //  
  
CREATE PROCEDURE GetEmployeeByDepartment (  
 IN dept_id INT)  
BEGIN  
 SELECT name, salary    FROM employees   
    WHERE department_id = dept_id;  
END //  
  
DELIMITER ;  
  
-- 调用  
CALL GetEmployeeByDepartment(2);  
2. SQL Server 示例:带输入和输出参数的统计
CREATE PROCEDURE GetDepartmentSalaryStats   
    @dept_id INT,  
 @avg_salary DECIMAL(10,2) OUTPUT, @max_salary DECIMAL(10,2) OUTPUTAS  
BEGIN  
 SELECT        @avg_salary = AVG(salary),  
 @max_salary = MAX(salary) FROM employees    WHERE department_id = @dept_id;  
END  
  
-- 调用  
DECLARE @avg DECIMAL(10,2), @max DECIMAL(10,2);  
EXEC GetDepartmentSalaryStats 2, @avg OUTPUT, @max OUTPUT;  
SELECT @avg AS AvgSalary, @max AS MaxSalary;  
3. PostgreSQL 示例:带 INOUT 参数的计数器
CREATE OR REPLACE PROCEDURE IncrementCounter (  
 INOUT counter INT DEFAULT 0)  
LANGUAGE plpgsql  
AS $$  
BEGIN  
 counter := counter + 1;END;  
$$;  
  
-- 调用  
DO $$  
DECLARE  
 cnt INT := 5;BEGIN  
 CALL IncrementCounter(cnt); RAISE NOTICE 'New Counter Value: %', cnt; -- 输出 6END $$;  
五、参数化的核心优势
- 防止 SQL 注入
参数化强制将输入值与 SQL 逻辑分离,避免恶意字符串拼接。 
-- 错误写法(易受注入攻击)  
CREATE PROCEDURE UnsafeSearch (IN keyword VARCHAR(100))  
BEGIN SET @sql = CONCAT('SELECT * FROM products WHERE name LIKE "%', keyword, '%"'); PREPARE stmt FROM @sql; EXECUTE stmt; END  
-- 正确写法(参数化过滤)  
CREATE PROCEDURE SafeSearch (IN keyword VARCHAR(100))  
BEGIN SELECT * FROM products WHERE name LIKE CONCAT('%', keyword, '%'); END  
- 
提升性能
存储过程预编译后减少解析时间,重复调用时效率更高。 - 
简化代码维护
业务逻辑集中存储在数据库,应用层只需调用接口。 
六、调用存储过程的常见方式
- 按位置传递参数
 
 -- MySQL/SQL Server  
CALL ProcedureName(1, 'Alice', @output);  
-- PostgreSQL CALL procedure_name(1, 'Alice');  
- 按名称传递参数
 
 -- SQL Server(命名参数)  
 EXEC ProcedureName @param1 = 1, @param2 = 'Alice';  
 
-- PostgreSQL(命名参数)  
 CALL procedure_name(param1 => 1, param2 => 'Alice');  
- 处理输出参数
 
 -- SQL Server  
DECLARE @result INT; EXEC ProcedureName @input = 10, @output = @result OUTPUT;  
-- MySQL SET @result = 0; CALL ProcedureName(10, @result); SELECT @result;  
七、注意事项
- 
参数数据类型匹配
确保传入参数的类型与定义一致,避免隐式转换错误。 - 
参数作用域
 
- 参数名避免与表字段名冲突。
 - 在存储过程内部修改 
IN参数的值(如 MySQL)可能导致不可预期行为。 
- 动态 SQL 中的参数化
使用EXECUTE或sp_executesql(SQL Server)时仍需绑定参数: 
-- SQL Server 动态 SQL 示例  
CREATE PROCEDURE DynamicSearch   
     @column_name NVARCHAR(100),  
@search_value NVARCHAR(100) AS BEGIN DECLARE @sql NVARCHAR(MAX); SET @sql = N'SELECT * FROM employees WHERE ' + QUOTENAME(@column_name) + ' = @value'; EXEC sp_executesql @sql, N'@value NVARCHAR(100)', @value = @search_value; END  
八、总结
- 定义存储过程:使用 
CREATE PROCEDURE并指定参数类型(IN/OUT/INOUT)。 - 参数化核心:分离数据与逻辑,提升安全性和性能。
 - 灵活调用:支持按位置或名称传递参数,处理输入输出值。
 
通过合理设计参数化存储过程,可显著优化数据库操作的安全性和效率。
PostgreSQL常用函数
以下是 PostgreSQL 中常用的 SQL 函数分类及示例,涵盖字符串处理、数值计算、日期时间、聚合、条件逻辑等核心操作:
一、字符串函数
| 函数 | 说明 | 示例 | 
|---|---|---|
CONCAT(str1, str2, ...) | 
连接字符串 | SELECT CONCAT('Hello', ' ', 'World') → 'Hello World' | 
SUBSTRING(str FROM start [FOR length]) | 
截取子串 | SELECT SUBSTRING('PostgreSQL' FROM 5 FOR 3) → 'gre' | 
LENGTH(str) | 
字符串长度 | SELECT LENGTH('abc') → 3 | 
UPPER(str) / LOWER(str) | 
大小写转换 | SELECT UPPER('test') → 'TEST' | 
TRIM([LEADING | TRAILING | BOTH] chars FROM str) | 
去除首尾字符 | SELECT TRIM(BOTH 'x' FROM 'xxSQLxx') → 'SQL' | 
POSITION(substr IN str) | 
子串位置 | SELECT POSITION('ql' IN 'PostgreSQL') → 9 | 
REPLACE(str, old, new) | 
替换子串 | SELECT REPLACE('foo bar', 'bar', 'baz') → 'foo baz' | 
LEFT(str, n) / RIGHT(str, n) | 
取左/右侧字符 | SELECT LEFT('database', 4) → 'data' | 
STRING_AGG(expr, delimiter) | 
分组字符串聚合 | SELECT STRING_AGG(name, ', ') FROM employees | 
二、数值函数
| 函数 | 说明 | 示例 | 
|---|---|---|
ROUND(num [, decimals]) | 
四舍五入 | SELECT ROUND(3.1415, 2) → 3.14 | 
CEIL(num) / FLOOR(num) | 
向上/向下取整 | SELECT CEIL(2.3) → 3 | 
ABS(num) | 
绝对值 | SELECT ABS(-10) → 10 | 
RANDOM() | 
生成 0~1 随机数 | SELECT RANDOM() → 0.1234... | 
POWER(base, exponent) | 
幂运算 | SELECT POWER(2, 3) → 8 | 
SQRT(num) | 
平方根 | SELECT SQRT(25) → 5 | 
MOD(n, m) | 
取模 | SELECT MOD(10, 3) → 1 | 
GENERATE_SERIES(start, end [, step]) | 
生成数值序列 | SELECT * FROM GENERATE_SERIES(1, 5) → 1,2,3,4,5 | 
三、日期与时间函数
| 函数 | 说明 | 示例 | 
|---|---|---|
CURRENT_DATE / CURRENT_TIME / NOW() | 
当前日期/时间 | SELECT NOW() → '2023-10-10 15:30:00' | 
EXTRACT(field FROM date) | 
提取日期部分 | SELECT EXTRACT(YEAR FROM NOW()) → 2023 | 
DATE_TRUNC('unit', date) | 
截断日期到指定精度 | SELECT DATE_TRUNC('month', NOW()) → '2023-10-01 00:00:00' | 
AGE(timestamp1, timestamp2) | 
计算时间差 | SELECT AGE('2023-10-10', '2000-01-01') → '23 years 9 mons 9 days' | 
TO_CHAR(date, format) | 
日期格式化 | SELECT TO_CHAR(NOW(), 'YYYY-MM-DD') → '2023-10-10' | 
INTERVAL 'value' | 
时间间隔 | SELECT NOW() + INTERVAL '1 day' | 
DATE_PART('field', date) | 
类似 EXTRACT | 
SELECT DATE_PART('dow', NOW()) → 1 (Monday) | 
四、聚合函数
| 函数 | 说明 | 示例 | 
|---|---|---|
COUNT(*) | 
统计行数 | SELECT COUNT(*) FROM employees | 
SUM(expr) | 
求和 | SELECT SUM(salary) FROM employees | 
AVG(expr) | 
平均值 | SELECT AVG(age) FROM users | 
MIN(expr) / MAX(expr) | 
最小/最大值 | SELECT MIN(price) FROM products | 
ARRAY_AGG(expr) | 
聚合为数组 | SELECT ARRAY_AGG(name) FROM departments | 
JSON_AGG(expr) | 
聚合为 JSON 数组 | SELECT JSON_AGG(name) FROM departments | 
五、条件逻辑函数
| 函数 | 说明 | 示例 | 
|---|---|---|
CASE WHEN ... THEN ... ELSE ... END | 
条件判断 | SELECT CASE WHEN age > 18 THEN 'Adult' ELSE 'Minor' END | 
COALESCE(val1, val2, ...) | 
返回第一个非 NULL 值 | SELECT COALESCE(NULL, 'default') → 'default' | 
NULLIF(a, b) | 
若 a = b 则返回 NULL | SELECT NULLIF(10, 10) → NULL | 
GREATEST(val1, val2, ...) | 
返回最大值 | SELECT GREATEST(3, 5, 1) → 5 | 
LEAST(val1, val2, ...) | 
返回最小值 | SELECT LEAST(3, 5, 1) → 1 | 
六、窗口函数
| 函数 | 说明 | 示例 | 
|---|---|---|
ROW_NUMBER() | 
行号 | SELECT ROW_NUMBER() OVER (ORDER BY salary) | 
RANK() / DENSE_RANK() | 
排名 | SELECT RANK() OVER (PARTITION BY dept ORDER BY salary) | 
LAG(expr [, offset]) / LEAD(expr [, offset]) | 
前/后行值 | SELECT LAG(salary) OVER (ORDER BY id) | 
SUM(expr) OVER (...) | 
窗口累加 | SELECT SUM(sales) OVER (ORDER BY date) | 
NTILE(n) | 
分桶 | SELECT NTILE(4) OVER (ORDER BY score) | 
七、JSON 处理函数
| 函数 | 说明 | 示例 | 
|---|---|---|
JSON_EXTRACT_PATH(json, path) | 
提取 JSON 值 | SELECT JSON_EXTRACT_PATH('{"a": {"b": 1}}', 'a', 'b') → '1' | 
JSONB_SET(jsonb, path, new_value) | 
修改 JSONB 字段 | SELECT JSONB_SET('{"a": 1}', '{a}', '2') → '{"a": 2}' | 
JSONB_ARRAY_ELEMENTS(jsonb) | 
展开 JSON 数组为行 | SELECT * FROM JSONB_ARRAY_ELEMENTS('[1, 2]') | 
TO_JSONB(expr) | 
转换为 JSONB | SELECT TO_JSONB(ROW(1, 'test')) | 
JSONB_PATH_EXISTS(jsonb, path) | 
检查 JSON 路径是否存在 | SELECT JSONB_PATH_EXISTS('{"a": 1}', '$.a') → true | 
八、其他实用函数
| 函数 | 说明 | 示例 | 
|---|---|---|
CAST(expr AS type) | 
类型转换 | SELECT CAST('123' AS INTEGER) → 123 | 
COALESCE | 
同前述条件逻辑 | |
GENERATE_SUBSCRIPTS(array, dim) | 
生成数组索引 | SELECT GENERATE_SUBSCRIPTS(ARRAY['a','b'], 1) → 1,2 | 
PG_TYPEOF(expr) | 
获取数据类型 | SELECT PG_TYPEOF(42) → integer | 
MD5(str) | 
计算 MD5 哈希 | SELECT MD5('text') → '1cb251ec0d568de6a929b520c4aed8d1' | 
使用示例模板
-- 示例:统计每个部门平均工资并格式化  
SELECT   
  department_id,  
 TO_CHAR(AVG(salary), '999,999.99') AS avg_salaryFROM employees  
GROUP BY department_id;  
  
-- 示例:递归查询树形结构  
WITH RECURSIVE tree AS (  
 SELECT id, parent_id, name FROM nodes WHERE parent_id IS NULL UNION ALL SELECT n.id, n.parent_id, n.name FROM nodes n JOIN tree ON n.parent_id = tree.id)  
SELECT * FROM tree;  
总结
- 按需选择函数:根据场景选择字符串、数值、日期等函数简化操作。
 - 组合使用:如 
STRING_AGG+OVER实现分组字符串聚合。 - 性能注意:JSON 处理函数可能影响查询效率,合理使用索引(如 GIN 索引优化 JSONB)。
 
PostgreSQL 官方文档是终极参考:Functions and Operators
Oracle常用函数
以下是 Oracle 数据库常用 SQL 函数分类及示例,涵盖字符串处理、数值计算、日期时间、聚合分析、条件逻辑等核心操作:
一、字符串函数
| 函数 | 说明 | 示例 | 
|---|---|---|
CONCAT(str1, str2) | 
连接两个字符串 | SELECT CONCAT('Hello', 'World') → 'HelloWorld' | 
SUBSTR(str, start [, length]) | 
截取子字符串 | SELECT SUBSTR('Oracle', 2, 3) → 'rac' | 
LENGTH(str) | 
返回字符串长度 | SELECT LENGTH('SQL') → 3 | 
UPPER(str) / LOWER(str) | 
大小写转换 | SELECT UPPER('test') → 'TEST' | 
TRIM([LEADING | TRAILING | BOTH] chars FROM str) | 
去除首尾字符 | SELECT TRIM('x' FROM 'xxSQLxx') → 'SQL' | 
REPLACE(str, old, new) | 
替换子字符串 | SELECT REPLACE('foo bar', 'bar', 'baz') → 'foo baz' | 
LPAD(str, len [, pad_str]) / RPAD | 
左/右填充字符串 | SELECT LPAD('7', 3, '0') → '007' | 
LISTAGG(expr, delimiter) WITHIN GROUP (ORDER BY ...) | 
分组字符串聚合 | SELECT LISTAGG(name, ', ') WITHIN GROUP (ORDER BY id) FROM employees | 
二、数值函数
| 函数 | 说明 | 示例 | 
|---|---|---|
ROUND(num [, decimals]) | 
四舍五入 | SELECT ROUND(3.1415, 2) → 3.14 | 
CEIL(num) / FLOOR(num) | 
向上/向下取整 | SELECT CEIL(2.3) → 3 | 
ABS(num) | 
绝对值 | SELECT ABS(-10) → 10 | 
MOD(n, m) | 
取模运算 | SELECT MOD(10, 3) → 1 | 
POWER(base, exponent) | 
幂运算 | SELECT POWER(2, 3) → 8 | 
SQRT(num) | 
平方根 | SELECT SQRT(25) → 5 | 
TRUNC(num [, decimals]) | 
截断数值 | SELECT TRUNC(3.1415, 2) → 3.14 | 
SIGN(num) | 
符号函数(-1/0/1) | SELECT SIGN(-5) → -1 | 
三、日期与时间函数
| 函数 | 说明 | 示例 | 
|---|---|---|
SYSDATE | 
当前系统日期时间 | SELECT SYSDATE FROM DUAL → 2023-10-10 15:30:00 | 
TO_DATE(str, format) | 
字符串转日期 | SELECT TO_DATE('20231010', 'YYYYMMDD') → 2023-10-10 | 
TO_CHAR(date, format) | 
日期格式化 | SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD') → '2023-10-10' | 
ADD_MONTHS(date, n) | 
添加月份 | SELECT ADD_MONTHS('2023-01-31', 1) → 2023-02-28 | 
MONTHS_BETWEEN(date1, date2) | 
计算月份差 | SELECT MONTHS_BETWEEN('2023-10-10', '2023-01-01') → 9.29 | 
LAST_DAY(date) | 
返回月份最后一天 | SELECT LAST_DAY('2023-02-15') → 2023-02-28 | 
EXTRACT(field FROM date) | 
提取日期部分 | SELECT EXTRACT(YEAR FROM SYSDATE) → 2023 | 
NEXT_DAY(date, weekday) | 
下一周某天的日期 | SELECT NEXT_DAY(SYSDATE, 'MONDAY') → 2023-10-16 | 
四、聚合函数
| 函数 | 说明 | 示例 | 
|---|---|---|
COUNT(*) | 
统计行数 | SELECT COUNT(*) FROM employees | 
SUM(expr) | 
求和 | SELECT SUM(salary) FROM employees | 
AVG(expr) | 
平均值 | SELECT AVG(age) FROM users | 
MIN(expr) / MAX(expr) | 
最小/最大值 | SELECT MIN(price) FROM products | 
STDDEV(expr) | 
标准差 | SELECT STDDEV(sales) FROM orders | 
VARIANCE(expr) | 
方差 | SELECT VARIANCE(salary) FROM employees | 
五、条件逻辑函数
| 函数 | 说明 | 示例 | 
|---|---|---|
CASE WHEN ... THEN ... ELSE ... END | 
条件判断 | SELECT CASE WHEN age > 18 THEN 'Adult' ELSE 'Minor' END | 
NVL(expr1, expr2) | 
替换 NULL 值 | SELECT NVL(NULL, 'N/A') → 'N/A' | 
NVL2(expr1, expr2, expr3) | 
三值逻辑替换 | SELECT NVL2(NULL, 'Not Null', 'Null') → 'Null' | 
COALESCE(expr1, expr2, ...) | 
返回第一个非 NULL 值 | SELECT COALESCE(NULL, 'default') → 'default' | 
DECODE(value, match1, result1, ..., default) | 
简单条件分支 | SELECT DECODE(gender, 'M', 'Male', 'F', 'Female', 'Unknown') | 
六、分析函数(窗口函数)
| 函数 | 说明 | 示例 | 
|---|---|---|
ROW_NUMBER() | 
行号 | SELECT ROW_NUMBER() OVER (ORDER BY salary) | 
RANK() / DENSE_RANK() | 
排名 | SELECT RANK() OVER (PARTITION BY dept ORDER BY salary) | 
LAG(expr [, offset]) / LEAD(expr [, offset]) | 
前/后行值 | SELECT LAG(salary) OVER (ORDER BY id) | 
SUM(expr) OVER (...) | 
窗口累加 | SELECT SUM(sales) OVER (ORDER BY date) | 
NTILE(n) | 
分桶 | SELECT NTILE(4) OVER (ORDER BY score) | 
FIRST_VALUE(expr) / LAST_VALUE(expr) | 
首/末行值 | SELECT FIRST_VALUE(name) OVER (PARTITION BY dept) | 
七、层次查询函数
| 函数 | 说明 | 示例 | 
|---|---|---|
CONNECT_BY_ROOT | 
根节点值 | SELECT name, CONNECT_BY_ROOT name AS root FROM employees START WITH manager_id IS NULL CONNECT BY PRIOR id = manager_id | 
LEVEL | 
当前层级 | SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 5 | 
SYS_CONNECT_BY_PATH | 
路径聚合 | SELECT SYS_CONNECT_BY_PATH(name, '/') FROM employees ... | 
八、其他实用函数
| 函数 | 说明 | 示例 | 
|---|---|---|
CAST(expr AS type) | 
类型转换 | SELECT CAST('123' AS NUMBER) → 123 | 
GREATEST(val1, val2, ...) | 
返回最大值 | SELECT GREATEST(3, 5, 1) → 5 | 
LEAST(val1, val2, ...) | 
返回最小值 | SELECT LEAST(3, 5, 1) → 1 | 
USER / UID | 
当前用户/用户 ID | SELECT USER FROM DUAL → 'SCOTT' | 
ROWNUM | 
返回行号(伪列) | SELECT * FROM employees WHERE ROWNUM <= 10 | 
使用示例模板
-- 示例:统计每个部门平均工资并格式化  
SELECT   
  department_id,  
 TO_CHAR(AVG(salary), '999,999.99') AS avg_salaryFROM employees  
GROUP BY department_id;  
  
-- 示例:递归查询员工层级  
SELECT   
  name,   
  LEVEL,   
  SYS_CONNECT_BY_PATH(name, ' -> ') AS hierarchy  
FROM employees  
START WITH manager_id IS NULL  
CONNECT BY PRIOR id = manager_id;  
总结
- 按需选择函数:根据场景选择字符串、数值、日期等函数简化操作。
 - 注意 Oracle 特性:如 
NVL、DECODE、层次查询函数(CONNECT BY)。 - 性能优化:分析函数(如 
ROW_NUMBER)可替代复杂子查询,提升效率。 
Oracle 官方文档是终极参考:SQL Functions.