互联网大厂数据分析师面试指南:SQL与商业分析思维_第1页
互联网大厂数据分析师面试指南:SQL与商业分析思维_第2页
互联网大厂数据分析师面试指南:SQL与商业分析思维_第3页
互联网大厂数据分析师面试指南:SQL与商业分析思维_第4页
互联网大厂数据分析师面试指南:SQL与商业分析思维_第5页
已阅读5页,还剩101页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

互联网大厂数据分析师

面试指南:SQL与商业分析思维文档类型:面试指南与真题精讲(INTV)

适用对象:备战字节跳动、阿里巴巴、腾讯、美团、京东、快手、小红书等互联网大厂数据分析师、数据科学家、商业分析师岗位的求职者;具备基础SQL语法知识但缺乏面试实战经验的转行者;希望系统提升商业分析思维与数据驱动决策能力的在职人员。

核心承诺:本书提供80道SQL面试真题(含完整题干、答案与逐项解析)、20道商业分析思维案例(含完整分析框架与满分示范)、5套全流程模拟面试(涵盖技术面、业务面、HR面)、3套可直接填写的配套工具模板、10条数据分析面试常见误区与避坑指南、15项附录学习资源。摘要本书是一本专为互联网大厂数据分析师岗位求职者打造的面试实战指南,深度覆盖SQL硬核技能与商业分析思维两大核心考察维度。全书原创收录80道互联网大厂SQL面试高频真题,涵盖查询执行顺序、JOIN连环陷阱、窗口函数精讲、索引优化与执行计划等硬核模块,每道题均提供完整题干、逐项选项解析与正确答案。商业分析思维篇聚焦指标体系搭建、用户留存分析、归因分析、A/B测试全流程等面试重灾区,20道案例均配备“分析框架—推导过程—满分示范”的完整链路。另附5套全流程模拟面试、3套可直接打印的工具模板、10条避坑指南及15项附录资源,确保读者从技术储备到面试表达实现全链路提升。所有内容均基于互联网大厂近三年真实面试题库与评分标准进行还原,是一本可直接用于高强度面试备战的操作手册。使用说明与学习目标学习前提

①建议读者已具备基础SQL语法知识,至少掌握SELECT、FROM、WHERE、GROUPBY、HAVING、ORDERBY等核心子句的基本用法。

②建议读者对互联网常见的业务指标(DAU、MAU、留存率、GMV、ARPU等)有初步了解。

③本书中的SQL题目均以MySQL8.0作为参考环境,部分题目涉及窗口函数、CTE等高级特性,建议在本地搭建练习环境同步实操。学习路径建议

①第一步:通读第三章“SQL面试核心考点精讲”,建立完整的SQL面试知识框架。

②第二步:逐题攻克第四章“80道SQL面试真题精练”,每道题先独立完成,再对照解析复盘。

③第三步:进入第五章“商业分析思维精讲与案例分析”,掌握指标体系、漏斗分析、A/B测试等核心框架。

④第四步:完成第六章“5套全流程模拟面试”,在限定时间内模拟真实面试压力环境。

⑤第五步:使用第八章“配套工具模板”进行自我复盘与面试准备清单管理。学习目标

①能够在白板或在线编辑器内独立完成中等难度SQL查询的书写,无语法错误。

②能够清晰阐述一条SQL语句的执行顺序与各阶段的性能瓶颈点。

③能够针对任意给定的互联网业务场景,独立设计3-5个核心监控指标并说明理由。

④能够完整复述A/B测试的统计学原理、实验设计步骤与结果分析方法。

⑤能够在面试中流畅地运用“背景-目标-行动-结果”的STAR法则讲述过往项目经历。适用人群与阅读路径建议适用人群类型特征描述推荐阅读路径行动指示校招求职者计算机/统计/数学/商科等相关专业应届生,具备理论学习但缺乏面试经验第三章→第四章前40题→第五章全部→第六章第1-2套→第八章重点刷窗口函数与留存分析类题目,这是校招SQL面试中占比最高的题型。商业分析部分重点掌握指标体系搭建和A/B测试,务必能脱稿完整推导一遍实验全流程社招转行者具备1-3年其他岗位经验,通过培训班或自学掌握了SQL和数据分析工具第四章全部→第五章第三节至第五节→第六章第3-5套→第七章→第八章社招面试对项目深挖的要求远高于校招,务必按照第八章的STAR模板逐一梳理过往项目,确保每个项目都能讲出“业务影响”和“可量化的贡献”在职数据分析师已在中小厂工作1-3年,希望跳槽至大厂获取更高平台和薪资第四章后40题→第五章第四节至第六节→第六章全部→第九章→第十章大厂面试的高区分度环节在索引优化、执行计划分析和归因模型选择,这些在中小厂工作中接触较少,务必深入理解底层原理而非仅背诵结论数据产品经理/运营工作中频繁与数据打交道,但SQL技术能力偏弱,面试中存在技术短板第三章→第四章前60题→第五章第一节至第三节→第六章技术面模拟不必追求达到纯技术岗位的SQL深度,但窗口函数和多表JOIN必须熟练。商业分析思维是强项,重点练习如何用数据语言包装运营决策逻辑第一章互联网大厂数据分析师面试全景解读岗位定位与核心能力要求

①互联网大厂的数据分析师岗位,本质上是在数据中台或业务线内部,承担“将数据转化为业务决策”的角色。与数据工程师不同,分析师更强调与业务方的沟通、指标体系的搭建以及对数据波动的归因能力。与数据科学家相比,分析师对机器学习算法深度的要求略低,但对SQL熟练度、业务Sense以及沟通表达的要求更高。

②核心能力拆解:

(a)SQL硬核能力:不仅要求能写出正确的结果,更要求能在面试压力环境下,在白板或记事本上写出语法规范、逻辑清晰且性能可接受的查询语句。窗口函数、多层嵌套子查询、JOIN优化是高频考点。

(b)商业分析思维:能够将模糊的业务问题(如“最近DAU下降了怎么办”)转化为可量化的数据分析框架(拆解、假设、验证、归因、建议),并在回答中展现出结构化的思维过程和落地可执行的建议。

(c)数据可视化与沟通:能够清晰地描述用什么图表呈现什么结论,以及在跨部门会议中如何向非技术同事解释统计概念。

(d)统计学基础:A/B测试的假设检验、置信区间、p值含义、样本量计算是面试的“必答题”,几乎每一家大厂都会考核。

(e)项目经验与业务Sense:能够使用STAR法则清晰地叙述过往项目的背景、自己承担的角色、采取的具体行动、遇到的技术难题以及最终带来的业务成果。面试流程全景图

①互联网大厂数据分析师的面试通常包含3-5轮,整体流程约为2-4周。常见流程如下:

(a)简历筛选与HR初筛:约1周,HR会通过电话或视频核实基本背景、离职原因、薪资期望等。此关淘汰率约30%-40%。

(b)技术面(SQL笔试/在线编程):约45-60分钟,面试官通常为团队内的资深数据分析师或TechLead。此关为硬核筛选,淘汰率最高,通常可达50%-60%。形式可能是在线共享屏幕的SQL编辑器中实时写题,也可能是白板手写。题目通常从简单查询逐步递进到复杂窗口函数或数据变形。

(c)业务分析面:约45-60分钟,面试官通常为未来的直属Leader或业务线负责人。主要考核方向为:给一个业务场景,让你设计指标体系;给一组业务波动数据,让你分析原因;讨论过往项目经历中的分析思路和业务影响。

(d)交叉面或数据分析总监面:约30-45分钟,面试官为更高层级的管理者。考察重点转向逻辑思维深度、对数据价值的理解、对行业的认知以及综合素质。此关常见的问题包括“你觉得数据分析师的价值边界在哪里”或“描述一次你通过数据推动业务做出重要决策的经历”。

(e)HR面:约30分钟,主要考察价值观匹配、沟通风格、职业规划、抗压能力等软性维度。常见的行为面试题包括“你经历过的最大的失败是什么”“当你的分析结论与业务方意见不一致时你怎么办”等。本章小结

读完本章,你应该已经对互联网大厂数据分析师的面试流程和考核重点有了全局认知。接下来请完成两个动作:第一,在第八章的工具模板中填写你自己的面试准备进度追踪表;第二,确认自己是否已满足本章列出的前提条件。如果你的SQL基础还达不到熟练书写多表JOIN的水平,请先利用附录推荐的学习资源进行基础补强,再继续阅读后续章节。第二章数据分析师面试必备软技能与面试官视角揭秘面试官视角:我们在寻找什么样的候选人

许多求职者花了大量时间刷题、背概念,却从未认真思考过一个问题——面试官到底想要什么样的人?理解这一点,是制定面试策略的起点。从大量互联网大厂面试官的反馈中,可以提炼出以下四条核心评估维度。

①技术基本功扎实,但更重要的是“可成长性”。

(a)面试官并不指望你掌握所有技术栈,但他们非常在意你在遇到不熟悉的问题时的思考路径。例如,如果面试官问了一个你从未接触过的业务场景,你直接回答“我不会”和“我不熟悉这个领域,但我可以尝试这样去拆解它”,两者传递出的信号天差地别。

(b)可成长性的另一个关键指标是“能否举一反三”。在SQL题目中,如果你只是背出了正确答案但无法解释为什么要用LEFTJOIN而不是INNERJOIN,或者无法分析执行计划的差异,面试官会认为你的技术深度不足。

②业务Sense是区分初级与高级分析师的标尺。

(a)初级分析师关注“这个数是多少”,高级分析师关注“这个数为什么变化”“应该怎么办”“谁应该去执行”。面试官在业务分析环节,重点考察的是你是否具备“从数据到洞察,从洞察到建议”的完整闭环能力。

(b)一个常用的检验标准是:如果你的回答中只有描述性统计(均值、比例、趋势)而没有诊断性分析(根因、影响面、优先级)和处方性建议(具体动作、预期收益、衡量标准),那么你的回答在面试官的评分表上大概率不会超过中等分数。

③逻辑表达清晰度直接影响你的面试得分。

(a)面试和日常工作的最大区别在于,面试是高度压缩的沟通场景。你必须在30-40分钟内让一个此前完全不了解你的人认可你的能力。如果你的回答缺乏结构,想到哪说到哪,哪怕最终观点正确,面试官也可能因为信息接收成本过高而给出偏低的评价。

(b)数据类面试的最优表达策略是“总-分-总”金字塔结构:先用一句话给出核心结论,然后用2-4个分论点支撑这个结论,每个分论点用数据或事实佐证,最后用一句话总结并给出下一步行动方向。

④价值观匹配与文化适配不可或缺。

(a)互联网大厂普遍追求“快速试错、数据驱动、结果导向”的文化。面试官会通过行为面试题来探测你是否具备这些特质。例如,“你如何看待一个没有明确KPI的项目”这样的问题,背后其实在考察你在模糊环境下主动定义目标并推动落地的主观能动性。

(b)诚信与实事求是是数据分析师的底线。如果你试图在数据中“挑选”支持自己观点的样本,或者在A/B测试中绕过了统计显著性检验而草率下结论,这在面试官眼中是不可触碰的红线。STAR法则深度拆解:项目经历的表达公式

在业务分析面或HR面中,至少70%的问题可以通过STAR法则来结构化回答。但大多数求职者对STAR的使用停留在表面,本节给出面向数据分析师的高阶STAR用法。

①S代表背景。不要把背景讲成流水账。有效的方法是:用一句话描述业务环境,再用一个数据指标来量化问题的严重程度。例如,不要说“我们公司的用户活跃度比较低”,而要说“我们负责的业务线,DAU已经连续三个月维持在120万左右,环比零增长,而竞品的同比增速约为8%”。

②T代表目标。目标必须可衡量。除了描述自己承担的角色之外,还必须清晰说出项目的量化目标。例如,“我的目标是找到活跃度瓶颈,并在一个季度内将DAU提升10%”。

③A代表行动。这是最关键也最容易失分的环节。不要用“我们做了数据分析”这样的概括描述。你必须拆解出至少三个步骤:

(a)数据获取:你从哪些表取了什么数据?大概的数据量级是多少?你是否遇到了数据质量问题,又是如何处理的?

(b)分析过程:你使用了什么分析方法?漏斗分析?留存分析?用户分群?是否建立了一个分析框架?你的假设是什么?验证结果如何?

(c)沟通推动:你是如何将分析结论呈现给业务方的?你给出了哪些具体建议?哪些建议被采纳了?

④R代表结果。结果必须用数据说话,且要区分过程指标和最终结果指标。例如,“我的分析报告定位了三个关键流失节点,其中第一个节点优化后,流失率从18%下降至11%,最终帮助DAU在一个季度内实现了12%的增长,超出预期2个百分点”。压力面与反向提问技巧

①压力面通常是交叉面或总监面可能出现的环节。面试官会故意抛出尖锐的追问,例如“你做的这个分析在我看来毫无价值,因为你没有考虑季节性因素,你怎么看?”应对压力面的核心策略不是辩解,而是三步法:

(a)先承认问题的合理性:“您提到的季节性因素确实非常重要,如果在分析期间内包含了大型节假日,那么的确需要对这个变量进行剥离。”

(b)再展示你当时的思考:“我当时在做这个分析时,特意选取了去年同期同一个非假期的时间窗口作为对比基准,以尽量规避季节性干扰。但如果当时的数据条件允许加入更细颗粒度的时间序列分解,比如STL分解,确实会得出更精确的结论。”

(c)最后表明学习态度:“这提醒我以后在做趋势分析时,要主动把季节性检验纳入标准分析流程。”

②反向提问环节往往被求职者忽视,但它同样是面试官打分的重要参考。你应该至少准备3个有深度的问题。反向提问的禁忌:不要问薪资福利(这些应该留给HR面),不要问“您觉得我刚才回答得怎么样”(这会让面试官非常尴尬)。反向提问的三个优质方向:

(a)关于团队与业务:“我加入后主要支持的业务线是什么?这个业务线目前最迫切希望通过数据解决的一个问题是什么?”

(b)关于成长与期望:“您对这个岗位在入职三个月和一年的期望分别是什么?”

(c)关于技术栈与工具链:“团队目前主要使用什么数据分析工具栈?BI报表和模型部署的流程是怎样的?”本章小结

本章的核心思想可以浓缩为一句话:技术过关是入场券,但拿下Offer的关键在于你是否能让面试官觉得“我想和这个人一起工作”。STAR法则和总-分-总表达框架是你能直接带进下一场面试的可执行武器。下一章将正式进入SQL硬核技术面,请确保你的本地SQL环境已经准备就绪。第三章SQL面试核心考点精讲SQL查询执行顺序:面试的底层逻辑

在开始刷题之前,你必须深刻地理解一条SQL语句的完整执行顺序。这是面试的底层逻辑,也是性能优化的基础。超过70%的求职者只能正确写出SQL语法,却无法准确回答“SELECT、FROM、WHERE、GROUPBY、HAVING、ORDERBY、LIMIT这几个子句的执行顺序是什么”。如果你也不能在5秒内给出完整且正确的回答,请把本节反复阅读直到形成肌肉记忆。

①SQL标准执行顺序(注意,这不是书写顺序):

(a)FROM:确定查询所涉及的数据表,并执行表连接以生成初始虚拟表。如果涉及子查询,子查询会在此阶段先被独立执行。

(b)ON:根据JOIN条件进行表的连接匹配。如果JOIN条件写在WHERE中,则会在当前阶段延迟到后续步骤处理,这是常见的性能隐患。

(c)JOIN:如果存在外连接(LEFT/RIGHTJOIN),在此阶段添加外部行,即保留左表或右表中未匹配的记录,用NULL填充。

(d)WHERE:对虚拟表进行条件筛选,不满足条件的行被过滤掉。注意,WHERE子句中不能使用聚合函数,因为此时聚合尚未执行。

(e)GROUPBY:根据指定的列对数据进行分组。分组后的数据已经不再是原始行,而是按组聚合。

(f)HAVING:对分组后的聚合结果进行二次筛选。HAVING中可以出现聚合函数,这是它与WHERE最本质的区别。

(g)SELECT:对经过前面步骤处理的虚拟表选择所需列,并在此阶段计算所有字段的表达式以及别名。

(h)DISTINCT:对SELECT出的结果集进行去重。

(i)ORDERBY:对最终的结果集进行排序。此阶段才能使用SELECT中定义的列别名。

(j)LIMIT/OFFSET:截取指定数量的行返回。

②面试陷阱之一:WHERE和HAVING的深层区别。很多求职者知道WHERE在GROUPBY之前,HAVING在之后。但面试官会追问:“如果我在WHERE中用一个子查询实现了和HAVING相同的过滤效果,二者在性能上有区别吗?”这道题的答案是:有区别,且关键区别在于WHERE能利用索引进行高效过滤,而HAVING只能处理聚合后的中间结果集,通常无法走索引。因此,能用WHERE提前过滤掉的行,绝不放到HAVING中去处理。这是面试中的高频加分点。

③面试陷阱之二:SELECT中的别名能否在WHERE中使用?不能,因为WHERE在SELECT之前执行,此时别名尚未生效。但别名可以在ORDERBY中使用,因为ORDERBY执行顺序在SELECT之后。这个考点虽然简单,但面试中答错的人不在少数,属于典型的“送分题”。JOIN全家福与连环面试题

①JOIN的七种类型必须烂熟于心,并且能在白板上准确画出文氏图来表示每种JOIN的集合关系。

②INNERJOIN:只返回两个表中匹配上的行。语法为INNERJOIN...ON...。如果两个表中有键值不匹配的记录,这些行将被丢弃。面试中常考场景:统计两个用户集合的交集,如“既买了商品A又买了商品B的用户”。

③LEFTJOIN:返回左表中的所有行,以及右表中匹配上的行。如果右表无匹配,对应字段用NULL填充。这是面试中最常出现的JOIN类型,考点主要集中在“NULL值的处理”和“WHERE条件写在ON后还是WHERE后的区别”。

④RIGHTJOIN:与LEFTJOIN相反,但在实际工作中较少使用,因为可以通过调换表顺序用LEFTJOIN实现完全相同的结果。面试中如果被问到,只需说明原理即可。

⑤FULLOUTERJOIN:返回两个表中的所有行,不匹配的部分用NULL填充。MySQL原生不支持FULLOUTERJOIN,需要借助UNIONALL来实现。这是面试中的进阶考点。

⑥CROSSJOIN:笛卡尔积,返回左表的每一行与右表每一行的组合。如果没有WHERE条件,结果集的行数等于两表行数相乘。面试官通常会问“如果不小心漏掉了JOIN条件,会出现什么后果”。

⑦自连接:表与自身进行连接,通常用于在同一张表中比较不同行之间的数据。典型应用场景如“计算用户连续登录天数”或“找出所有比部门平均薪资高的员工”。

⑧高频面试陷阱:ON和WHERE在LEFTJOIN中的区别。这是一道必然会被问到的面试题。ON中的条件是连接条件,用于在生成虚拟表时决定右表的哪些行能匹配上。WHERE中的条件是筛选条件,作用于连接完成后的整个结果集。如果是LEFTJOIN,将条件写在ON后面不会减少左表的行数(但会影响右表字段是否有值),而写在WHERE后面则会直接过滤掉不满足条件的行(包括左表行)。请看下面的核心对比:

(a)写法A:SELECT*FROMALEFTJOINBONA.id=B.idANDB.status=1。左表A的所有行都会保留,如果B表中对应行的status不为1,则B表的字段为NULL。

(b)写法B:SELECT*FROMALEFTJOINBONA.id=B.idWHEREB.status=1。由于WHERE在连接后执行,如果B表中对应行的status不为1,整行会被过滤掉。因此写法B实际上退化为INNERJOIN的效果。窗口函数:拉开分差的分水岭

在互联网大厂的数据分析师SQL面试中,窗口函数是区分60分和90分的核心模块。如果不会窗口函数,你基本拿不到通过的技术面评分;如果能熟练使用窗口函数并清楚解释其原理,你在技术面的竞争力会大幅提升。

①窗口函数的基本语法:聚合函数/排序函数OVER(PARTITIONBY分区列ORDERBY排序列ROWS/RANGE窗口范围)。PARTITIONBY定义了数据如何分组(类比GROUPBY但不合并行),ORDERBY定义了组内的排序依据,ROWS/RANGE定义了窗口在组内的滑动范围。

②四大排名函数之间的微妙区别,这是面试中的必考题。题目通常是:“请分别说出ROW_NUMBER、RANK、DENSE_RANK、NTILE这四种排名函数的区别,并给出实际应用场景。”

(a)ROW_NUMBER():为每一行分配一个唯一且连续的序号,即使排序字段值相同也不会分配相同序号。适用场景:为每个用户的订单按时间排序生成1,2,3...的序号,用于提取每个用户的第一次或最后一次订单。

(b)RANK():排序字段值相同的行获得相同序号,但后续行的序号会出现跳号。例如如果前两名并列第一,则没有第二名,第三名直接排到3。适用场景:比赛中出现并列排名后需要跳号的场景。

(c)DENSE_RANK():排序字段值相同的行获得相同序号,但后续行的序号连续不跳号。例如如果前两名并列第一,第三名排到2。适用场景:统计“排名前N”时使用DENSE_RANK可以避免因为跳号而导致结果数量不足N的问题。

(d)NTILE(N):将全部行按排序后的顺序平均分配到N个桶中,返回每行所在的桶编号。适用场景:将用户按消费金额分为高、中、低三个层级,或者将学生成绩分为四个等第。

③聚合窗口函数的精妙之处:SUM、AVG、COUNT等聚合函数配合窗口函数,可以实现移动平均、累计求和等传统分组查询难以完成的计算。例如,SUM(amount)OVER(PARTITIONBYuser_idORDERBYorder_dateROWSBETWEEN2PRECEDINGANDCURRENTROW)可以实现每个用户最近3笔订单金额的滚动总和。面试中,面试官可能会继续追问:“ROWS和RANGE有什么区别?”答案是:ROWS按物理行计算窗口范围,RANGE按值的逻辑范围计算窗口范围。举例来说,如果ORDERBY是日期且窗口范围是“当前行的日期减去2天”,则使用RANGE会更准确,因为它会基于日期的差值而不是行数来决定窗口内的数据范围。

④LEAD和LAG函数是计算连续行为的关键工具。LEAD(字段名,偏移量,默认值)可以获取当前行之后第N行的值,LAG则是获取之前第N行的值。高频应用场景为计算“用户相邻两次下单的时间间隔”或“用户连续登录天数”。这是留存分析和漏斗分析的SQL基础。索引优化与执行计划面试精讲

当你的SQL写对之后,面试官通常会增加难度,追问一句:“你写的这条SQL在大数据量下能跑动吗?你会如何优化它?”这标志着面试进入了高区分度区间。

①索引的底层数据结构:B+树。你不需要深入到叶子节点的页分裂机制,但需要清楚说出B+树的两个核心特性:一是所有数据都存储在叶子节点,叶子节点之间通过双向链表连接,这使得范围查询(大于、小于、BETWEEN)非常高效;二是非叶子节点只存储索引键和指向下一层节点的指针,不存储实际数据,因此树的高度通常很低,查询磁盘I/O次数少。

②最左前缀原则是索引面试的绝对核心。联合索引(a,b,c)能支持哪些查询条件?答案是:能够支持(a)、(a,b)、(a,b,c)三种前缀组合的查询。任何跳过了a的查询条件(如只用b或只用c)都无法走索引。面试中经常出现的陷阱是:“联合索引(a,b,c),查询条件WHEREa=1ANDc=3能否走索引?”答案是:能走一部分,因为a匹配了最左前缀,所以索引可以用到a的过滤,但c的部分失效,此时会使用索引条件下推或用where对索引过滤后的结果再次过滤。

③EXPLAIN执行计划的关键字段解读。面试官可能现场给你一个EXPLAIN的输出结果,让你解读。

(a)type字段:从最好到最差的顺序依次是system、const(使用主键或唯一索引的等值查询)、eq_ref(关联查询中被驱动表使用主键关联)、ref(使用普通索引的等值查询)、range(索引范围扫描)、index(全索引扫描)、ALL(全表扫描)。如果出现了ALL,通常意味着需要优化。

(b)key字段:实际使用的索引名称。如果possible_keys有值而key为NULL,说明优化器认为不走索引更快,但这种情况往往是需要手动干预的信号,或者索引失效了。

(c)Extra字段:如果出现Usingfilesort或Usingtemporary,说明查询使用了临时表或外部排序,这是性能隐患,需要检查ORDERBY或GROUPBY是否命中了索引。如果出现Usingindex(覆盖索引),说明查询只需要从索引中就能获取所有所需数据,不需要回表查询,这是最优性能的标志。

④常见的索引失效场景必须能完整列举,面试中经常以判断题形式出现。

(a)WHERE子句中对索引列进行了函数计算或隐式类型转换,例如WHEREDATE(create_time)='2024-01-01',索引会失效。应改写为WHEREcreate_time>='2024-01-01'ANDcreate_time<'2024-01-02'。

(b)LIKE模糊查询以百分号开头,例如LIKE'%keyword',索引失效。但LIKE'keyword%'可以使用索引。

(c)联合索引不满足最左前缀原则。

(d)使用OR连接多个条件时,如果OR两侧的条件不是所有列都有索引,可能会导致全表扫描。MySQL5.7之后的版本对OR的优化有所改善,但仍需谨慎。本章小结

本章是SQL面试的骨架。执行顺序、JOIN机制、窗口函数、索引优化这四个模块是面试中高频出现的四大支柱。在你继续进入第四章的真题训练之前,请务必确认自己能做到以下三点:第一,能够在一分钟内画出SQL执行顺序的全流程图并在旁边标注每个阶段可以做什么、不可以做什么;第二,能够闭着眼画出七种JOIN的文氏图并准确说出每种JOIN的语法和适用场景;第三,能够将窗口函数的四种排名函数的区别写在白纸上,并用自创的示例数据演示一遍。确认完成这三点后,请进入第四章开始实战演练。第四章80道SQL面试真题精练:从基础到高阶的完整覆盖本章收录的80道SQL面试真题,基于近三年字节跳动、阿里巴巴、腾讯、美团、京东、快手、小红书等互联网大厂数据分析师技术面的真实考题改编而成。题目按照难度递进分为四个模块:基础查询与过滤(第1-15题)、多表连接与子查询(第16-30题)、聚合函数与分组过滤(第31-45题)、窗口函数与高级分析(第46-65题)、索引优化与执行计划(第66-80题)。每一道题均提供完整题干、所有选项的完整文字、正确答案以及逐项解析,解析中包含对每一个选项正确或错误原因的详细阐述。请务必先独立思考并手写答案,再对照解析进行复盘,切勿直接阅读答案。模块一:基础查询与过滤(第1-15题)第1题

题干:在用户信息表user_info中,包含字段user_id(用户ID,主键)、user_name(用户名)、register_date(注册日期,DATE类型)、city(所在城市)。现在需要查询所有注册日期在2023年1月1日及之后的北京用户的信息。以下SQL语句中,语法完全正确且性能最优的是哪一个?

选项:

①SELECT*FROMuser_infoWHEREcity='北京'ANDregister_date>='2023-01-01';

②SELECT*FROMuser_infoWHEREcity='北京'ANDregister_date>'2023-01-01';

③SELECT*FROMuser_infoWHEREcity='北京'ANDregister_date>=TO_DATE('2023-01-01','YYYY-MM-DD');

④SELECT*FROMuser_infoWHEREcity="北京"ANDregister_date>=2023-01-01;

⑤SELECT*FROMuser_infoWHEREcity='北京'ANDregister_date>=STR_TO_DATE('2023-01-01','%Y-%m-%d');

正确答案:①

解析:

选项①正确。该SQL语法完全符合MySQL标准,日期字面量使用标准字符串格式'YYYY-MM-DD'可以直接进行比较,>=运算符包含了注册日期等于2023年1月1日的用户,满足题干“2023年1月1日及之后”的要求。在city列上有索引的情况下,此查询可以利用索引高效过滤。

选项②错误。该SQL使用了>运算符,会排除注册日期恰好等于2023年1月1日的用户,不符合题干要求。虽然语法正确,但结果不满足需求。

选项③错误。MySQL中不存在TO_DATE函数,这是Oracle数据库的函数。在MySQL中使用此函数会直接报错。函数名不符合MySQL语法规范。

选项④错误。该SQL存在两处问题:第一,日期值2023-01-01没有被引号包围,MySQL会将其解析为算术表达式2023减1减1,结果为2021,语义完全错误;第二,虽然MySQL中双引号在特定模式下可用来引用字符串,但标准SQL中双引号用于标识符(如表名、列名),使用单引号引用字符串字面量更为安全和标准。因此该语句不符合要求。

选项⑤错误。该SQL使用了STR_TO_DATE函数将字符串转换为日期类型,虽然语法正确且功能上能实现与选项①相同的结果,但性能较差。在register_date列上存在索引时,对索引列施加函数操作会导致索引失效,退化为全表扫描。在大数据量下,选项①的性能远优于选项⑤。因此不是最优选择。第2题

题干:订单表orders包含字段order_id(订单ID)、user_id(用户ID)、amount(订单金额,DECIMAL类型)、order_status(订单状态,取值:'completed'、'cancelled'、'refunded')、create_time(创建时间,DATETIME类型)。以下哪条查询能够准确统计出每个用户已完成订单的总金额,并按总金额降序排列,只显示总金额大于1000的用户?

选项:

①SELECTuser_id,SUM(amount)AStotal_amountFROMordersWHEREorder_status='completed'ANDtotal_amount>1000GROUPBYuser_idORDERBYtotal_amountDESC;

②SELECTuser_id,SUM(amount)AStotal_amountFROMordersWHEREorder_status='completed'GROUPBYuser_idHAVINGSUM(amount)>1000ORDERBYtotal_amountDESC;

③SELECTuser_id,SUM(amount)AStotal_amountFROMordersGROUPBYuser_idHAVINGorder_status='completed'ANDSUM(amount)>1000ORDERBYtotal_amountDESC;

④SELECTuser_id,SUM(amount)AStotal_amountFROMordersWHEREorder_status='completed'GROUPBYuser_idHAVINGtotal_amount>1000ORDERBYtotal_amountDESC;

⑤SELECTuser_id,SUM(amount)FROMordersWHEREorder_status='completed'ANDSUM(amount)>1000GROUPBYuser_idORDERBYSUM(amount)DESC;

正确答案:②

解析:

选项②正确。执行流程为:首先通过WHERE子句筛选出已完成状态的订单,然后按user_id分组,对每个用户的订单金额求和,之后通过HAVING子句过滤出总和大于1000的用户,最后按total_amount降序排列。整个过程完全符合SQL执行顺序:WHERE→GROUPBY→HAVING→SELECT→ORDERBY。WHERE中使用了order_status='completed'正确过滤,HAVING中使用了聚合函数SUM(amount)的条件,语法完全正确。

选项①错误。该SQL在WHERE子句中使用了total_amount>1000,但total_amount是SELECT中定义的别名,在WHERE执行时该别名尚未生效。此外,WHERE子句中不能使用聚合函数SUM,因此语法错误。

选项③错误。该SQL在HAVING子句中使用了order_status='completed',但order_status是原始列,没有出现在GROUPBY中,也没有被聚合函数包裹。HAVING子句中的条件应该要么是聚合函数的条件,要么是出现在GROUPBY中的列。order_status既不在GROUPBY中,也没有用聚合函数处理,因此这条SQL在严格模式下会报错。

选项④错误。该SQL在HAVING中使用了total_amount>1000,其中total_amount是SELECT中定义的别名。在MySQL中,HAVING子句支持使用别名(因为执行顺序在SELECT之后),因此这一条在MySQL中实际上可以执行,并且结果与选项②相同。然而,标准SQL和大多数其他数据库系统(如PostgreSQL、Oracle)不允许在HAVING中直接使用SELECT中的别名。面试中如果明确指出使用MySQL,可以认为语法正确,但从严谨性和跨数据库兼容性角度,选项②更标准且所有数据库都支持。考虑到大厂面试可能考察SQL标准知识,以及面试官可能故意设置此陷阱来考察你对执行顺序和标准语法的理解,选项②是绝对安全且公认的最佳答案。

选项⑤错误。该SQL在WHERE子句中使用了聚合函数SUM(amount)作为过滤条件,这是不允许的。WHERE在执行时聚合还未发生,无法使用聚合函数,此语句会直接报错。第3题

题干:现有用户行为日志表user_log,包含字段user_id、event_type(事件类型,如'click'、'view'、'purchase')、event_time(事件发生时间)。要求查询每个用户在2024年6月1日当天的最早一次事件记录。以下哪条SQL无法实现该需求?

选项:

①SELECTuser_id,MIN(event_time)ASfirst_event_timeFROMuser_logWHEREevent_time>='2024-06-01'ANDevent_time<'2024-06-02'GROUPBYuser_id;

②SELECTuser_id,event_timeASfirst_event_timeFROMuser_logWHEREevent_time>='2024-06-01'ANDevent_time<'2024-06-02'AND(user_id,event_time)IN(SELECTuser_id,MIN(event_time)FROMuser_logWHEREevent_time>='2024-06-01'ANDevent_time<'2024-06-02'GROUPBYuser_id);

③SELECTuser_id,MIN(event_time)ASfirst_event_timeFROMuser_logWHEREDATE(event_time)='2024-06-01'GROUPBYuser_id;

④SELECTDISTINCTuser_id,FIRST_VALUE(event_time)OVER(PARTITIONBYuser_idORDERBYevent_timeASC)ASfirst_event_timeFROMuser_logWHEREevent_time>='2024-06-01'ANDevent_time<'2024-06-02';

⑤SELECTuser_id,event_timeASfirst_event_timeFROM(SELECTuser_id,event_time,ROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYevent_timeASC)ASrnFROMuser_logWHEREevent_time>='2024-06-01'ANDevent_time<'2024-06-02')tWHERErn=1;

正确答案:④

解析:本题要求选出“无法实现”的选项。

选项④无法实现需求。虽然语法上使用了窗口函数FIRST_VALUE,但FIRST_VALUE()返回的是窗口内每一行对应的第一个值,而不是对结果集进行聚合去重。此查询写的是SELECTDISTINCT...FIRST_VALUE(...)OVER(...)...,这会导致FIRST_VALUE在每一行上计算,DISTINCT无法消除因为event_time不同而产生的多行,最终结果不是每个用户仅一行,而是每个用户可能返回多行。要正确使用FIRST_VALUE实现该需求,需要配合子查询和ROW_NUMBER或直接使用聚合。该选项的逻辑有误,无法得出每个用户一条最早记录的需求。

选项①能实现。通过WHERE限定时间范围,然后对user_id分组取MIN(event_time),直接得到每个用户最早事件时间,简单高效。

选项②能实现。通过子查询找出每个用户的最小event_time,然后外层用IN子句匹配出具体的完整记录。虽然写法略复杂,但可以实现。

选项③能实现,但不推荐。在event_time上使用DATE函数会导致索引失效,性能差,但功能上可以实现需求。在大数据量下,这种写法会导致全表扫描,面试中应明确指出其性能问题。

选项⑤能实现。使用ROW_NUMBER()窗口函数对每个用户的记录按时间排序生成序号,然后在外层筛选rn=1的行,这是非常标准的实现方式,性能良好且语法清晰。第4题

题干:商品表product包含字段product_id、product_name、category(商品类目)、price(价格)。要求查询每个类目中价格第二高的商品信息(不包含价格并列的情况,即假设每个类目内所有商品价格均不相同)。以下SQL语句正确的是?

选项:

①SELECT*FROMproductp1WHEREprice=(SELECTMAX(price)FROMproductp2WHEREp1.category=p2.categoryANDprice<(SELECTMAX(price)FROMproductp3WHEREp1.category=p3.category));

②SELECT*FROMproductp1WHEREprice=(SELECTDISTINCTpriceFROMproductp2WHEREp1.category=p2.categoryORDERBYpriceDESCLIMIT1OFFSET1);

③SELECT*FROMproductp1WHEREprice=(SELECTpriceFROMproductp2WHEREp1.category=p2.categoryORDERBYpriceDESCLIMIT1,1);

④SELECT*FROM(SELECT*,ROW_NUMBER()OVER(PARTITIONBYcategoryORDERBYpriceDESC)ASrnFROMproduct)tWHERErn=2;

⑤以上均不正确

正确答案:④

解析:

选项④正确。使用ROW_NUMBER()窗口函数按category分区,并在每个分区内按price降序排序生成行号rn。价格最高的商品rn=1,价格第二高的商品rn=2,外层筛选rn=2即可精确获取每个类目的第二高价商品。语法标准,逻辑清晰,性能良好。

选项①存在逻辑漏洞。该语句试图通过双重嵌套找出每个类目中小于最高价的最大值,即第二高价。但内层子查询中(SELECTMAX(price)FROMproductp3WHEREp1.category=p3.category)在每个p1行上都会执行一次,而中间层的子查询(SELECTMAX(price)FROMproductp2WHEREp1.category=p2.categoryANDprice<...)同样多次执行。逻辑上是正确的,但存在严重的性能问题且极度复杂。更重要的是,如果类目中只有一件商品,则第二高价格不存在,此查询会返回空,而不会优雅地处理这种情况。因此这不被视为最佳实践,但在逻辑上勉强可行。不过从标准面试答案来看,选项④明显优于①。

选项②错误。在WHERE子句中使用子查询时,如果子查询返回多个值(如LIMIT),某些数据库会报错。而且这里LIMIT1OFFSET1返回第二高的价格,但语法上ORDERBYpriceDESCLIMIT1OFFSET1在子查询中可能不支持返回标量值。此外,外层WHEREprice=(...)要求子查询返回单一值,如果类目内商品不够两件,子查询返回空,可能导致问题。在MySQL中这个写法可能能执行,但不够可靠。

选项③错误。LIMIT1,1是MySQL特有的语法,意思是偏移1行取1行,即第二行。但此语法不是标准SQL,且在子查询中可能不被优化器很好地支持。从跨数据库兼容性和标准性来看,不推荐。

选项⑤错误,因为④是正确的。第5题

题干:有一张记录用户每日步数的表daily_steps,字段为user_id、date(日期)、steps(步数)。现需要统计每个用户在任意连续3天内步数全部大于10000的天数区间(即找出所有满足“连续3天每天步数均大于10000”的时间段,输出该连续区间的起始日期和结束日期)。你认为以下哪种思路是该问题的正确SQL实现方向?

选项:

①使用自连接,将表按user_id和相邻日期进行JOIN,然后筛选连续三天的条件

②对每个用户按日期排序后使用LAG函数判断前一天和后一天步数,然后筛选连续三天

③先筛选出步数>10000的记录,然后用ROW_NUMBER减去日期的差值来识别连续区间,再分组统计

④使用递归CTE遍历每个用户的所有日期,动态构建连续区间

⑤使用CROSSJOIN生成所有可能的连续三天日期组合,再与步数表关联验证

正确答案:③

解析:

选项③正确。这道题是典型的“识别连续区间”问题,最经典的解法被称为“gapsandislands”问题。实现思路如下:首先过滤出steps>10000的记录,然后为每个用户的这些记录按日期排序,计算ROW_NUMBER(),再用日期减去ROW_NUMBER()的天数(如DATE_SUB(date,INTERVALrnDAY))。如果日期是连续的,这个差值会是一个相同的常量;一旦中间有间断,差值就会变化。然后按user_id和这个差值分组,统计组内记录数,如果大于等于3,就说明存在连续3天以上的记录,取出MIN(date)作为起始、MAX(date)作为结束即可。这是最高效且逻辑清晰的解法。

选项①不是最优方向。自连接可以解决连续两天的问题,但要扩展到连续三天,自连接会变得非常复杂且性能极差,尤其是当数据量大时。不推荐作为主要思路。

选项②可作为辅助判断,但不能直接得出所有连续区间。LAG可以判断当前行与前一行的关系,但要识别完整的连续区间需要更复杂的处理,一般仍需结合分组差值法。单纯用LAG无法优雅地输出连续区间的起止日期。

选项④方向可行但过度设计。递归CTE可以遍历,但在MySQL8.0中虽然支持CTE,递归效率在大数据量下较低,且对于识别连续区间问题,选项③的方法更具普适性和高效性。通常不会优先推荐递归CTE来做这种常规需求。

选项⑤方向不可行。CROSSJOIN生成所有连续三天组合会产生指数级的数据量膨胀,性能灾难,完全不具备生产环境可行性。第6题

题干:在员工表employee中,字段包括emp_id(员工ID)、name、department(部门)、salary(薪资)。现要查询每个部门薪资高于部门平均薪资的员工姓名和薪资。以下SQL语句中,哪个是正确的?

选项:

①SELECTname,salaryFROMemployeeWHEREsalary>(SELECTAVG(salary)FROMemployeeGROUPBYdepartment);

②SELECT,e1.salaryFROMemployeee1WHEREe1.salary>(SELECTAVG(e2.salary)FROMemployeee2WHEREe1.department=e2.department);

③SELECTname,salaryFROMemployeeGROUPBYdepartmentHAVINGsalary>AVG(salary);

④SELECT,e1.salaryFROMemployeee1JOIN(SELECTdepartment,AVG(salary)ASavg_salaryFROMemployeeGROUPBYdepartment)e2ONe1.department=e2.departmentWHEREe1.salary>e2.avg_salary;

⑤SELECTname,salaryFROMemployeeWHERE(department,salary)IN(SELECTdepartment,MAX(salary)FROMemployeeGROUPBYdepartment);

正确答案:④

解析:

选项④正确。通过子查询先计算出每个部门的平均薪资,生成一个派生表e2,然后与原表e1按department进行JOIN,再用WHERE条件过滤出e1.salary大于部门平均薪资的行。这个写法逻辑清晰,关联正确,且性能可以通过在department和salary上建立索引来优化。

选项②同样在逻辑上能实现需求,且语法正确。它使用了一个关联子查询,对于e1的每一行,子查询计算e1所在部门的平均薪资,然后进行比较。功能上可以正确运行,也经常作为正确答案出现。但此题中选项④使用了JOIN方式,通常在大数据量下JOIN比关联子查询效率更高,且面试中通常推荐使用JOIN方案。因此④是更优的写法。不过,如果面试中同时出现②和④,两者都可作为正确回答,但④是更被推荐的生产环境写法。由于题干问“哪个是正确的”,两者都是正确的。但考虑到选择题通常单选最佳,我们在此强调④的JOIN方式更高效。本题答案为④。

选项①错误。子查询SELECTAVG(salary)FROMemployeeGROUPBYdepartment返回每个部门的平均薪资,这是一个多行的结果集。但外层WHERE中salary直接与一个多行的结果集进行比较,会引发错误,因为比较操作要求标量子查询。除非数据库能隐式转换为单值,否则会报错。

选项③错误。GROUPBYdepartment后,SELECT中的name没有出现在GROUPBY中,也没有用聚合函数包裹,这在标准SQL中是不允许的。MySQL默认模式下可能不报错但返回随机的一个name,但这不符合需求且不可靠。

选项⑤错误。该语句意图是找出薪资等于部门最高薪的员工,而不是高于平均薪资,与题干不符。第7题

题干:日志表access_log记录了每次页面访问,字段包括log_id(主键)、user_id、page_url、access_time(DATETIME)。现在希望找出每个用户在同一个小时内访问次数超过5次的时段。以下哪种方式最为合理?

选项:

①按user_id和access_time的小时部分分组,然后用HAVINGCOUNT()>5过滤

②使用窗口函数COUNT()OVER(PARTITIONBYuser_id,DATE_FORMAT(access_time,'%Y%m%d%H')ORDERBYaccess_time)然后筛选

③先用DATE_FORMAT生成小时字段,再按user_id和该小时字段分组,HAVINGCOUNT()>5

④对access_time取整到小时,使用TIMESTAMPDIFF函数做自连接

⑤创建一个每小时粒度的汇总表,然后关联查询

正确答案:③

解析:

选项③正确。使用DATE_FORMAT(access_time,'%Y-%m-%d%H')或其他截断函数将精确到秒的时间转化为小时级字符串,然后按user_id和这个小时字符串进行分组,利用HAVINGCOUNT()>5筛选出超过5次的记录。这是最直接、最符合SQL语义的实现方式。

选项①描述不清,但思路类似,但如果没有明确处理时段的截断,仅说“小时部分”可能会导致歧义,但本质与③相同。不过③明确给出了实现方式,是最佳答案。

选项②能够实现,但是窗口函数在这里并不是最适合的场景。窗口函数会为每一行保留其对应的计数,但如果只是要找出用户和时段,还需要额外的去重操作。相较于简单分组,窗口函数写法更复杂,性能也未必更优,不是“最合理”的方式。

选项④自连接方案过于复杂,且性能低下,不应作为常规解法。

选项⑤虽然也是工程上的一种优化手段(预计算),但面试中是即时查询场景,不能假设已经有现成的汇总表。面试考察的是单条SQL解决问题的能力。第8题

题干:存在表score记录学生成绩,字段student_id、course_id、score。要求找出每一门课程中成绩排名第三的学生ID及其成绩(不考虑并列,即假设所有成绩都不同)。以下SQL语句哪一个是正确的?

选项:

①SELECTstudent_id,scoreFROMscores1WHERE(SELECTCOUNT(DISTINCTscore)FROMscores2WHEREs1.course_id=s2.course_idANDs2.score>s1.score)=2;

②SELECTstudent_id,scoreFROM(SELECTstudent_id,course_id,score,RANK()OVER(PARTITIONBYcourse_idORDERBYscoreDESC)ASrkFROMscore)tWHERErk=3;

③SELECTstudent_id,scoreFROM(SELECTstudent_id,course_id,score,ROW_NUMBER()OVER(PARTITIONBYcourse_idORDERBYscoreDESC)ASrnFROMscore)tWHERErn=3;

④SELECTstudent_id,scoreFROMscoreWHEREscore=(SELECTDISTINCTscoreFROMscores2WHEREs1.course_id=s2.course_idORDERBYscoreDESCLIMIT2,1);

⑤SELECTstudent_id,scoreFROMscoreWHERE(course_id,score)IN(SELECTcourse_id,MIN(score)FROM(SELECTcourse_id,scoreFROMscoreORDERBYscoreDESCLIMIT3)GROUPBYcourse_id);

正确答案:③

解析:

选项③正确。使用ROW_NUMBER()按课程分区,分数降序排名,行号rn=3的就是第三名。ROW_NUMBER在处理无并列情况时与RANK效果相同,但语义上更精确地返回“第三个位置”,符合题干不考虑并列的条件。

选项①也能正确实现。它通过一个关联子查询统计同课程中分数高于当前学生的不同分数个数。如果恰好有两个不同的分数比当前学生高,那么当前学生就是第三名。这个写法虽然经典,但性能相对较差,因为每行都要执行一次子查询。不过逻辑上完全正确。但在本题多个正确选项中,③使用窗口函数是更现代、更高效的写法,通常是面试中的推荐答案。由于本题是单选,③是标准答案。

选项②错误。RANK()在处理并列情况时,如果出现并列第一,那么可能没有第三名,直接跳到3(如果有第三名),但如果分数不重复,RANK和ROW_NUMBER结果一样。但在有重复分数的情况下,题干说“不考虑并列”,意味着数据中可能没有并列,但万一有并列,RANK的行为是跳过序号,而ROW_NUMBER不会跳过,会更精确地给出第三行。通常面试题要求不考虑并列时,使用ROW_NUMBER更符合“第三名”的字面要求。而且②中使用了RANK却筛选rk=3,如果有并列第一,可能rk=2就有人排第二名,rk=3实际是第二高的分数,不符合需求。因此②不正确。

选项④错误。LIMIT2,1是MySQL特有的分页语法,但在这里子查询没有被正确关联,s1未定义,且子查询返回的是全局第三高的分数,不是每个课程的第三高,语法和逻辑均有问题。

选项⑤错误。该语句逻辑混乱,LIMIT3无法给出每个课程的第三名,且GROUPBYcourse_id搭配MIN会选出每个课程第三高之后的最小值,完全不符合题意。第9题

题干:表user_follow记录了用户的关注关系,字段follower_id(关注者ID)、followee_id(被关注者ID),主键为(follower_id,followee_id)。要求找出所有互相关注的用户对(即A关注了B,同时B也关注了A)。假设关注关系不会重复。请选出最优的查询语句。

选项:

①SELECTa.follower_id,a.followee_idFROMuser_followa,user_followbWHEREa.follower_id=b.followee_idANDa.followee_id=b.follower_idANDa.follower_id<a.followee_id;

②SELECTa.follower_idASuser1,a.followee_idASuser2FROMuser_followaINNERJOINuser_followbONa.follower_id=b.followee_idANDa.followee_id=b.follower_idWHEREa.follower_id<a.followee_id;

③SELECTDISTINCTLEAST(follower_id,followee_id)ASuser1,GREATEST(follower_id,followee_id)ASuser2FROMuser_followaWHEREEXISTS(SELECT1FROMuser_followbWHEREa.follower_id=b.followee_idANDa.followee_id=b.follower_id);

④SELECTfollower_id,followee_idFROMuser_followWHERE(follower_id,followee_id)IN(SELECTfollowee_id,follower_idFROMuser_follow);

⑤以上都可以

正确答案:⑤

解析:

选项⑤正确。上述四种写法均能正确找出所有互相关注的用户对。

选项①使用隐式连接(逗号连接)和自连接条件,最后通过a.follower_id<a.followee_id确保每对只出现一次且排序统一,结果正确。

选项②使用INNERJOIN自连接,逻辑与①相同,写法更规范,并同样用小于号去重和排序。

选项③使用EXISTS子查询判断反向关系是否存在,并利用LEAST和GREATEST函数将两个用户ID归一化为升序对,去重且输出格式统一。

选项④使用IN子句直接匹配反向关注对。但注意,此写法可能存在问题:IN子查询返回的是(followee_id,follower_id)的列组合,外层(follower_id,followee_id)与之匹配,这可以找出互相关注。但此写法会返回两行(如(A,B)和(B,A)都会出现在结果中),因为没有去重。但题干并未明确要求去重,只是要求“找出所有互相关注的用户对”,即便返回(A,B)和(B,A)也算找出了这对关系,只是数据重复。如果要去重,可以再加DISTINCT或使用LEAST/GREATEST。因此④也能完成任务,只是不如前三种优雅。综合来看,四种方式均能实现核心功能,故⑤最准确。第10题

题干:在用户表user中,字段user_id、mobile(手机号)。现在需要删除手机号重复的记录,保留每个手机号中user_id最小的那一行。数据表中存在完全重复的行吗?假设只存在手机号重复但user_id不同的情况。请给出DELETE语句的正确写法。

选项:

①DELETEFROMuserWHEREuser_idNOTIN(SELECTMIN(user_id)FROMuserGROUPBYmobile);

②DELETEu1FROMuseru1INNERJOINuseru2ONu1.mobile=u2.mobileANDu1.user_id>u2.user_id;

③DELETEFROMuserWHERE(mobile,user_id)NOTIN(SELECTmobile,MIN(user_id)FROMuserGROUPBYmobile);

④DELETEFROMuserWHEREuser_id>(SELECTMIN(user_id)FROMuseru2WHEREuser.mobile=u2.mobile);

⑤以上都可以

正确答案:②

解析:

选项②正确。这是MySQL中删除重复记录的标准写法之一。通过自连接,将user表别名u1与u2按照手机号相等且u1的user_id大于u2的user_id的条件进行连接,这样u1就代表了那些拥有相同手机号但user_id不是最小的记录,然后DELETEu1即可删除这些重复行。这种写法在MySQL中能够正确执行。

选项①错误。在MySQL中,不能在同一张表的子查询中直接进行DELETE操作,会报错“Youcan'tspecifytargettable'user'forupdateinFROMclause”。需要通过嵌套一层子查询或者使用多表删除语法来规避这个限制。因此此写法在MySQL中不被允许。

选项③错误。与①类似,同样存在不能在同一张表的子查询中直接操作的问题,且NOTIN子句后接双列子查询在MySQL中限制更多,通常不推荐。

选项④语法正确,通过关联子查询找到每个手机号的最小user_id,然后删除所有大于该最小值的记录。在MySQL中,这种写法理论上可以执行,因为使用的是相关子查询,且子查询中用了别名u2,与主表没有直接同表限制冲突。不过,在老的MySQL版本中可能有限制,但MySQL5.7及以上允许。然而,这种写法可能会因为每删除一行都要执行一次子查询导致性能较差。但语法上是可行的。不过从最佳实践来看,②是最清晰高效且被广泛使用的写法。所以②是标准答案。

选项⑤错误,因为①和③有语法问题。第11题

题干:表order_detail包含字段order_id、product_id、quantity(购买数量)、price(单价)。需要计算每个订单的总金额(总金额=各商品数量乘以单价之和)。以下哪条SQL正确?

选项:

①SELECTorder_id,SUM(quantity*price)AStotal_amountFROMorder_detailGROUPBYorder_id;

②SELECTorder_id,SUM(quantity)*SUM(price)AStotal_amountFROMorder_detailGROUPBYorder_id;

③SELECTorder_id,SUM(quantity*price)AStotal_amountFROMorder_detail;

④SELECTorder_id,(quantity*price)AStotal_amountFROMorder_detailGROUPBYorder_id;

⑤SELECTorder_id,SUM(quantity*price)AStotal_amountFROMorder_detailWHEREquantity>0ANDprice>0GROUPBYorder_id;

正确答案:①

解析:

选项①正确。通过GROUPBYorder_id,对每个订单内的明细行计算quantity乘以price,然后使用SUM汇总,得到正确的订单总金额。

选项②错误。SUM(quantity)*SUM(price)是先分别计算订单中所有商品的数量总和与单价总和,再相乘。这个结果在数学上不等于每个商品的数量乘以单价的累加。例如一个订单有两件商品A(数量2,单价10)和B(数量3,单价20),正确总金额为210+320=80。但②的计算为(2+3)*(10+20)=5*30=150,完全错误。

选项③错误。缺少GROUPBY子句,在标准SQL中会报错,因为SELECT中出现了非聚合列order_id且没有分组。MySQL默认可能允许但返回随机的一条order_id,且sum是对全表汇总,不是按订单汇总。

选项④错误。quantity*price没有使用聚合函数,且GROUPBYorder_id后,quantity和price没有出现在GROUPBY中,也未使用聚合函数,语法错误。

选项⑤在逻辑上与①相同,只是多加了WHERE过滤。题干没有要求排除quantity或price为0或负数的记录,所以①已经足够。⑤在某些业务场景下是更严谨的,但作为回答哪个SQL能正确计算订单总金额,①就是正确的,且⑤只是加了额外条件,不改变基本正确性。但①是直接且符合需求的。因此选①。第12题

题干:一个记录用户登录的表login,字段user_id、login_time(DATETIME)。现在需要找出每个用户的最后一次登录时间。以下哪条SQL语句是错误的?

选项:

①SELECTuser_id,MAX(login_time)FROMloginGROUPBYuser_id;

②SELECTDISTINCTuser_id,FIRST_VALUE(login_time)OVER(PARTITIONBYuser_idORDERBYlogin_timeDESC)ASlast_loginFROMlogin;

③SELECTuser_id,login_timeFROMloginWHERE(user_id,login_time)IN(SELECTuser_id,MAX(login_time)FROMloginGROUPBYuser_id);

④SELECTuser_id,login_timeFROMloginl1WHERElogin_time=(SELECTMAX(login_time)FROMloginl2WHEREl1.user_id=l2.user_id

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论