ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从力扣1757题看SQL基础:WHERE执行细节与工程素养

从力扣1757题看SQL基础:WHERE执行细节与工程素养 力扣的数据库题库里1757 题可回收且低脂的产品被贴上了简单的标签通过率也常年居高。可我在面试别人、带实习生的过程中发现越是这种十秒写完的题越能暴露一个人对 SQL 理解的深浅。很多人能秒出正确答案却答不出 WHERE 的执行过程、枚举字段的隐藏行为甚至不知道为什么输出只需要 product_id。这篇文章就以 1757 题为引子聊一聊 SQL 查询题的读题方法、标准解法的内部逻辑以及从一道简单题延伸到真实业务开发的工程习惯。适合刚开始刷力扣 SQL 题库的同学也适合想在面试中把基础题讲出深度的人。1. 先别急着写 SQL把可回收且低脂翻译成筛选口径1.1 题目原貌一张产品表与两个条件1757 题的题干很短核心就是一张产品表 Products字段一共三个列名类型说明product_idint产品编号主键low_fatsenum(Y, N)是否低脂Y 表示低脂recyclableenum(Y, N)是否可回收Y 表示可回收题目要求找出同时满足低脂和可回收两个条件的产品 id返回结果无顺序要求。示例数据如下product_idlow_fatsrecyclable0YN1YY2NY3YY4NN期望输出是 1 和 3。注意看这几行数据它把四种取值组合全覆盖了产品 0 低脂但不可回收产品 2 可回收但不低脂产品 4 两个条件都不满足只有产品 1 和产品 3 是真正的双 Y。题目这样的示例设计其实是在明确告诉你筛选条件必须两个同时成立缺一不可。很多人在这一步容易忽略的是输出列。题目要的是 product_id不是整个表。如果你习惯性写 SELECT *输出就会多出 low_fats 和 recyclable 两列LeetCode 判题系统会比较整个结果集列数不一致直接判错。我见过不少同学条件写对了卡在这个细节上反复提交。1.2 业务翻译这不是 SQL 题是需求理解题为什么说第一步是翻译因为力扣的 SQL 题本质上考的不是语法背得多熟而是你能不能把一句自然语言描述的业务条件准确无误地映射成 SQL 表达式。想象一个真实场景你是电商平台的数据分析师运营发来消息说把低脂且可回收的产品清单给我。这句话里至少包含四个需要确认的信息输出粒度按产品输出也就是一行一个 product_id。如果同一个产品在表中出现多行比如不同批次是否需要去重本题因为 product_id 是主键天然不会重复所以不需要 DISTINCT但真实业务里这种重复行问题相当常见。筛选口径且字决定了两个条件必须同时成立对应 SQL 里的 AND。如果需求说的是低脂或可回收那就要用 OR结果集会完全不同——产品 2 会进来这是天壤之别。字段取值Y 和 N 是枚举值不是布尔值 true/false也不是可空字段。这个细节后面会专门展开。排序要求题目明确写了返回结果无顺序要求所以不加 ORDER BY 完全没问题但如果你为了保险加上 ORDER BY product_id也不会错。这里想强调一个习惯写 SQL 之前先花 30 秒在脑子里过一遍这四个问题。力扣题目把这些约束都写在题干里了我们要训练的是看见题干就能自动提取这些信息。等到真实工作里没人帮你把需求写得这么清楚的时候这个习惯能帮你少返工很多次。2. 标准解法的执行细节WHERE 到底做了什么2.1 绝大多数人提交的答案这道题的标准解法和最终答案其实就三行代码SELECT product_id FROM Products WHERE low_fats Y AND recyclable Y;没有 GROUP BY没有 JOIN没有子查询提交通过。这是最简单直接的做法也是题解区的主流写法。但也正因为太简单反而容易让人觉得这题没啥可看的。我见过个别同学在简单题上放飞自我写出这种版本SELECT product_id FROM Products WHERE low_fats Y INTERSECT SELECT product_id FROM Products WHERE recyclable Y;INTERSECT 是集合运算在逻辑上确实能得出同样的结果PostgreSQL 也支持MySQL 8.0 的某些版本则不一定支持。但这种写法有两个问题一是把简单逻辑复杂化完全没有必要二是面试时如果这么写面试官基本会追问为什么不用 AND答不上来反而扣分。力扣题解区的价值在于帮你建立用最简单的方式表达正确语义的直觉而不是展示花哨技巧。2.2 从执行层面看 SELECT-WHERE 的协作理解这道题不能停留在会写还要知道数据库是怎么执行的。我们可以把一条 SELECT 语句的逻辑执行顺序粗略理解成三步FROM Products确定数据来源是 Products 表。WHERE low_fats Y AND recyclable Y对每一行逐行判断条件是否成立成立则保留否则丢弃。SELECT product_id对保留的行做投影只留下需要的列。严格来说MySQL 这类数据库在执行时还有优化器改写、索引扫描、批量读取等动作真实执行顺序并不完全等于这个逻辑顺序。但用这个逻辑模型去理解 SQL 语义对初学者来说是最不容易出错的。这里有一个关键点WHERE 是在行级别上做过滤的。对于每一行low_fats Y和recyclable Y会分别得到一个布尔结果再用 AND 合并。只有两个都为 true 的行才会进入最终结果而最终结果里我们只保留 product_id 一列。正因为 WHERE 是对行做过滤这道题完全没有用到分组、去重、聚合。很多同学刷到后面看到找出满足条件的记录就条件反射地问要不要 GROUP BY其实没必要。判断一个查询是否需要分组关键看输出粒度输出行和输入行是否一一对应本题一一对应不需要分组如果输出的是每个产品的总销量那才需要 GROUP BY。顺带提一个和 AND 相关的知识点AND 的优先级高于 OR。如果题目变成低脂或可回收且某个状态为真不加括号就很容易写错。本题只有两个条件且用 AND没踩不到优先级陷阱但养成复杂条件加括号的习惯总是好的。2.3 为什么不需要 DISTINCT这是个高频追问点。答案很简单product_id 是主键主键意味着唯一所以同一个产品在 Products 表中不可能出现两行。但如果你把表结构改一下比如主键变成(product_id, batch_id)即同一个产品有多个批次记录那么查询结果里同一产品 id 就会出现多行。这时候要不要加 DISTINCT取决于业务上产品清单是不是要去重。真实业务里这种主键组合导致语义重复的情况非常多而且往往是 bug 的温床。力扣这道题用单列主键把这个问题屏蔽掉了但你在工作中一定要保留这个意识看到结果集可能重复时先想清楚业务到底要什么粒度。3. 容易被翻车的三个细节ENUM、NULL 和大小写基础答案人人会写但拉开差距的往往是边角细节。这一章是全文我觉得最值得反复看的部分。3.1 ENUM 不是普通字符串low_fats 和 recyclable 都是枚举类型而不是 VARCHAR。在 MySQL 里ENUM 类型有几个底层特性值得了解第一内部存储。ENUM 在底层占用的空间极小默认用 1 或 2 个字节存储整数索引。比如定义是enum(Y, N)那么内部 Y 对应索引 1N 对应索引 2NULL 单独处理。查询时数据库再把索引映射回字符串返回给你。好处是省空间代价是灵活性差。第二数据约束。ENUM 只允许插入定义时列出的值。如果你试图插入y或Maybe在严格模式下会直接报错在非严格模式下可能会被转成空字符串或给出警告。这种隐式行为在实际业务里可能会让数据悄悄变形。第三比较查询。WHERE low_fats Y这种写法数据库会把右侧字符串 Y 转成对应索引再做比较所以效率并不差。你可能感觉不到它和普通字符串比较有什么区别但在底层确实多了一层映射。那为什么很多团队在实际项目中宁愿用VARCHAR CHECK约束也不用 ENUM因为 ENUM 的扩展性太差。一旦想新增一个枚举值比如把 N 扩展为 N 和 U未知就需要 ALTER TABLE 修改枚举定义。在大表上做这种 DDL 操作成本不低而且容易锁表。力扣这道题选择 ENUM其实是在用最简单的方式告诉你这两个字段只有两种合法取值不用考虑脏数据。但如果你把这种理想化设定直接套到真实业务中就会踩坑。3.2 NULL 进场后一切都不一样这是整道题最容易被延伸的考点。题目示例里没有任何 NULL但真实业务表中可空字段到处都是。问题来了如果某一行的 low_fats 是 NULL那么low_fats Y会命中吗答案是不会。SQL 遵循三值逻辑一个比较的结果除了 TRUE 和 FALSE还有 UNKNOWN。NULL 参与比较时结果不是 TRUE 也不是 FALSE而是 UNKNOWN。WHERE 子句只保留判断结果为 TRUE 的行UNKNOWN 会被过滤掉。具体到本题各种取值组合的结果是这样的product_idlow_fatsrecyclable是否进入结果1YY进入2YN不进入recyclable 条件为 FALSE3NY不进入low_fats 条件为 FALSE4YNULL不进入recyclable Y 为 UNKNOWN5NULLY不进入low_fats Y 为 UNKNOWN注意第 4 行和第 5 行哪怕另一个条件成立只要有一个条件碰到 NULL整行就会被丢掉因为 AND 两边必须同时为 TRUE 才行。如果你想让结果包含未知也算的情况那需要改写条件比如显式处理 NULL 或用 COALESCE但这已经超出本题范围了。面试官特别喜欢干一件事把示例数据里插入一行 NULL问你结果会怎样。如果你能脱口说出 UNKNOWN 和 WHERE 只保留 TRUE这一题的价值就真正体现出来了。3.3 大小写、排序规则与写入规范第三个细节是大小写。ENUM 值在 MySQL 中按字符串比较时通常受排序规则collation影响。默认的排序规则多数是 cicase insensitive不区分大小写所以low_fats y理论上也能匹配 Y。但依赖这种隐式行为非常不安全。最稳妥的做法是写入层统一规范比如全部大写 Y/N查询层也统一用大写常量。这样无论底层排序规则怎么变化查询结果都可预期。如果你用了 ORM还要注意字段映射是否会把 Y 自动转成 true/false那又是另一层坑。还有一个容易让人困惑的点在某些数据库驱动或可视化工具里ENUM 字段展示出来可能就是字符串但如果你用程序读取驱动可能把它映射成整数索引。这个时候如果你拿数字 1 去和 Y 比较肉眼看着很怪但在某些配置下真的能命中。这种碰巧能跑的代码绝对不能出现在生产环境因为换了驱动或连接配置行为就可能变。写 SQL 时永远面向语义编程而不是面向巧合编程。4. 一道简单题背后的工程素养列选择、索引与刷题方法4.1 查需要的列而不是 SELECT *这道题输出只需要 product_id那就只 SELECT product_id。这不仅是力扣判题的要求也是生产环境的好习惯。SELECT * 至少有三大问题第一网络传输开销。你不需要的列也被传回客户端数据量大时纯属浪费。一个表如果有几十个字段而业务只需要其中一列SELECT * 传回的数据量可能是实际需要的几十倍。第二无法利用覆盖索引。如果查询只需要 product_id而索引刚好覆盖该列数据库可以直接从索引中返回结果不需要回表读整行。一旦 SELECT *就必须回表取全部字段速度慢很多。在简单题里你感觉不到但在大表上这就是天壤之别。第三隐式耦合。表的列结构一旦变更SELECT * 的结果集会跟着变可能悄悄破坏下游的数据处理逻辑。维护这种代码改表的人不会意识到有哪个查询被他影响了简直是埋雷。很多新手觉得多查一列又没关系。是的在 5 行数据的测试用例上确实没关系。但职业习惯恰恰是在这种小地方养成的等上了生产环境再改成本高得多。4.2 低基数字段的索引困境low_fats 和 recyclable 都只有 Y/N 两种取值这类字段在数据库术语里叫低基数字段。低基数字段上建索引有一个经典问题选择性太差。索引本质上是一棵排序树按照字段值组织数据。如果某个字段只有两种取值每个值对应的行数大约是表的一半。优化器计算代价时发现走索引要访问一半的行还不如直接全表扫描来得快于是索引就形同虚设。那如果建联合索引(low_fats, recyclable)呢两个条件各筛掉一半组合起来理论上能把候选集缩小到 25% 左右选择性会好一些。在真实的业务场景里如果这张表特别大而且这种筛选是高频操作更常见的做法还有几种增加一个冗余列比如audit_status在写入时就计算好低脂且可回收的结果查询直接查这个布尔状态。使用生成列generated column把组合条件物化成一列再在这列上建索引。重新审视业务看是否应该从订单、库存等维度来做过滤而不是在宽表上硬查。说实话1757 这种查询建不建索引根本不是重点力扣判题也不会因为你没索引就超时。但当你从刷题切换到真实业务优化时这种思维转换非常重要。简单说力扣题考的是语义正确业务题更关注语义正确基础上的性能与成本。4.3 简单题的正确打开方式建立反馈闭环最后聊点刷题方法论。1757 题实在简单认真说它考察的就是最基础的 WHERE 条件过滤。但简单题恰恰最容易让人产生背下答案就完事的错觉。我刷 LeetCode 数据库题库时有个习惯每道题做完之后不管简单还是难都问自己三个问题。第一为什么这个写法能行搞清楚背后的执行逻辑和 SQL 语义而不是只记语法。第二哪些地方换一种写法会挂比如把 AND 换成 OR、把列名写错、加上 GROUP BY或者加个 ORDER BY DESC结果怎么变这种魔改练习能在很短时间内帮你理解边界。第三如果字段是 NULL结果会变吗把题目示例数据改一改插入 NULL用脑子跑一遍 SQL或者真的写出来验证一下。这三个问题会让你在一分钟内把一道送分题吃透而不是仅仅拿到一个提交通过的绿色勾。拿 1757 来说如果你能写出标准答案还能解释 ENUM 的底层存储、NULL 的三值逻辑、为什么不需要 DISTINCT、SELECT * 在生产环境中的危害那么这道题对你的价值就远超过那五分钟的通过记录。后面你再刷 182查找重复的电子邮箱、595大的国家、584寻找用户参考人这些同级别题目时会发现它们本质上都在用同一套基本功理解表结构、翻译筛选条件、处理边界值。基础打得稳后面刷聚合、连接、窗口函数时才不会地基松动。我在面试别人时也喜欢问类似 1757 这种入门题。目的根本不是看候选人能不能写出来——能写出来是基本盘。真正分高下的是他能不能把为什么讲清楚能不能主动提到 NULL 的影响能不能说出不用 SELECT * 的原因。SQL 的语法栈很浅但语义层很深。想清楚 WHERE 与三值逻辑、列选择与索引、ENUM 与数据约束这些概念后你对基础题的掌控力和别人会完全不同。刷题数量固然重要但每次刷题都往深处多想一步才是性价比最高的成长方式。
返回列表