⚙️ SQL底层执行顺序
所有SQL报错排查和性能优化的根基。代码的书写顺序和机器的执行顺序是两码事。
✍️ 书写顺序:
SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
⚙️ 执行顺序:
FROM- 找表WHERE- 单行过滤GROUP BY- 揉碎分堆HAVING- 淘汰数据堆SELECT- 提取列、起别名、派生计算ORDER BY- 排版排序LIMIT- 截取交付
📝 基础查询与别名
提取所需的列,并为派生列或原列赋予易读的别名。
SELECT 与 AS 别名
SELECT
name AS card_name, -- 重命名原列
attack * 2 AS burst -- 创建计算后的派生列
FROM cards;
🔍 WHERE vs HAVING
单行过滤与分组过滤的区别与配合使用。
- WHERE:分组前的"单兵筛选",不能跟聚合函数
- HAVING:分组后的"集体淘汰",专门过滤统计后的结果
WHERE + HAVING 组合拳
SELECT
class,
COUNT(*) AS card_count
FROM cards
WHERE attack > 2 -- 1. 先把基础攻击力<=2的弱牌踢掉
GROUP BY class -- 2. 按职业分堆
HAVING COUNT(*) > 5; -- 3. 淘汰掉优质牌总数不超过5张的职业
📊 排序与截断
ORDER BY控制排序方向,LIMIT限制返回行数。
ORDER BY + LIMIT
SELECT name, attack
FROM cards
ORDER BY attack DESC -- DESC: 降序 (从大到小); ASC: 升序 (默认)
LIMIT 10; -- 只取排在最前面的 10 行数据
🔢 聚合函数家族
用于SELECT或HAVING中,将多行数据压缩成一行统计结果。笔试高频坑点!
COUNT 系列用法
-- 1. 无脑数行数 (包含 NULL 的行也算)
SELECT COUNT(*) FROM cards;
-- 2. 只数非空值 (跳过该列为 NULL 的行)
SELECT COUNT(attack) FROM cards;
-- 3. 去重计数 (大厂最爱:统计有多少种不同的有效值)
SELECT COUNT(DISTINCT mana) FROM cards;
-- 其他常用聚合:
SUM(列名) -- 求和
MAX(列名) -- 求最大值
MIN(列名) -- 求最小值
AVG(列名) -- 求平均值
📝 字符串清洗函数
常用字符串处理函数,用于数据清洗和格式转换。
字符串处理
-- 拼接字符串
SELECT CONCAT(name, ' (', mana, '费)') FROM cards;
-- 截取字符串 (例:截取手机号前三位)
SELECT SUBSTRING(phone_num, 1, 3) FROM users;
-- 替换脏数据
SELECT REPLACE(content, '脏话', '***') FROM comments;
-- 长度判断 (注意大坑!)
CHAR_LENGTH('中国') -- 结果为 2 (字符数,业务中最常用)
LENGTH('中国') -- 结果为 6 (字节数,UTF-8下汉字占3字节)
📅 日期与时间函数
留存率/活跃度必考!
日期时间处理
-- 获取当前精准时间
SELECT NOW();
-- 计算日期差 (算次日留存必备)
SELECT DATEDIFF('2026-03-15', '2026-03-13'); -- 结果: 2
-- 格式化时间 (精确到秒的日志按月/按天聚合时使用)
-- 将 '2026-03-13 14:30:00' 转为 '2026-03'
SELECT DATE_FORMAT(login_time, '%Y-%m') FROM user_logs;
🎯 逻辑判断与兜底
空值处理和条件标签化,数据清洗的必备工具。
IFNULL + CASE WHEN
-- 空值处理 (如果为空,指定一个默认值参与计算,防止报错)
SELECT IFNULL(attack, 0) FROM cards;
-- CASE WHEN 标签化 (极其强大的打标签工具)
SELECT
name,
CASE
WHEN mana >= 7 THEN '高费大哥'
WHEN mana >= 4 THEN '中坚力量'
ELSE '低费随从'
END AS card_tag
FROM cards;
🏗️ CTE公共表表达式 (WITH)
大厂代码规范神器!拒绝深度嵌套的"俄罗斯套娃"代码,让SQL像流水线一样清晰。
WITH 子句模块化开发
WITH
-- 模块1:清洗异常数据
cleaned_data AS (
SELECT user_id, test_group
FROM user_logs
WHERE is_valid = 1
),
-- 模块2:基于清洗后的数据计算总数
group_count AS (
SELECT test_group, COUNT(user_id) AS total_users
FROM cleaned_data
GROUP BY test_group
)
-- 最终调用
SELECT * FROM group_count;
🔗 INNER JOIN 内连接
严苛的"双向奔赴",取两张表的交集。
核心逻辑:只有当两张表里都存在能够匹配上的线索时,这行数据才会被保留。只要有一方匹配不上,整行直接丢弃。
实战场景:找出"既注册了账号,又进行过实名认证"的用户。
INNER JOIN 示例
SELECT
u.user_id,
u.user_name,
c.cert_status
FROM user_info AS u -- 表A (左表):用户信息表,别名 u
INNER JOIN certification AS c -- 表B (右表):实名认证表,别名 c
ON u.user_id = c.user_id; -- 缝合线:两张表的 user_id 必须一模一样
💡 注:在日常敲代码时,如果只写
JOIN,数据库默认执行的就是 INNER JOIN。
⬅️ LEFT JOIN 左连接
无条件偏爱"左表",数据分析最常用!
核心逻辑:无论如何,左表(写在FROM后面的那张表)里的所有行都必须100%保留!
实战场景:老板要看"全服所有玩家的充值金额"。必须把所有玩家都列出来,即使有些玩家一分钱没充(充值表里找不到他),也要展示出来,金额显示为空即可。
LEFT JOIN 示例
SELECT
u.user_id,
u.user_name,
p.pay_amount
FROM user_info AS u -- 左表:必须包含全服所有玩家!
LEFT JOIN payment_logs AS p -- 右表:只有充过钱的玩家才在这里面
ON u.user_id = p.user_id; -- 缝合线
-- 结果:零氪玩家的 pay_amount 列会显示为 NULL
💡 黄金避坑指南:ON vs WHERE
淘汰率极高的面试陷阱!拼接表和过滤数据是两码事。
- ON:是用来拼接两张表的规则
- WHERE:是两张表已经拼成一张大宽表之后,再去进行整行过滤的砍刀
ON + WHERE 配合使用
-- 高阶实战:找出"所有法师玩家"的"本月游戏局数"
SELECT
u.player_name,
l.game_rounds
FROM user_info AS u
LEFT JOIN game_logs AS l
ON u.user_id = l.user_id -- 第一步:先不管什么职业,把人跟日志拼起来
WHERE u.class = '法师'; -- 第二步:对着拼好后的大表,一刀砍掉所有非法师玩家