1. 数据库查询进阶之路
记得刚接触数据库时,我连最简单的SELECT语句都写不利索。直到某次项目需要处理百万级数据,才发现基础查询根本不够用。那次经历让我明白,掌握高级查询技巧就像拿到数据库的万能钥匙,能让你在数据海洋中自由遨游。
2. 核心查询技术解析
2.1 多表连接的艺术
JOIN操作是数据库查询的基石。内连接(INNER JOIN)就像相亲,只有匹配的记录才会配对成功。左连接(LEFT JOIN)则像家长会,保证左表所有记录都能出席,右表匹配不上的就留空。
sql复制-- 典型的多表连接示例
SELECT o.order_id, c.customer_name, p.product_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
LEFT JOIN products p ON o.product_id = p.product_id
注意:多表连接时一定要明确关联条件,否则会产生笛卡尔积。我曾见过一个漏写ON条件的查询,把1000条订单和500个客户组合成了50万条结果。
2.2 子查询的妙用
子查询就像俄罗斯套娃,可以在SELECT、FROM、WHERE等各个位置嵌套。相关子查询尤其强大,它能引用外部查询的字段:
sql复制-- 找出销售额高于平均值的商品
SELECT product_name, sales_amount
FROM products p1
WHERE sales_amount > (
SELECT AVG(sales_amount)
FROM products p2
WHERE p2.category = p1.category
)
3. 窗口函数实战
3.1 排名与分页
ROW_NUMBER()、RANK()、DENSE_RANK()这三个函数就像比赛颁奖台,都能排序但处理并列的方式不同:
| 函数 | 并列处理 | 示例(成绩:100,100,90) |
|---|---|---|
| ROW_NUMBER | 不处理 | 1,2,3 |
| RANK | 跳过名次 | 1,1,3 |
| DENSE_RANK | 不跳名次 | 1,1,2 |
sql复制-- 分页查询标准写法
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER(ORDER BY sales DESC) AS rn
FROM products
) t
WHERE rn BETWEEN 11 AND 20
3.2 移动平均与累计
窗口函数最强大的特性是能定义帧(Frame),实现滑动窗口计算:
sql复制-- 计算7天移动平均销售额
SELECT
sales_date,
sales_amount,
AVG(sales_amount) OVER(
ORDER BY sales_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg
FROM daily_sales
4. 性能优化要点
4.1 执行计划解读
拿到慢查询时,第一件事就是看执行计划。重点关注:
- 全表扫描(Full Scan)警告
- 缺失的索引建议
- 高成本的排序操作
sql复制-- MySQL查看执行计划
EXPLAIN SELECT * FROM large_table WHERE create_date > '2023-01-01';
4.2 索引使用陷阱
不是所有索引都能被有效利用,常见失效场景:
- 对索引列使用函数:WHERE YEAR(create_date) = 2023
- 隐式类型转换:WHERE user_id = '123' (user_id是整数)
- 前导模糊查询:WHERE name LIKE '%张%'
5. 实战案例解析
5.1 电商数据分析
典型的多维度分析查询:
sql复制-- 各品类月度销售趋势
SELECT
category,
DATE_FORMAT(order_date, '%Y-%m') AS month,
SUM(amount) AS total_sales,
SUM(SUM(amount)) OVER(PARTITION BY category ORDER BY DATE_FORMAT(order_date, '%Y-%m')) AS cum_sum
FROM orders
GROUP BY category, DATE_FORMAT(order_date, '%Y-%m')
5.2 社交网络关系挖掘
使用递归CTE查找好友关系链:
sql复制WITH RECURSIVE friend_path AS (
-- 基础查询:找出直接好友
SELECT user_id, friend_id, 1 AS depth
FROM friendships
WHERE user_id = 123
UNION ALL
-- 递归查询:找出好友的好友
SELECT f.user_id, f.friend_id, fp.depth + 1
FROM friendships f
JOIN friend_path fp ON f.user_id = fp.friend_id
WHERE fp.depth < 3 -- 限制递归深度
)
SELECT * FROM friend_path;
6. 避坑指南
- NULL值处理:任何与NULL的比较结果都是NULL,要用IS NULL判断
- GROUP BY陷阱:SELECT中的非聚合字段必须出现在GROUP BY中
- 事务隔离级别:重复读(Repeatable Read)可能导致幻读问题
- 字符串编码:UTF8mb4才是真正的UTF-8,emoji存储必须用它
记得有次我写了个统计查询,GROUP BY了日期字段但SELECT中忘了加,结果在不同数据库表现迥异。这个教训让我养成了测试SQL在不同数据库兼容性的习惯。