← 返回主页

⚙️ SQL底层执行顺序

所有SQL报错排查和性能优化的根基。代码的书写顺序和机器的执行顺序是两码事。

✍️ 书写顺序: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
⚙️ 执行顺序:
  1. FROM - 找表
  2. WHERE - 单行过滤
  3. GROUP BY - 揉碎分堆
  4. HAVING - 淘汰数据堆
  5. SELECT - 提取列、起别名、派生计算
  6. ORDER BY - 排版排序
  7. 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 = '法师';               -- 第二步:对着拼好后的大表,一刀砍掉所有非法师玩家