引言:我也曾卡在 “写 SQL” 上

作为秋招上岸的 Java 实习生,刚入职那周我慌得一批 —— 导师扔过来一个 “故障列表查询” 需求,我盯着 Navicat 半天,连 “怎么关联设备表查名称” 都想不明白。

后来翻了无数业务代码、整理了自己踩的坑,才发现实习生日常用的 SQL 根本不用 “炫技”,把基础语法 + 避坑点吃透,80% 的开发需求都能搞定。所以今天把这份 “实习生专属 SQL 笔记” 分享出来,希望能帮到刚入职、或即将入职的你~

一、SQL 基础:从 “会查” 到 “查对”(1 周吃透)

实习生最常做的就是 “查数据”—— 列表页、详情页、简单统计,核心就是SELECT相关语法,重点是 “精准” 和 “高效”。

1. 指定字段查询:别再用SELECT *了!

刚学 SQL 时总觉得SELECT *方便,但实际工作中会踩两个坑:

  • 浪费性能:如果表有 20 个字段,你只需要 3 个,查全字段会多传很多无用数据;
  • 字段变更报错:比如表新增了一个字段,SELECT *会把新字段也查出来,若 Java 实体没对应,MyBatis 映射直接报错。

正确示例(查询故障表关键信息,只查需要的字段):

SELECT fault_id, device_id, fault_time, fault_reason
FROM fault
WHERE fault_time >= '2024-01-01' AND device_id = 'DEV001';

2. 去重查询:DISTINCT解决 “重复数据”

场景:查 “所有出过故障的设备 ID”“有哪些故障类型”,避免重复值。

示例(查 1 月份所有出现故障的设备,不重复):

SELECT DISTINCT device_id
FROM fault
WHERE fault_time BETWEEN '2024-01-01' AND '2024-01-31';

3. 条件筛选:WHERE常用运算符

这是 “精准查数据” 的核心,我整理了实习生最常用的运算符,直接套模板就行:

运算符 用途 示例(故障查询场景)
= / != 等于 / 不等于 device_id = 'DEV001'
> / < / >= / <= 范围比较 fault_time >= '2024-01-01'
BETWEEN ... AND ... 闭区间匹配(含边界) fault_time BETWEEN '2024-01-01' AND '2024-01-31'
IN / NOT IN 多值匹配 device_id IN ('DEV001', 'DEV002')
LIKE 模糊查询(%匹配任意字符) fault_reason LIKE '%信号%'(查 “信号” 相关故障)
IS NULL / IS NOT NULL 空值判断 remark IS NOT NULL(查有备注的故障)

4. 排序与分页:列表页必备

  • 排序ORDER BY:列表页一般要 “最新数据在前”,用DESC(倒序);ID 升序用ASC(默认可省略)。示例(按故障时间倒序,最新故障排第一):

    sql

    SELECT fault_id, fault_time
    FROM fault
    ORDER BY fault_time DESC;
    
  • 分页LIMIT:一次性查太多数据会卡死接口,分页是必选项(MySQL 用LIMIT,Oracle 用ROWNUM,实习生先记 MySQL 的就行)。示例(查第二页数据,每页 10 条):

    sql

    SELECT fault_id, fault_time
    FROM fault
    ORDER BY fault_time DESC
    -- 两种写法:LIMIT 偏移量, 条数 / LIMIT 条数 OFFSET 偏移量
    LIMIT 10, 10; -- 偏移量=10(跳过前10条),条数=10(取10条)
    

二、数据操作:增删改的 “安全法则”(1 周熟练)

除了查询,实习生也会做简单的 “增删改”(比如新增故障记录、修改故障状态),核心是 “别搞崩数据”—— 这部分踩坑后果比查询严重多了!

1. 新增数据INSERT:推荐 “指定字段”

两种常用写法,批量新增效率更高,但不管哪种,都要指定字段(避免表字段顺序变了导致数据插错)。

  • 单条新增:

    INSERT INTO fault (device_id, fault_time, fault_reason, closedloop_state)
    VALUES ('DEV001', '2024-05-20 14:30:00', '信号中断', '未闭环');
    
  • 批量新增(比循环单条插快 10 倍):

    INSERT INTO fault (device_id, fault_time, fault_reason)
    VALUES 
    ('DEV001', '2024-05-20 14:30:00', '信号中断'),
    ('DEV002', '2024-05-20 15:00:00', '电源故障');
    

2. 修改数据UPDATE:必须加WHERE

重中之重:没加WHEREUPDATE会全表更新,比如你想改 “F001” 的状态,结果把所有故障的状态都改了 —— 我刚入职时差点这么干,幸好先在 Navicat 测了!

正确示例(修改指定故障 ID 的状态和更新时间):

-- 技巧:先写WHERE,再写SET,防止手抖
UPDATE fault 
SET closedloop_state = '已闭环', update_time = '2024-05-21 10:00:00'
WHERE fault_id = 'F001'; -- 精准定位,只改这一条

3. 删除数据DELETE:慎用!加WHERE

UPDATE同理,没WHERE会全表删除,而且删了可能恢复不了(除非有备份)。实习生一般只删 “无效数据”(比如过期的测试数据)。

示例(删除 1 个月前已闭环的无效故障):

DELETE FROM fault 
WHERE fault_time < '2024-04-20' AND closedloop_state = '已闭环';

三、进阶查询:搞定统计与跨表(2 周掌握)

学会基础后,会遇到 “统计每个设备的故障次数”“查故障时显示设备名称” 这类需求,需要聚合函数+GROUP BYJOIN

1. 聚合查询:COUNT/SUM/AVG+GROUP BY

常用聚合函数整理好了,直接套场景用:

函数 用途 示例(故障统计)
COUNT(*) 统计总行数(含 NULL) COUNT(*)(查故障总数)
COUNT(字段) 统计非 NULL 的字段数 COUNT(remark)(查有备注的故障数)
SUM(字段) 求和(数值型字段) SUM(fault_duration)(总故障时长)
AVG(字段) 求平均值 AVG(fault_duration)(平均故障时长)
MAX(字段) 最大值 MAX(fault_time)(最新故障时间)

分组统计示例(查 1 月份故障数 > 5 的设备,按设备 ID 分组):

SELECT device_id, COUNT(*) AS fault_count -- 别名fault_count,方便Java映射
FROM fault 
WHERE fault_time >= '2024-01-01'
GROUP BY device_id -- 按设备分组,每组统计一次
HAVING COUNT(*) > 5; -- 筛选分组结果(类似GROUP BY的WHERE)

2. 多表联合查询:INNER JOINLEFT JOIN

实习生最常用的两种连接,记住 “用左表还是内表” 的场景就行:

  • 内连接INNER JOIN:只查 “两张表都有匹配数据” 的行(比如查 “有故障记录且设备表存在的设备”)。示例(查故障 + 设备名称,只显示两边都有的数据):

    SELECT f.fault_id, f.device_id, d.device_name, f.fault_time
    FROM fault f -- 表别名f,简化写法
    INNER JOIN device d -- 关联设备表,别名d
    ON f.device_id = d.device_id; -- 关联条件:必须加!否则笛卡尔积(数据爆炸)
    
  • 左连接LEFT JOIN:左表(比如 fault)的所有行都显示,右表(比如 device)没匹配的显示NULL(场景:“即使设备表没数据,也要显示故障记录”)。示例(确保所有故障都显示,无设备匹配时 device_name 为 NULL):

    SELECT f.fault_id, f.device_id, d.device_name
    FROM fault f
    LEFT JOIN device d
    ON f.device_id = d.device_id;
    

3. 简单子查询:先查 ID,再查详情

场景:“先查未闭环的故障 ID,再查这些故障的详情”—— 不用写复杂关联,子查询更直观。

示例:

SELECT fault_id, device_id, fault_time
FROM fault
WHERE fault_id IN (
    -- 子查询:先查未闭环的故障ID
    SELECT fault_id 
    FROM fault 
    WHERE closedloop_state = '未闭环'
);

四、实习生不用慌:这些复杂 SQL 暂时不用学!

刚入职时我总担心 “不会复杂 SQL 会不会被骂”,后来发现根本没必要 —— 这些内容多是数据分析师、高级后端用的,实习生不用浪费精力:

  • 复杂子查询(多层嵌套、关联子查询);
  • 窗口函数(ROW_NUMBER()RANK(),用于复杂排名);
  • 存储过程、函数(自定义 SQL 逻辑,维护成本高);
  • 事务、锁(框架会处理,不用手动写BEGIN/COMMIT);
  • 数据库优化(索引、执行计划,资深开发负责)。

五、避坑指南:比语法更重要的 4 件事!

我踩过的坑,希望你别踩 —— 这些细节比 “会不会写 SQL” 更影响工作效率(甚至避免背锅)。

1. 防 SQL 注入:用#{}代替${}

这是 Java 实习生必须记的!MyBatis 中:

  • #{}:预编译参数,会把参数当成 “值” 处理,比如传入'1 OR 1=1',会当成字符串,避免注入;
  • ${}:直接拼接字符串,比如传入'1 OR 1=1',会变成WHERE id = 1 OR 1=1,查所有数据,风险极高!

正确示例(MyBatis 映射文件):

<!-- 正确:用#{deviceId} -->
<select id="getFaultByDeviceId" parameterType="String" resultType="Fault">
    SELECT * FROM fault WHERE device_id = #{deviceId}
</select>

2. 字段别名:适配 Java 驼峰命名

数据库字段一般是下划线(fault_id),Java 实体是驼峰(faultId),如果不写别名,MyBatis 会映射失败(查不到faultId)。

正确示例(用AS取别名):

SELECT fault_id AS faultId, device_id AS deviceId, fault_time AS faultTime
FROM fault;

3. 测试 SQL:先在工具里跑通!

别写完 SQL 就直接贴到代码里 —— 先在 Navicat/DBeaver 中执行,看结果是否符合预期:

  • 分页是否正确(比如第二页是不是从第 11 条开始);
  • 关联查询有没有数据(比如左连接是不是所有故障都显示);
  • 统计结果对不对(比如故障数是不是和手动数的一致)。

我之前没测试就上线,结果分页查出来的是重复数据,接口联调时才发现,浪费了半天时间。

4. 写 UPDATE/DELETE:先写WHERE再写其他!

再强调一次 —— 防止手抖删全表:

  • UPDATE时:先写WHERE fault_id = 'F001',再写UPDATE fault SET ...
  • DELETE时:先写WHERE ...,再写DELETE FROM ...

六、我的学习路径:2 周搞定日常 SQL!

最后给个实习生友好的学习计划,按这个来,不用焦虑:

  1. 第 1 周:熟练SELECT基础(筛选、排序、分页)+ INSERT/UPDATE/DELETE,能独立写列表查询、简单增删改;
  2. 第 2 周:掌握COUNT/GROUP BY+JOIN+ 简单子查询,能看懂业务代码中的关联查询,自己写统计 SQL;
  3. 实践:结合公司业务练手,比如 “写一个故障列表接口(带时间筛选、分页、关联设备名称)”—— 边用边记,比死记语法快 10 倍。

结尾:SQL 学习不用 “炫技”,正确安全就好!

作为实习生,我们写 SQL 的核心目标是 “正确拿到数据、不搞崩数据”,不用追求 “写出别人看不懂的复杂 SQL”。遇到不会的,先翻公司现有业务代码(复制模板改条件),但一定要先在数据库工具(Navicat/DBeaver)中跑通写好的 SQL 不要直接上线,先在工具中执行,看结果是否符合预期(比如分页是否正确、关联是否有数据),避免接口联调时才发现 SQL 错误。

再不行就问导师 / 同事 —— 大家都经历过 “不会 SQL” 的阶段,没人会嘲笑你~

如果这篇笔记帮到你了,欢迎在评论区交流你的 SQL 学习心得;如果有遗漏的避坑点,也欢迎补充~ 一起做 “不踩坑的 Java 实习生”!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐