资讯动态

MySQL数据查询四层结构:从单表到多表与子查询

发布时间:2026/10/27 19:24:21 来源:尧图企业网站定制
简介面向学习MySQL数据库应用、需要完成数据查询实验训练的学生与自学者这份PDF文档以汽车用品网上商城为统一案例系统梳理字段查询、多条件查询、DISTINCT去重、ORDER BY排序、GROUP BY分组及COUNT、SUM、AVG、MAX、MIN等聚合函数操作并延伸至内连接、外连接、复合条件连接以及IN、比较运算符、EXISTS嵌套查询。文档按“实验目标—分析思路—SQL实现”组织每个实验均说明涉及的表、字段与条件关系分析部分强调去重原因、分组依据与连接键选择便于对照练习和复盘查询逻辑也适合整理实验报告时参考其分析表述。包内共1个PDF文件大小约1.75MB轻量便携适合课前预习、课中跟练和课后查漏补缺。该资料已有3338人学习读者可借此掌握从单表检索到多表关联的完整查询路径并积累条件组合、结果去重、分组统计与子查询的排错经验。1. 数据查询的地图从单表到多表一条 SELECT 的四层结构拿到一句查询需求最容易卡住的地方往往不是语法写不出来而是不知道该先想什么先想表、先想条件、还是先把结果长什么样画出来。MySQL 数据库应用里的数据查询这一块看着就是 SELECT 后面不停加关键字实际是四层结构叠起来的——单表筛、聚合分、多表连、子查询套。前一层没想明白后一层写出来基本靠碰运气。这份笔记把实验训练里的十九个查询重新归到这四个层次上标清每层的判断依据、常见写法和容易翻车的位置适合正在系统学 SQL、或者临考要把数据查询快速过一遍的人按图索骥。2. 单表查询字段、条件、去重、排序四个动作单表查询是整套数据查询的底座。实验里前八个需求看似各管一摊其实共用同一个判断流程先看需求里出现哪些字段再看这些字段落在哪张表上如果只落在一张表就不用考虑连接然后才是挑条件、挑去重方式、挑排序规则。这个流程走顺了后面的聚合、连接、子查询都只是在这套流程上再加一层。把实验场景里的表先摆出来后面所有例子都基于它表名存什么关键字段goods 商品表商品信息goods_id、goods_name、cat_id、shop_price、is_promote、goods_numbercategory 类别表商品类别cat_id、cat_nameusers 用户表商城会员user_id、user_name、create_timeorders 订单表订单主信息order_id、user_id、order_total、order_dateorder_detail 订单明细表订单中的商品行order_id、goods_id、goods_numcomments 评论表商品评论comment_id、user_id、goods_id、contentcart 购物车表用户购物车user_id、goods_id、goods_num2.1 字段查询定位表比写 SELECT 更难需求里说“查询商品名称为挡风玻璃的商品信息”判断依据只有一条商品名称这个字段在商品表里输出要求也是商品信息那么整条语句只需要一张表。同理查询 ID 为 1 的订单订单信息全在订单表用户 ID 也在这张表里不需要再去连用户表。-- 条件列和输出列都在 goods 表单表即可完成 SELECT goods_id, goods_name, cat_id, shop_price FROM goods WHERE goods_name 挡风玻璃; -- 订单表自带 user_id查订单不需要连用户表 SELECT order_id, user_id, order_total, order_date FROM orders WHERE order_id 1;第一段代码演示的是“条件列决定 FROM”这个思路WHERE 里用到 goods_name输出又要商品明细于是 FROM 只能是 goods。第二段说明另一个判断习惯——外键字段已经在表里并不等于一定要连表只有当需求要输出另一张表的字段时连接才有必要。SELECT *在实验里能用但写作业之外的场景最好把列名列全一来结果稳定二来表结构变动时不会突然多出几列把前端搞崩。字符串条件一定用单引号包住数字类型的列不要加引号混着写会触发隐式类型转换索引直接失效这类问题排查起来相当玄学。2.2 多条件查询AND 的优先级与条件的写法“查询所有促销的价格小于 1000 的商品”有两个条件是否促销、价格。两个条件都在商品表内属于典型的多重条件查询用 AND 连接即可。-- is_promote 用 1 表示促销两个条件必须同时满足 SELECT goods_id, goods_name, shop_price FROM goods WHERE is_promote 1 AND shop_price 1000;这段的关键在于把“同时满足”翻译成 AND而不是把两个条件写成两个 SELECT。布尔型字段在表里通常存成 tinyint写is_promote 1比写is_promote 是稳得多后者在部分字符集下会静默返回空结果。价格这类数值列用、、BETWEEN都行BETWEEN 0 AND 1000包含边界和shop_price 1000等价但和 1000差一个端点值考试和实际业务里都常在这一个等号上翻车。AND 和 OR 混用时优先级是最容易忽略的点AND 先于 OR 计算。写“促销且价格低于 1000或者类别为 3”如果不加括号语义会变成“促销且价格低于 1000 或类别为 3”。稳妥做法是任何混用都加括号宁可多打两个字符也别指望读代码的人替你脑补优先级。2.3 DISTINCT去重去的是整行不是某一列“查询所有对商品 ID 为 1 发表过评论的用户 ID”一个用户可以评论多次直接查会得到重复的 user_id所以要 DISTINCT。这里有个概念要钉死DISTINCT 作用在 SELECT 的整个列表上不是只作用在紧挨着它的那一列。-- 一个用户评论多行去重后每个用户只剩一行 SELECT DISTINCT user_id FROM comments WHERE goods_id 1;如果写成SELECT DISTINCT user_id, comment_id因为 comment_id 本身唯一整行永远不重复DISTINCT 等于白写结果和没加一样。这是很多人第一次用 DISTINCT 时的血泪经验以为在给第一列去重其实是在给整行去重。第二个场景是查询会员创建时间段1 年为一段。用户表里每年会有多个会员直接列年份必然重复用 DISTINCT 包住 YEAR() 函数就能得到去重后的年份集合。-- 先截取年份再对结果集去重最后按年份排一下更好读 SELECT DISTINCT YEAR(create_time) AS create_year FROM users ORDER BY create_year;写法上要注意函数的位置DISTINCT YEAR(create_time)是对函数结果去重YEAR(DISTINCT create_time)是对精确到秒的时间去重、几乎没意义两者结果差得很远。另外 DISTINCT 天然带一次排序或哈希去重的开销数据量大时比 GROUP BY 慢能明确分组维度时优先用 GROUP BY。2.4 ORDER BY默认升序、多列、NULL 的位置排序需求有两个要素排哪一列、升序还是降序。不写方向时默认 ASC 升序降序必须显式写 DESC这一点实验里两个例子正好对照。-- 类别为 1 的商品按商品 ID 从大到小 SELECT goods_id, goods_name, shop_price FROM goods WHERE cat_id 1 ORDER BY goods_id DESC; -- 今年新增会员按用户名升序YEAR 比较避免写死具体年份 SELECT user_id, user_name, create_time FROM users WHERE YEAR(create_time) YEAR(CURDATE()) ORDER BY user_name ASC;第一段说明 ORDER BY 永远写在 WHERE 之后、LIMIT 之前执行顺序上它作用在筛选完的结果集上。第二段有两个细节一是“今年”这种相对时间不要在 SQL 里写死年份用YEAR(CURDATE())这类函数表达跨年运行时结果才正确二是文本列排序按字符集排序规则来中文和英文混排时的顺序依赖 collation不是简单的拼音序遇到结果“看着不对”先查排序规则别急着怀疑语句。多列排序的写法是ORDER BY 列1 DESC, 列2 ASC前序列相同才轮到后序列。NULL 在排序里的位置各数据库不一致MySQL 中升序时 NULL 排在最前需要固定顺序时用ORDER BY 列 IS NULL, 列这类写法显式控制。排序字段如果没有索引大数据量下会走文件排序这是慢查询里出现频率很高的一类原因。3. 聚合与分组GROUP BY 和五个聚合函数的配合单表筛选解决的是“取哪些行”聚合解决的是“把这些行压成几个数”。实验里 COUNT、SUM、AVG、MAX、MIN 五个函数加上 GROUP BY构成了统计类需求的全部套路。这一块的难点不在函数名而在分组粒度和 NULL 的处理分组粒度错了结果会多出一堆行NULL 处理错了数字会偏大或偏小而且两条都不会报错只能靠对结果。3.1 五个聚合函数的分工与返回值函数作用对 NULL 的处理实验里的典型场景COUNT(*)数行数统计所有行含全 NULL 行每天的接单数COUNT(列)数该列非空值个数跳过 NULL类别数量SUM(列)求和跳过 NULL全 NULL 返回 NULL每天销售额、用户消费总额AVG(列)求平均跳过 NULL分母是非空行数所有订单平均销售金额MAX(列)最大值跳过 NULL商品数量最大者、名字最靠前者MIN(列)最小值跳过 NULL商品价格最低者MAX 和 MIN 不只能用于数字文本列按排序规则取字母序最靠前或最后的值日期列取最早或最晚的时间实验里“按字母排序中名字最靠前者”考的就是这个点。COUNT(*) 与 COUNT(列) 的差别要单独记前者数行后者数非空值某列有 NULL 时两者结果不同而这类差异在数据量小的时候很难被发现。-- 类别数量COUNT(*) 与 COUNT(cat_id) 在主键列上结果一致 SELECT COUNT(*) AS cat_cnt FROM category; -- 商品数量最大者和价格最低者两个聚合可以写进同一条语句 SELECT MAX(goods_number) AS max_num, MIN(shop_price) AS min_price FROM goods;两段代码说明不含 GROUP BY 的聚合查询整张表就是一组结果永远只有一行这一行里可以并排放多个聚合函数因为它们的分组粒度相同。要留意 SUM 和 AVG 在空表或无匹配行时的返回SUM 返回 NULL 而不是 0前端如果直接做加法会变成 NaN需要 COALESCE 兜底。3.2 GROUP BY 的执行顺序先分组再聚合“查询每个用户的消费总金额”这句话里“每个”就是分组信号。订单表里一行是一个订单同一个用户有多行先按 user_id 分组再对组内的 order_total 求和。-- 先按用户分组再把组内订单总价相加 SELECT user_id, SUM(order_total) AS total_amount FROM orders GROUP BY user_id;理解这段的关键是执行顺序FROM 取表 → WHERE 过滤行 → GROUP BY 分组 → 聚合函数计算 → SELECT 输出列 → ORDER BY 排序。因为聚合发生在 SELECT 之前所以 SELECT 列表里除了聚合函数只能出现 GROUP BY 里列过的列。写了没分组的列在严格模式下直接报错在宽松模式下会从组内随机挑一行的值返回结果看起来正常但语义是错的这是最容易留下隐患的写法。3.3 多列分组与 HAVING 的位置“查询类别价格一样的各种商品数量总和”比上一个例子复杂分组依据是类别和价格两列两列都相同才算一组这叫多列分组。-- 类别 价格两列组合决定分组统计每组商品数量 SELECT cat_id, shop_price, COUNT(*) AS goods_cnt FROM goods GROUP BY cat_id, shop_price;如果把 GROUP BY 只写 cat_id结果就变成“每个类别一个数”与需求里的“类别价格一样”完全不匹配。多列分组的判断方法是把需求里“每个 X 的 Y”读出来X 有几个维度就分几列缺一列粒度就粗了。分组之后要过滤组就得用 HAVING。WHERE 过滤的是行、执行在分组之前HAVING 过滤的是组、执行在聚合之后两者位置不能互换。-- WHERE 先筛出有效订单GROUP BY 再分组HAVING 最后筛组 SELECT user_id, SUM(order_total) AS total_amount FROM orders WHERE order_date 2024-01-01 GROUP BY user_id HAVING SUM(order_total) 500 ORDER BY total_amount DESC;这段把四个子句的先后关系串起来了。判断某个条件该放 WHERE 还是 HAVING看它依赖什么只依赖单行的列放 WHERE依赖聚合结果放 HAVING。把SUM(order_total) 500写进 WHERE 会直接报错因为分组还没发生聚合值不存在。3.4 按天统计日期列必须先截断再分组“每天的接单数”“每天的销售额”看着简单实际是最容易出错的一类统计。订单表里的下单时间是 datetime精确到秒直接GROUP BY order_date会把每一秒都变成一组结果和订单条数一样多。-- 用 DATE() 把时间截断到天再按天分组统计单量和金额 SELECT DATE(order_date) AS stat_date, COUNT(*) AS order_cnt, SUM(order_total) AS total_amount FROM orders GROUP BY DATE(order_date) ORDER BY stat_date;代码里的 DATE() 是关键它把 2024-03-01 09:12:33 和 2024-03-01 21:40:02 归成同一个 2024-03-01。如果需要按月统计换成DATE_FORMAT(order_date, %Y-%m)按年就是 YEAR()。SELECT 里的别名 stat_date 不能在 GROUP BY 里引用标准 SQL 中 GROUP BY 先于 SELECT 执行要么重复写函数要么用子查询包一层。平均单价这类指标也要注意分母AVG(order_total)是所有订单金额的平均SUM(order_total) / COUNT(*)结果相同但意图更清楚如果分母想用“有金额的订单数”就得写SUM(order_total) / COUNT(order_total)因为 COUNT(列) 会跳过 NULL。4. 连接与嵌套多表查询的三种组织方式单表能解决的问题终究有限一旦需求里同时出现两张表的字段就必须进入多表查询。实验里从 2.11 到 2.19 覆盖了三条路线JOIN 把表横向拼起来、子查询把一条语句的结果喂给另一条、集合运算把多个结果集纵向摞起来。三条路线经常能互相改写选哪条主要看可读性和数据量。4.1 内连接ON 写的是两张表的关系“查询所有订单的发出者名字”订单号在订单表、用户名在用户表两张表通过 user_id 关联这就是内连接的标准场景。-- 订单表别名 o用户表别名 uON 描述两表的对应关系 SELECT o.order_id, o.order_total, u.user_name FROM orders o INNER JOIN users u ON o.user_id u.user_id;购物车商品名称同理购物车表同时有 user_id 和 goods_id通过 goods_id 连商品表就能拿到名称SELECT c.user_id, g.goods_name, c.goods_num FROM cart c INNER JOIN goods g ON c.goods_id g.goods_id;内连接的语义是“两边都匹配上才保留”。如果某条订单的 user_id 在用户表里找不到对应记录用户被删除、脏数据这条订单在内连接结果里会直接消失。做核对类需求时要意识到这一点内连接的结果行数通常小于等于左表的行数数量对不上先查关联键是否有孤儿值。给表起别名不只是少打字两张表有同名列时比如都叫 user_id不加别名限定会报“列名歧义”。4.2 外连接谁是“保留全部”的那张表“列出所有用户 ID以及他们的评论如果有的话”——“所有”和“如果有的话”是外连接信号用户表是保留全部的一方评论表是可能缺失的一方。-- 用户表在左LEFT JOIN 保留左表全部行没评论的补 NULL SELECT u.user_id, c.comment_id, c.content FROM users u LEFT JOIN comments c ON u.user_id c.user_id; -- 等价写法把用户表放右边用 RIGHT JOIN 保留右表全部行 SELECT u.user_id, c.comment_id, c.content FROM comments c RIGHT JOIN users u ON u.user_id c.user_id;两段代码结果集相同区别只在书写习惯。判断用 LEFT 还是 RIGHT 的方法很简单先在脑子里确定哪张表要“全部保留”把它放在 LEFT JOIN 的左边另一张放右边。写完再检查一遍 ON 后面的关联键方向左右写反不会报错只会让结果集从“全部用户”变成“全部评论”条数差一个量级。外连接里没匹配上的列会填 NULL所以后续如果有WHERE c.comment_id 5这种条件会把补 NULL 的行全部过滤掉外连接退化成内连接。要过滤右表又想保留左表全部行条件得写在 ON 里而不是 WHERE 里。注意LEFT JOIN 之后对右表列加非空过滤是外连接失效最常见的原因写完先看结果行数对不对。4.3 复合条件连接连接条件放 ON过滤条件放 WHERE“查询用户 ID 为 1 的客户的订单信息和客户名”先做内连接拿到订单和用户名的组合再加一个精确条件缩小范围。-- ON 负责两表怎么对上WHERE 负责筛掉不要的行 SELECT o.order_id, o.order_total, u.user_name FROM orders o INNER JOIN users u ON o.user_id u.user_id WHERE u.user_id 1;第二个例子是购物车商品价格并按价格排序连接之后再排序-- 连接拿价格ORDER BY 对最终结果集排序 SELECT c.user_id, g.goods_name, g.shop_price FROM cart c INNER JOIN goods g ON c.goods_id g.goods_id ORDER BY g.shop_price ASC;这两段体现的是分工ON 描述表与表的关系WHERE 描述业务过滤ORDER BY 作用在连接后的结果集上。内连接时把过滤条件写进 ON 或 WHERE 结果相同但可读性差很多外连接时写错位置直接改变语义。养成“关系进 ON、过滤进 WHERE”的习惯能省掉大量排查时间。4.4 嵌套查询IN 与 NOT IN 的空值陷阱“查询订购商品 ID 为 1 的订单 ID再根据订单 ID 查发出订单的用户 ID”这句话本身就是两层内层在订单明细表查订单号外层在订单表查用户。-- 内层返回多个订单号外层用 IN 匹配 SELECT DISTINCT user_id FROM orders WHERE order_id IN ( SELECT order_id FROM order_detail WHERE goods_id 1 );反过来查“未发出此订单的用户”把 IN 改成 NOT IN 即可。但 NOT IN 有个经典坑如果子查询结果里含 NULL整个 NOT IN 的比较结果会变成 UNKNOWN外层一行都查不出来。补法是子查询里加WHERE 列 IS NOT NULL或者改用 NOT EXISTS。比较运算符做子查询时要求内层只返回单值例如“今年新增会员中订单总价小于 100 的订单”-- 子查询先取今年注册的用户外层再按用户和金额筛选 SELECT order_id, user_id, order_total FROM orders WHERE user_id IN ( SELECT user_id FROM users WHERE YEAR(create_time) YEAR(CURDATE()) ) AND order_total 100;这里的金额条件放在外层 WHERE属于对最终结果的过滤如果塞进子查询就变成“用户表里有金额小于 100 的记录”语义完全跑偏。4.5 EXISTS、ANY、ALL三个容易被念错的关键字EXISTS 判断子查询有没有返回行返回布尔值不关心具体列是什么所以子查询里写SELECT 1就够了。-- 相关子查询外层每一行都拿去内层验证一次 SELECT u.user_id, u.user_name FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id u.user_id );这段和IN (SELECT user_id FROM orders)结果相近但机制不同EXISTS 逐行验证、命中即停子查询里有 NULL 也不影响结果IN 是先算出整个子查询结果集再做匹配。数据量大时 EXISTS 往往更稳。ANY 和 ALL 必须跟比较运算符一起用语义是“和子查询结果中的任意一个比”与“和所有值比”-- 比类别 2 中任意一个商品贵等价于大于该类别的最低价 SELECT goods_id, shop_price FROM goods WHERE shop_price ANY (SELECT shop_price FROM goods WHERE cat_id 2); -- 比类别 2 中所有商品都贵等价于大于该类别最高价 SELECT goods_id, shop_price FROM goods WHERE shop_price ALL (SELECT shop_price FROM goods WHERE cat_id 2);记忆方法是把 ANY 换算成 MIN、ALL 换算成 MAX ANY等于 MIN ALL等于 MAX。子查询返回空集时ANY 为假、ALL 为真这个边界在实验数据里碰不到但在真实数据里会突然冒出来。4.6 集合查询UNION 去重、UNION ALL 不去重“查询价格小于 5 的商品以及类别 ID 为 1 和 2 的所有商品”两个条件没有包含关系结果要合并成一个集合用 UNION。-- 两条 SELECT 纵向拼接UNION 自动去掉完全重复的行 SELECT goods_id, goods_name FROM goods WHERE shop_price 5 UNION SELECT goods_id, goods_name FROM goods WHERE cat_id IN (1, 2);把 UNION 换成 UNION ALL重复行会原样保留。两者的选择标准很直接需要去重就用 UNION确定两边不会有交集、或者业务上允许重复就用 UNION ALL省掉一次排序去重的开销。使用 UNION 有几个硬性前提两条 SELECT 的列数必须相同、对应列的类型要兼容、结果集的列名取第一条 SELECT 的列名。列顺序对不上不会报错但数据会错位这种错误比语法错误难查得多。5. 易错点五个不报错但结果错的位置数据查询最折磨人的地方在于语法错会立刻报错语义错却安安静静返回一个看起来合理的数字。下面五条是这套实验里最常出问题、也最容易被忽略的位置每条都按“漏在哪、为什么漏、怎么补”拆开。5.1 易漏点在 WHERE 里过滤聚合结果漏在哪想筛出消费总额超过 500 的用户写成WHERE SUM(order_total) 500。为什么漏WHERE 的执行时机在分组和聚合之前那时候 SUM 还根本不存在语句直接报错。很多人第一次遇到这个报错时以为是函数写错了反复改函数名。怎么补聚合结果的过滤一律交给 HAVINGGROUP BY user_id HAVING SUM(order_total) 500。写之前先问一句“这个条件依赖的是单行还是一组”依赖一组就是 HAVING这是最快的判断方法。5.2 易错点GROUP BY 的 SELECT 列表里混进非分组列漏在哪SELECT user_id, order_id, SUM(order_total) FROM orders GROUP BY user_idorder_id 既没分组也没聚合。为什么漏在非严格模式下这条语句能跑order_id 会从组内随机取一行的值返回结果看起来像“这个用户的代表订单”实际毫无业务含义换台服务器开了严格模式就突然报错同一份作业两种结果。怎么补SELECT 列表里只允许两类东西——GROUP BY 里出现过的列、包在聚合函数里的列。确实需要组内某一行的明细时先用聚合算出每组的关键值再和原表连接回去取明细。5.3 易漏点外连接的过滤条件写进了 WHERE漏在哪FROM users u LEFT JOIN comments c ON u.user_id c.user_id WHERE c.goods_id 1本意是“所有用户附上他们对商品 1 的评论”。为什么漏WHERE 在连接之后执行没评论的用户在 c 相关列上全是 NULLc.goods_id 1对 NULL 判定为 UNKNOWN这些行被过滤掉左外连接退化成内连接结果只剩评论过的用户。语句不报错条数少了也没人察觉。怎么补把右表的过滤条件挪进 ONLEFT JOIN comments c ON u.user_id c.user_id AND c.goods_id 1这样连接时先按条件找匹配匹配不上仍然保留左表整行。判断口诀是外连接里想“限制匹配”的条件放 ON想“限制最终结果”的条件放 WHERE。提示看到 LEFT JOIN 就顺手数一下左表总行数和结果行数对不上大概率是 WHERE 出的问题。5.4 易错点NOT IN 遇到 NULL 返回空结果漏在哪WHERE order_id NOT IN (SELECT order_id FROM order_detail WHERE goods_id 9)子查询里只要有一行 order_id 是 NULL整条语句返回零行。为什么漏NOT IN 展开后是一串 NULL的比较任何值和 NULL 比较结果都是 UNKNOWNAND 起来整条条件不成立。这个行为符合 SQL 三值逻辑但和人的直觉相反而且没有任何警告。怎么补子查询里显式加WHERE order_id IS NOT NULL或者改用NOT EXISTS (SELECT 1 FROM ... WHERE ... AND order_id 外层.order_id)后者对 NULL 免疫。凡是写 NOT IN条件反射检查一下子查询列是否允许为空。5.5 易漏点COUNT(列) 与 COUNT(*) 混用、DISTINCT 位置写错漏在哪统计“有多少用户下过单”写成COUNT(user_id)却在一个可能为空的列上统计或者想统计“有多少个不同用户”写成COUNT(user_id)而不是COUNT(DISTINCT user_id)。为什么漏COUNT(列) 跳过 NULL如果关联键有缺失结果会比真实行数少不做 DISTINCT 则把同一用户的多条订单重复计数两个错误方向相反混在一起更难发现。怎么补数行用 COUNT(*)数非空值用 COUNT(列)数不同值用 COUNT(DISTINCT 列)三者在纸上写清楚再敲键盘比事后对账省事。含 NULL 的列做统计前先用SELECT COUNT(*) - COUNT(列)看看差值有多大心里有个底。6. 查漏补缺一份可以照着走的复习清单复习到这个阶段最有效的做法不是重看一遍语法而是拿一句需求反推该用哪些子句。下面这张表把实验里的需求类型和判断依据对上做题时先归类再动笔比凭感觉写快得多。需求特征判断依据该用的子句只出现一张表的字段字段全在一张表WHERE 列清单“并且”“同时满足”多个条件同时成立AND混用 OR 时加括号“不重复”“有哪些”可能有重复行DISTINCT 或 GROUP BY“按……排序”“前几名”有明确顺序要求ORDER BY ASC/DESC“每个……的总额/数量”出现“每个”GROUP BY 聚合函数“总额大于……”条件依赖聚合结果HAVING需求里同时出现两张表字段需要横向拼接INNER/LEFT JOIN ON“所有……即使没有”有一方要全部保留LEFT JOIN需要先算一个结果再用语句有先后依赖子查询 IN/EXISTS两个结果集合并条件无包含关系UNION / UNION ALL配套的自查动作有三步。第一步查行数单表查询结果条数不该超过原表条数内连接结果不该超过任一张表外连接结果不该少于保留侧那张表数字对不上就是连接或过滤出了问题。第二步查 NULL凡是结果里出现 NULL 的列回头确认它是不是外连接补出来的、是不是被聚合函数跳过了。第三步查粒度所有带“每个”的语句把 GROUP BY 的列和需求里的维度逐个对照少一列粒度就粗一档。-- 对账用的三个小查询原表行数、分组数、含 NULL 的键数 SELECT COUNT(*) AS total_rows FROM orders; SELECT COUNT(DISTINCT user_id) AS user_cnt FROM orders; SELECT COUNT(*) - COUNT(user_id) AS null_key_rows FROM orders;这三条语句在调试任何统计类需求时都能派上用场第一条给分母第二条给预期分组数第三条提醒你关联键是不是干净。跑完再对照业务语句的结果差异立刻现形。我自己的习惯是每写完一条带 GROUP BY 或 JOIN 的语句都强制走一遍“行数—NULL—粒度”这三步不跳步。刚开始觉得啰嗦被随机取值的 GROUP BY 和悄悄退化的 LEFT JOIN 坑过几次之后这套动作已经变成肌肉记忆。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑