第二章 SQL基础回顾:数据库、表、select查询归纳总结
说实话,每次带新人做团伙识别项目,总有人上来就写千行大SQL,结果连表结构都没看全。我自己的习惯是,不管多复杂的图谱特征衍生,先回去把SQL基础捋一遍。这一章我们就快速过一下数据库、表、SELECT这三样东西,不是给你讲语法手册,而是把项目里最常用、最容易踩坑的点拎出来。
核心提要:本章会覆盖关系数据库基本概念、表设计原则、SELECT查询执行顺序、常见函数以及多表关联。这些都是后续图谱特征衍生的地基。
2.1 数据库与表——不只是“存数据”
数据库是什么?说白了就是一个有组织的数据容器。在团伙识别场景里,我们通常用的就是关系型数据库(PostgreSQL、MySQL 居多)。表就是二维结构,行叫记录,列叫字段。
我记得有一次项目,合作方把电话号码存在同一个字段里,用逗号分隔。我问为什么不拆成单独的表?对方说“这样查询快”。嗯,你想想看,团伙识别要频繁做聚类、关联,这种反范式设计直接导致后续SQL写得像噩梦。所以我的建议是:遵循至少第三范式,针对分析场景合理冗余,但不要为了“省事”把多值塞进一个字段。
| 术语 | 解释 | 团伙识别中的例子 |
|---|---|---|
| 数据库(Database) | 数据集合的容器,对应一个项目或业务域 | gang_analysis 库 |
| 表(Table) | 具有相同结构的数据行集合 | call_records 表,存放通话记录 |
| 字段(Column) | 表的属性列 | caller_id, callee_id, duration |
| 记录(Row) | 表的一行数据 | 某一次通话的具体数据 |
建表的时候,主键、外键、索引是怎么设计的,直接影响后面图谱特征衍生性能。我见过有人把全表扫描当成常态,结果亿级表跑特征跑了三天。所以,花10分钟做好表设计,比后面花10小时调优划算得多。
避坑指南:我曾经在项目中直接用 SELECT * 从几十亿行的日志表里捞数据,结果把生产库搞挂了。记住:查询时永远只选你需要的列,不仅快,而且安全。
2.2 SELECT 查询——从数据到信息的桥梁
SELECT 语句是你与数据对话的工具。多数人只记得基本语法,但核心是理解它的执行顺序。很多人写得很长,结果数据量和预期完全不一样,就是没搞清楚 SQL 到底先干哪一步。
我习惯用这张顺序表来帮自己和团队记牢:
| 步骤 | 关键字 | 作用 |
|---|---|---|
| 1 | FROM + JOIN | 确定数据源,做表关联 |
| 2 | WHERE | 行级过滤,删掉不满足条件的记录 |
| 3 | GROUP BY | 分组,聚合计算的基础 |
| 4 | HAVING | 分组后过滤,对聚合结果做筛选 |
| 5 | SELECT 子句 | 投影列,计算表达式 |
| 6 | DISTINCT | 去重(实际上在SELECT之后执行) |
| 7 | ORDER BY | 排序 |
| 8 | LIMIT / OFFSET | 限制返回行数 |
注意:你写的 HAVING 条件里引用的别名,必须是在 SELECT 中已经定义过的,因为 HAVING 执行在 SELECT 投影之后;而 WHERE 里不能用聚合后的别名,因为那时还没聚合。这个坑,我见过不下五次。
来看一个团伙识别的简单例子:统计每个号码作为主叫的通话次数,只保留通话次数大于 5 的号码。
SELECT
caller_id,
COUNT(*) AS call_count
FROM call_records
WHERE callee_id IS NOT NULL
GROUP BY caller_id
HAVING COUNT(*) > 5
ORDER BY call_count DESC;
这个查询里,WHERE 先过滤掉无效被叫,然后按主叫分组,HAVING 只保留高频主叫,最后降序排列。每一步都按上面的顺序来的,心里有数,就不会犯「在 WHERE 里用聚合函数」之类的错。
小技巧:复杂查询可以先写 FROM + WHERE 测试行数,确认过滤逻辑正确,再加 GROUP BY 和聚合。我每次至少做两次这种分段验证。
2.3 常用函数与聚合——把数据转换成特征
团伙识别特征的源头,基本就是各种聚合函数、窗口函数以及字符串/日期函数。这里我只挑最常用的几个列个表,后面章节会深入讲它们的组合用法。
| 函数类别 | 函数示例 | 用途举例(团伙场景) |
|---|---|---|
| 聚合函数 | COUNT(), SUM(), AVG(), MAX(), MIN() | 统计每个节点出现的次数、金额总和、平均通话时长 |
| 字符串函数 | CONCAT(), SUBSTRING(), REPLACE() | 拼接识别号、截取地区编码、清洗脏数据 |
| 日期函数 | DATE_TRUNC(), EXTRACT(), DATEDIFF() | 按天/周聚合行为、计算周期 |
| 窗口函数 | ROW_NUMBER(), RANK(), LAG(), LEAD() | 排序、前后事件间隔、团伙内排名 |
注意:COUNT(*) 和 COUNT(1) 没区别,但 COUNT(column) 会忽略 NULL。写聚合前一定要确认字段里有没有 NULL,不然结果少一行都不知道。
2.4 多表关联——图谱的骨架
团伙识别本质就是发现实体间的关联。多表关联用得最多的就是 INNER JOIN 和 LEFT JOIN。我见过一个项目,用笛卡尔积 CROSS JOIN 做关联,结果数据直接指数级爆炸。所以,关联必须有明确的条件。
举个例子,把通话记录表和用户基本信息表关联,捞取主叫的注册地区:
SELECT
c.caller_id,
u.region,
COUNT(*) AS call_cnt
FROM call_records c
INNER JOIN user_info u ON c.caller_id = u.user_id
WHERE c.call_date >= '2024-01-01'
GROUP BY c.caller_id, u.region;
这个查询里,关联条件在 ON 里,过滤在 WHERE 里,分组在 GROUP BY 里,逻辑清晰。如果先 WHERE 再 JOIN,性能通常比先 JOIN 再 WHERE 差(大多数优化器会智能调整,但写清楚更不容易出错)。
小结:多表关联最核心的是搞清楚驱动表和被驱动表,以及连接键上的索引。没有索引的大表 JOIN,就是灾难。
这张流程图把 SELECT 语句里每一步的先后顺序标清楚了。后面我们做图谱特征衍生时,每一步想做什么、数据在经过哪一步后变成什么样子,心里就能有个谱。
我的习惯:在写任何复杂查询前,先在草稿纸上按这个流程画一遍。看起来笨,但能省掉至少一半的 debug 时间。