1. 为什么SQL每日练习如此重要
SQL作为关系型数据库的标准查询语言,其重要性不言而喻。但很多开发者常犯一个致命错误:只在需要时才临时抱佛脚学习SQL语法。这种碎片化的学习方式导致在实际工作中遇到复杂查询时束手无策。
我见过太多这样的案例:一个看似简单的多表关联查询,因为对JOIN的理解不够深入,写出来的SQL性能极差;或者因为不熟悉窗口函数,用笨拙的子查询实现本可以优雅解决的问题。这些问题的根源都在于缺乏持续、系统的SQL训练。
每日一题的训练方式有几个独特优势:
- 保持对SQL语法的肌肉记忆
- 积累各种业务场景下的查询模式
- 培养解决复杂问题的思维模式
- 发现知识盲区并及时补足
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 今日题目解析:多条件筛选与聚合
假设我们有一个电商数据库,包含以下表结构:
sql复制CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
category VARCHAR(50),
price DECIMAL(10,2)
);
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
status VARCHAR(20)
);
CREATE TABLE order_items (
item_id INT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(10,2),
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
题目要求:找出2023年第一季度(1-3月)销售额排名前5的电子产品(category='Electronics'),且这些产品的订单完成率(status='Completed'的订单数/总订单数)不低于80%。
2.1 解题思路拆解
这个题目综合了多个SQL核心技能点:
- 时间范围筛选
- 多表关联查询
- 条件聚合
- 窗口函数排序
- 子查询过滤
正确的解决路径应该是:
- 先筛选时间范围内的订单
- 关联获取电子产品信息
- 计算每个产品的总销售额
- 计算每个产品的订单完成率
- 按销售额降序排序
- 筛选完成率符合条件的记录
- 取前5条结果
2.2 完整解决方案
sql复制WITH product_stats AS (
SELECT
p.product_id,
p.product_name,
SUM(oi.quantity * oi.unit_price) AS total_sales,
COUNT(DISTINCT CASE WHEN o.status = 'Completed' THEN o.order_id END) AS completed_orders,
COUNT(DISTINCT o.order_id) AS total_orders,
COUNT(DISTINCT CASE WHEN o.status = 'Completed' THEN o.order_id END) * 100.0 /
COUNT(DISTINCT o.order_id) AS completion_rate
FROM
orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE
o.order_date BETWEEN '2023-01-01' AND '2023-03-31'
AND p.category = 'Electronics'
GROUP BY
p.product_id, p.product_name
)
SELECT
product_id,
product_name,
total_sales,
completion_rate
FROM
product_stats
WHERE
completion_rate >= 80
ORDER BY
total_sales DESC
LIMIT 5;
3. 关键知识点详解
3.1 CASE WHEN在聚合中的妙用
计算完成率时,我们使用了CASE WHEN条件表达式:
sql复制COUNT(DISTINCT CASE WHEN o.status = 'Completed' THEN o.order_id END) AS completed_orders
这种写法比先过滤再COUNT更高效,因为:
- 只需扫描一次数据
- 可以同时计算多个指标
- 保持了查询结构的简洁性
3.2 CTE(Common Table Expression)的使用
我们使用了WITH子句创建临时结果集product_stats。CTE的优势在于:
- 提高查询可读性
- 避免重复子查询
- 支持递归查询(本示例未使用)
注意:在MySQL 8.0以下版本不支持CTE,可以用派生表替代。
3.3 聚合函数的精度处理
在计算完成率时,我们使用了* 100.0来确保得到小数结果:
sql复制COUNT(...) * 100.0 / COUNT(...) AS completion_rate
如果不加.0,在有些数据库中会进行整数除法,导致精度丢失。
4. 性能优化建议
4.1 索引设计
要使这个查询高效运行,建议创建以下索引:
- orders表的order_date和status字段的复合索引
- products表的category字段索引
- order_items表的order_id和product_id外键索引
sql复制CREATE INDEX idx_orders_date_status ON orders(order_date, status);
CREATE INDEX idx_products_category ON products(category);
4.2 执行计划分析
使用EXPLAIN分析查询计划时,重点关注:
- 是否使用了设计好的索引
- JOIN操作的顺序是否合理
- 是否有全表扫描操作
4.3 大数据量下的优化
当数据量很大时,可以考虑:
- 分区表:按时间范围分区orders表
- 物化视图:预计算常用聚合指标
- 查询重写:将复杂查询拆分为多个简单查询
5. 常见错误与排查
5.1 错误结果排查
如果查询结果不符合预期,可以:
- 逐步执行子查询,验证中间结果
- 检查JOIN条件是否正确
- 验证日期范围是否包含边界值
- 检查NULL值的处理是否恰当
5.2 性能问题排查
遇到查询缓慢时:
- 检查EXPLAIN输出
- 确认统计信息是最新的
- 检查锁等待情况
- 评估是否需要重构查询
6. 扩展练习建议
为了巩固这个题目涉及的知识点,建议尝试以下变体练习:
- 改为计算月度销售额趋势
- 增加按客户分组的分析
- 引入库存数据,计算库存周转率
- 使用窗口函数计算同类产品的销售排名
7. 实际业务中的应用场景
这类查询在实际业务中非常常见,比如:
- 电商平台的商品绩效报表
- 供应链管理的畅销品分析
- 市场营销活动的效果评估
- 财务部门的收入预测模型
掌握这种多条件聚合查询的技能,可以应对大多数业务分析场景的需求。
