先说点真话做数据分析项目那会儿有一次要从一张订单表里统计一共有多少个客户下过单。新人直接写了SELECT COUNT(客户id) FROM 订单表结果数字比实际客户数多了好几倍——因为同一个客户下了多笔单被重复算了。还有一次产品要看最近 10 笔已付款订单新人写了SELECT * FROM 订单表 WHERE 状态已付款结果一次性返回了几万行。这两个问题的解法就两个字去重DISTINCT和分页LIMIT。看着简单但组合起来坑不少。今天一次讲透。 「零基础学SQL」系列持续更新中关注我不迷路每篇都带标准练习数据复制就能跑。一、DISTINCT去掉重复行你会什么场景下用到它统计公司有几个部门——但员工表里部门名是重复的看有哪些客户下过单——但订单表里同一个客户出现多次检查有没有重复数据——先去重看看差多少基本写法-- 意图看看员工表里到底有几个部门SELECTDISTINCT部门FROM员工表;结果部门技术部销售部不加 DISTINCT 的话4 个员工会查出 4 行部门技术部、技术部、销售部、销售部加了就只留不重复的。多列去重-- 意图看看每个部门有哪些不同工资档位SELECTDISTINCT部门,工资FROM员工表;结果部门工资技术部9000.00销售部9200.00注意DISTINCT 是对整行去重不是只对第一列。(技术部, 9000)和(销售部, 9200)是两行不同的数据都保留。坑 1DISTINCT 和 COUNT 搭配想统计有几个部门很多人会写成两步先 DISTINCT 查出来再数行数。其实一步搞定-- 对COUNT 里直接嵌 DISTINCTSELECTCOUNT(DISTINCT部门)AS部门数FROM员工表;-- 结果2-- 错这样写等于先 COUNT 再去重语法不报错但逻辑完全不对SELECTDISTINCTCOUNT(部门)FROM员工表;-- 结果4先数了 4 行再去重还是 4毫无意义记住COUNT(DISTINCT 列)才是去重后计数DISTINCT COUNT(列)是无意义的写法。坑 2DISTINCT 对 NULL 的处理-- 意图看看任务表里备注字段有几种不同的值SELECTDISTINCT备注FROM任务表;结果备注完成需求评审修复线上bugNULLDISTINCT 会把所有 NULL 当作同一个值只保留一个 NULL 行。如果你不想让 NULL 出现在结果里加个 WHERE 过滤SELECTDISTINCT备注FROM任务表WHERE备注ISNOTNULL;二、LIMIT限制返回行数分页你会什么场景下用到它“只看前 3 条”——快速预览数据“分页显示每页 2 条看第 2 页”——列表展示“取工资最高的 2 个人”——和 ORDER BY 搭配基本写法-- 意图只看前 2 条员工数据SELECT姓名,部门,工资FROM员工表LIMIT2;结果姓名部门工资张三技术部9000.00李四技术部9000.00分页写法LIMIT OFFSET-- 意图每页 2 条看第 2 页跳过前 2 条SELECT姓名,部门,工资FROM员工表LIMIT2OFFSET2;结果姓名部门工资王五销售部9200.00赵六销售部9200.00公式LIMIT 每页条数 OFFSET (页码-1) × 每页条数第 1 页LIMIT 2 OFFSET 0OFFSET 0 可以省略第 2 页LIMIT 2 OFFSET 2第 3 页LIMIT 2 OFFSET 4坑 3LIMIT 不加 ORDER BY 每次结果可能不一样-- 危险写法不加 ORDER BY数据库不保证返回顺序SELECT姓名,工资FROM员工表LIMIT2;-- 这次可能是张三李四下次可能是王五赵六-- 安全写法先排序再取前 NSELECT姓名,工资FROM员工表ORDERBY工资DESCLIMIT2;-- 结果一定是工资最高的 2 个人姓名工资王五9200.00赵六9200.00记住LIMIT 必须和 ORDER BY 搭配才有稳定结果。不加 ORDER BY 的 LIMIT结果顺序是数据库心情决定的每次可能不一样。三、DISTINCT LIMIT 组合去重后取前 N场景想看工资从高到低排每个部门取第一个人——这个需要窗口函数后面单独讲。但去重后取前 N 条是可以的-- 意图去重后的部门只取第 1 个SELECTDISTINCT部门FROM员工表ORDERBY部门LIMIT1;-- 结果技术部执行顺序FROM → DISTINCT → ORDER BY → LIMIT。先去重再排序最后取前 N 条。四、速查卡建议收藏操作写法典型场景去重查列SELECT DISTINCT 列 FROM 表看有哪些不同的值去重计数SELECT COUNT(DISTINCT 列) FROM 表统计不重复的值有多少个多列去重SELECT DISTINCT 列1, 列2 FROM 表看组合不重复的行取前N条SELECT ... FROM 表 LIMIT N预览/取Top N分页SELECT ... FROM 表 LIMIT N OFFSET M列表分页展示排序后取前NSELECT ... FROM 表 ORDER BY 列 LIMIT N取最高/最低的N条三条铁律COUNT(DISTINCT 列)才是去重计数不要写DISTINCT COUNT(列)LIMIT 必须配 ORDER BY不然结果顺序不稳定DISTINCT 把多个 NULL 当作同一个值不想看 NULL 就加WHERE 列 IS NOT NULL五、练习题先自己写再看答案题目 1统计订单表里有多少个不同的员工下过单。题目 2查询订单表里金额最高的 2 笔订单显示订单id、订单金额、状态。题目 3查询任务表里备注字段有多少种不同的非空值。题目 4分页查询员工表每页 2 条按入职日期从早到晚排序写出第 2 页的 SQL。参考答案题目 1SELECTCOUNT(DISTINCT员工id)AS下单员工数FROM订单表;-- 结果3员工1、员工2、员工3员工4没下过单题目 2SELECT订单id,订单金额,状态FROM订单表ORDERBY订单金额DESCLIMIT2;-- 结果103(4000)、101(3000)题目 3SELECTCOUNT(DISTINCT备注)AS不同备注数FROM任务表WHERE备注ISNOTNULL;-- 结果2完成需求评审和修复线上bugNULL被排除题目 4SELECT姓名,部门,入职日期FROM员工表ORDERBY入职日期LIMIT2OFFSET2;-- 结果李四(2020-07-15)、张三(2019-03-01)... 按日期排王五(2018-01-10)、张三(2019-03-01)是第1页李四(2020-07-15)、赵六(2021-05-20)是第2页你站哪队学 SQL 的时候你是先把所有语法过一遍再动手型还是边写边查、踩坑了再记型我当年是后者——踩了无数坑才记住COUNT(DISTINCT 列)和DISTINCT COUNT(列)的区别。评论区聊聊你学 SQL 的方式我看看哪种人多。从零数据分析· 十年数据分析经验 · 实战笔记SQL 从入门到精通 28 篇持续更新中每篇附标准练习数据复制即运行。关注我把 SQL 学成肌肉记忆。 系列目录零基础学SQL从入门到精通完整目录28篇持续更新 上篇篇04 WHERE条件总写错AND/OR/IN一图搞懂 下篇预告篇06 数据增删改——INSERT/UPDATE/DELETE 的安全写法别把生产库删了附录标准练习数据复制即可运行全系列通用-- 全系列通用四张表每次运行先 DROP 再 CREATE不会报表已存在DROPTABLEIFEXISTS员工表;CREATETABLE员工表(员工idINTPRIMARYKEY,姓名VARCHAR(20),部门VARCHAR(20),工资DECIMAL(10,2),邮箱VARCHAR(50),手机VARCHAR(20),入职日期DATE);INSERTINTO员工表(员工id,姓名,部门,工资,邮箱,手机,入职日期)VALUES(1,张三,技术部,9000.00,zhangsandemo.com,13800000001,2019-03-01),(2,李四,技术部,9000.00,lisidemo.com,13800000002,2020-07-15),(3,王五,销售部,9200.00,wangwudemo.com,13800000003,2018-01-10),(4,赵六,销售部,9200.00,zhaoliudemo.com,13800000004,2021-05-20);DROPTABLEIFEXISTS订单表;CREATETABLE订单表(订单idINTPRIMARYKEY,员工idINT,订单金额DECIMAL(10,2),下单时间DATETIME,付款时间DATETIME,状态VARCHAR(20));INSERTINTO订单表(订单id,员工id,订单金额,下单时间,付款时间,状态)VALUES(101,1,3000.00,2026-01-10 10:00:00,2026-01-10 10:05:00,已付款),(102,1,2500.00,2026-02-15 14:00:00,2026-02-15 14:10:00,已付款),(103,3,4000.00,2026-03-20 09:30:00,2026-03-20 09:40:00,已付款),(104,2,1500.00,2026-04-05 16:00:00,NULL,待付款);DROPTABLEIFEXISTS用户表;CREATETABLE用户表(用户idINTPRIMARYKEY,姓名VARCHAR(20),手机VARCHAR(20),邮箱VARCHAR(50),地址VARCHAR(100));INSERTINTO用户表(用户id,姓名,手机,邮箱,地址)VALUES(1,张三,13900000001,zhangsandemo.com,北京市朝阳区),(2,李四,13900000002,lisidemo.com,上海市浦东新区),(3,王五,13900000003,wangwudemo.com,广州市天河区);DROPTABLEIFEXISTS任务表;CREATETABLE任务表(任务idINTPRIMARYKEY,员工idINT,备注VARCHAR(100),状态VARCHAR(20));INSERTINTO任务表(任务id,员工id,备注,状态)VALUES(1,1,完成需求评审,已完成),(2,2,NULL,进行中),(3,3,修复线上bug,已完成),(4,4,NULL,待分配);