第1章 SQL基础查询与数据探索:SELECT、WHERE、GROUP BY 怎么用才算“懂”

做特征工程这么多年,我见过太多人上来就写复杂窗口函数、搞标签交叉。其实,变量衍生的第一步从来不是炫技,而是老老实实把原始数据摸清楚。SQL 基础查询就是那双“摸数据的手”。你想想看,连表里有几个重复值、分布长啥样都不清楚,后面的分箱、归一化不就成了瞎折腾?

这一章,咱们就先把 SELECT、WHERE、GROUP BY 这三板斧练扎实。我会用实际项目里踩过的坑来帮你避开弯路。

1.1 SELECT —— 你每天写的第一个单词,用对了吗?

SELECT 看起来简单,但“简单”往往最容易出问题。我记得有一次帮团队排查上游数据接口,对方说“字段我都取出来了呀”,结果一查,他用的是 SELECT * 硬拉了整张 200 列的表,下游 ETL 跑了一个半小时。所以说,SELECT 的核心原则是:只取你需要的。

  • 取特定字段:SELECT user_id, age, income —— 减少 IO,提升速度。
  • 字段别名:SELECT age AS 年龄 —— 别笑,很多分析师不写别名,下游对接时一脸懵。
  • 常量列与计算列:SELECT 'v1.0' AS version, income / 12 AS monthly_income —— 变量衍生的起点。
  • 去重:SELECT DISTINCT user_id —— 检查唯一值数量,快速发现重复主键。
🔍 我的习惯:拿到一张新表,第一件事先 SELECT COUNT(*), COUNT(DISTINCT user_id) 看一眼。如果两个数不相等,说明这张表有重复记录,后面聚合前必须处理。

1.2 WHERE —— 让数据先“瘦身”,再干活

WHERE 不只是过滤条件那么简单。很多特征工程里,你需要在不同的子集上分别计算统计量,比如“高价值用户”和“普通用户”的消费均值差。这时候 WHERE 就是你的切割刀。

但注意:WHERE 的执行顺序在 GROUP BY 之前。这意味着你无法在 WHERE 里引用聚合后的结果。我刚开始做数据分析时就犯过这个错:

-- ❌ 错误写法:WHERE 不允许用聚合函数
SELECT user_id, SUM(amount) AS total
FROM orders
WHERE total > 1000   -- 这里会报错!
GROUP BY user_id;

正确的做法是用 HAVING 或在子查询里先聚合。不过这就是后面要讲的内容了。

这里列几个我常用的 WHERE 技巧:

场景写法示例注意
空值判断WHERE age IS NOT NULLNULL 用 IS,不能用 = NULL
日期范围WHERE create_date >= '2024-01-01'避免用函数包裹日期字段,会走不了索引
多条件组合WHERE status IN ('active','trial') AND plan_type = 'premium'IN 里面不要放太多值,超过 1000 建议用临时表
⚠️ 避坑指南:我曾经接手一个项目,上游把日期存成了字符串格式 '20240101'。结果业务方需要查上月数据,写了个 WHERE LEFT(date_str,6) = '202401'……全表扫描了 20 分钟。如果源头没法改,至少自己先转成日期类型:SELECT TO_DATE('20240101','YYYYMMDD')。

1.3 GROUP BY —— 特征工程真正的“炼丹炉”

GROUP BY 是 SQL 里聚合能力最强的操作之一。你想啊,用户的历史订单总金额、平均消费间隔、购买品类数……这些最常用的统计特征,统统离不开 GROUP BY。在我的理解里,GROUP BY 就是把同类数据“压”成一个特征行的过程。

常见的聚合函数:

  • COUNT(*) / COUNT(DISTINCT column) —— 订单笔数、活跃天数
  • SUM(column) —— 消费总额、停留时长
  • AVG(column) —— 客单价、评分均值
  • MAX(column) / MIN(column) —— 最早/最晚时间、极值偏差
  • STDDEV(column) / VARIANCE(column) —— 波动性特征,比如价格标准差

但很多人忽略了多字段分组的威力。比如你想生成“每个用户在每个产品类别的平均消费”,就得写 GROUP BY user_id, category。这其实是特征交叉的一种原始形态。

💡 案例:我之前做信贷风控,需要计算“用户最近 3 个月的借款笔数”。当时我写的是:
SELECT user_id, COUNT(*) AS loan_cnt_3m
FROM loan_table
WHERE apply_date >= DATE_SUB('2024-12-01', INTERVAL 3 MONTH)
GROUP BY user_id;
注意:WHERE 一定要先过滤时间窗口,再 GROUP BY。如果先 GROUP BY 再过滤,大数据量下效率差 10 倍以上。

1.4 结合数据探索:SQL 给你的三把“探照灯”

写特征之前,我习惯先用 SQL 输出几个“探照灯”结果:

  1. 分布概览:对每个连续字段做 MIN, AVG, MAX, STDDEV —— 看看有没有异常值。
  2. 空值排查:COUNT(*) - COUNT(age) 算空值数量 —— 决定是填充还是删除。
  3. 分组对比:GROUP BY label 看正负样本在各维度的均值差异 —— 快速发现区分度高的特征。

你想想看,这三个步骤走完,你基本就清楚这张表里哪些字段“能用”,哪些“得洗”。后面的特征衍生才有方向。

知识结构梳理

下面这张图概括了本章的核心流程。从原始表到探索性输出,每一步都在为特征工程铺路:

数据探索三阶段流程图 原始表(Raw Table) 所有字段、所有行 字段筛选 + 数据过滤 SELECT 选列 WHERE 行过滤 聚合输出 GROUP BY 聚合函数 HAVING 特征探索输出 分布统计 | 空值率 | 分组对比 ⚠️ 关键次序 WHERE 过滤 → GROUP BY 聚合 → HAVING 筛选聚合结果 绝对不要把 HAVING 能干的活放到 WHERE 里,反之亦然 每一步都可以作为特征衍生的“中间表”被下游消费

这个流程你可能已经写过无数次了,但每一次 SELECT 之前,不妨先问自己:我到底需要哪些字段?过滤条件够严格吗?聚合后要不要再丢一次异常值?这些都是经验活,写多了自然就有肌肉记忆。