JOIN、子查询、聚合函数不会?5道SQL高频面试题带你逆袭

第一章:JOIN、子查询、聚合函数不会?5道SQL高频面试题带你逆袭

在SQL面试中,JOIN、子查询和聚合函数是考察候选人数据处理能力的核心知识点。掌握这些概念不仅有助于通过技术面试,还能提升实际工作中的查询效率与逻辑表达能力。以下是五道高频出现的SQL题目,帮助你系统性突破常见难点。

查找每个部门薪资最高的员工信息

该问题涉及分组、聚合函数和子查询的结合使用。可通过子查询先获取各部门最高薪资,再匹配员工表获取完整信息。

-- 先找出每个部门的最高工资
SELECT department_id, MAX(salary) AS max_salary
FROM employees
GROUP BY department_id;

-- 再关联原表获取对应员工信息
SELECT e.name, e.salary, e.department_id
FROM employees e
INNER JOIN (
    SELECT department_id, MAX(salary) AS max_salary
    FROM employees
    GROUP BY department_id
) t ON e.department_id = t.department_id AND e.salary = t.max_salary;

统计连续登录超过两天的用户

此题考察日期函数与自连接(SELF-JOIN)的应用。关键在于通过日期差筛选出连续行为。
  1. 使用 DATE_SUB 或 INTERVAL 函数计算前一天
  2. 将登录记录表自连接,匹配当前登录日与前一日的记录
  3. 按用户分组并统计满足条件的次数

各科成绩排名前两名的学生

利用窗口函数 ROW_NUMBER() 进行分组内排序,是解决“Top-N”类问题的标准方案。

SELECT student_id, subject, score
FROM (
    SELECT student_id, subject, score,
           ROW_NUMBER() OVER (PARTITION BY subject ORDER BY score DESC) AS rn
    FROM scores
) ranked
WHERE rn <= 2;

用户购物行为分析常用指标对比

指标SQL实现方式函数类型
总订单数COUNT(order_id)聚合函数
人均消费AVG(amount)聚合函数
排名ROW_NUMBER()窗口函数

多表关联的经典模式

使用 INNER JOIN 和 LEFT JOIN 能有效整合用户、订单与商品三张表的数据关系,构建完整业务视图。

第二章:JOIN操作深度解析与实战应用

2.1 INNER JOIN 与 OUTER JOIN 的核心区别与使用场景

在SQL查询中,JOIN操作用于合并两个或多个表的数据。INNER JOIN仅返回两表中匹配的记录,而OUTER JOIN则包含未匹配的行,分为LEFT、RIGHT和FULL三种类型。
核心逻辑对比
  • INNER JOIN:仅保留键值在两表中均存在的记录
  • LEFT OUTER JOIN:保留左表全部记录,右表无匹配时填充NULL
  • RIGHT OUTER JOIN:保留右表全部记录,左表缺失则补NULL
典型应用场景
SELECT users.name, orders.amount 
FROM users 
LEFT OUTER JOIN orders ON users.id = orders.user_id;
该查询列出所有用户及其订单金额,即使某用户无订单也会显示(amount为NULL),适用于统计分析场景。而使用INNER JOIN将只返回有订单的用户,适合精确匹配需求。

2.2 多表连接中的逻辑推理与执行顺序剖析

在复杂查询场景中,多表连接的执行顺序直接影响性能与结果准确性。数据库优化器基于统计信息决定表的访问路径与连接次序,但理解其底层逻辑对编写高效SQL至关重要。
连接顺序的逻辑推演
通常,优化器会优先处理筛选性强的表,以减少中间结果集大小。例如,在三表JOIN中,先执行过滤效果最好的表连接,可显著降低后续操作的数据量。
执行计划示例
SELECT u.name, o.order_id, p.title 
FROM users u 
JOIN orders o ON u.id = o.user_id 
JOIN products p ON o.product_id = p.id 
WHERE u.status = 'active' AND p.category = 'electronics';
上述语句中,usersorders 先连接,因 status = 'active' 可快速缩小用户范围,再与 products 关联,避免全量笛卡尔积。
连接类型影响执行路径
  • INNER JOIN:仅保留匹配行,常被提前执行以缩减数据流
  • LEFT JOIN:左表为驱动表,右表匹配失败仍保留左表记录
  • 执行顺序并非书写顺序,而是由优化器重写决定

2.3 使用JOIN优化查询性能的典型策略

在复杂查询中,合理使用JOIN操作可显著提升数据检索效率。关键在于减少不必要的数据扫描和中间结果集膨胀。
选择合适的JOIN类型
优先使用INNER JOIN而非LEFT JOIN,当业务逻辑允许时,避免保留空匹配行带来的额外开销。
利用索引加速连接
确保连接字段(如user_id)在两张表中均有索引。例如:
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_users_id ON users(id);
上述语句为订单表和用户表的关联字段建立索引,使哈希或嵌套循环连接更高效。
小表驱动大表
在嵌套循环JOIN中,数据库通常以小表作为驱动表。可通过STRAIGHT_JOIN提示强制顺序:
SELECT /*+ STRAIGHT_JOIN */ u.name, o.total 
FROM users u JOIN orders o ON u.id = o.user_id;
该提示确保users表作为外层循环,减少整体比较次数。

2.4 自连接在层级结构中的巧妙应用实例

在处理具有层级关系的数据时,如组织架构或分类目录,自连接提供了简洁高效的查询方案。通过将表与自身进行关联,可以轻松提取父子关系。
组织架构查询示例
假设有一个员工表 employees,包含 idnamemanager_id 字段,可通过自连接查找每位员工及其直属领导的姓名:
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
该查询中,e 代表员工实例,m 代表管理者实例。通过 LEFT JOIN 确保即使顶级领导(无上级)也能被包含。
多级层级遍历
  • 适用于无限层级的树形结构
  • 结合递归CTE可实现深度遍历
  • 性能优于应用层逐级查询

2.5 实战演练:从面试真题看JOIN的灵活运用

在SQL面试中,JOIN操作是考察重点。一道典型题目要求找出“所有没有订单记录的客户信息”,这需要使用LEFT JOIN结合IS NULL条件筛选。
问题建模
假设有两张表:`customers(id, name)` 和 `orders(customer_id, order_date)`。目标是获取未下单客户。

SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.customer_id IS NULL;
上述语句通过LEFT JOIN保留左表全部记录,右表无匹配时字段值为NULL,再用WHERE过滤出未匹配项。
思维拓展
常见变体包括:
  • 查找至少下过两次订单的客户(GROUP BY + HAVING)
  • 查询每个客户的最近一次订单(子查询或窗口函数)
  • 对比有订单与无订单客户的地域分布(UNION 或 CASE 分组)
JOIN的灵活运用不仅限于关联数据,更是逻辑推理的体现。

第三章:子查询机制与常见模式精讲

3.1 标量子查询与行子查询的语义差异

在SQL查询中,标量子查询和行子查询的核心区别在于返回结果的结构与使用场景。
标量子查询:单值返回
标量子查询返回**单一值**(一行一列),常用于SELECT、WHERE等需要标量表达式的上下文。
SELECT name, 
       (SELECT MAX(salary) FROM employees) AS max_salary
FROM employees;
该子查询在整个查询中仅执行一次,返回全局最高薪资作为标量值,适用于聚合值引用。
行子查询:结构化结果
行子查询返回**一行多列**,通常用于比较复合条件。
SELECT * FROM employees 
WHERE (dept_id, salary) = 
      (SELECT dept_id, MAX(salary) 
       FROM employees GROUP BY dept_id HAVING dept_id = 10);
此处子查询返回部门10中最高薪资员工的部门与薪资组合,需匹配完整行值。
类型返回结构使用位置
标量子查询单行单列SELECT列表、条件表达式
行子查询单行多列行比较操作

3.2 EXISTS 与 IN 在子查询中的性能对比分析

在处理子查询时,EXISTS 和 IN 是两种常见方式,但其执行机制和性能表现存在显著差异。
执行逻辑差异
EXISTS 基于布尔判断,只要子查询返回任意一行即停止扫描,适合检查存在性;而 IN 需要遍历完整结果集并构建值列表,适用于明确的值匹配。
性能对比示例
-- 使用 EXISTS:短路求值,效率高
SELECT * FROM users u 
WHERE EXISTS (
  SELECT 1 FROM orders o 
  WHERE o.user_id = u.id
);

-- 使用 IN:需完全执行子查询
SELECT * FROM users u 
WHERE u.id IN (
  SELECT user_id FROM orders
);
上述代码中,EXISTS 在找到第一个关联订单后立即返回,而 IN 必须完成整个子查询。当子查询结果庞大时,IN 的性能明显下降。
适用场景总结
  • EXISTS 更适合大表关联、仅需判断存在的场景
  • IN 适用于子查询结果集小且固定的枚举匹配
  • 若子查询含 NULL 值,IN 可能导致意外结果,EXISTS 更稳健

3.3 关联子查询解决复杂业务问题的思维路径

在处理复杂的业务逻辑时,关联子查询提供了一种逐行匹配、上下文感知的查询方式。它允许外层查询与内层子查询通过相关字段建立动态联系,适用于“每条记录都需要独立计算条件”的场景。
典型应用场景
例如,在每个部门中找出薪资高于该部门平均工资的员工:

SELECT e1.name, e1.salary, e1.department
FROM employees e1
WHERE e1.salary > (
    SELECT AVG(e2.salary)
    FROM employees e2
    WHERE e2.department = e1.department
);
上述代码中,外层查询的每一行都会触发一次子查询执行。子查询中的 e2.department = e1.department 建立了关联,确保计算的是当前员工所在部门的平均薪资。这种“以行为单位绑定上下文”的机制,是解决分组比较类问题的核心思路。
思维路径拆解
  • 识别主实体:明确外层查询的数据主体(如员工);
  • 定位比较维度:确定需按什么维度分组比较(如部门);
  • 构造关联条件:在子查询中引入外层变量,形成动态过滤;
  • 验证执行逻辑:理解每行触发一次子查询的运行机制。

第四章:聚合函数与分组统计高级技巧

4.1 GROUP BY 与 HAVING 的协同工作原理

在SQL查询中,GROUP BY用于将数据按指定列分组,而HAVING则用于过滤分组后的结果集。与WHERE作用于行不同,HAVING作用于聚合后的组。
执行顺序解析
SQL语句的逻辑执行顺序决定了GROUP BY先生成分组,随后HAVING对聚合值进行条件筛选。
SELECT department, AVG(salary) AS avg_sal
FROM employees
GROUP BY department
HAVING AVG(salary) > 5000;
上述语句首先按部门分组,计算每组平均工资,再通过HAVING保留平均工资超过5000的部门。其中,AVG(salary)是聚合函数,不能在WHERE中使用。
常见聚合函数
  • COUNT():统计组内行数
  • SUM():求和
  • MAX()/MIN():获取极值

4.2 聚合函数在空值和重复数据下的行为解析

空值处理机制
大多数聚合函数(如 SUMAVG)会自动忽略 NULL 值。例如,AVG 仅对非空值计算平均值,避免结果失真。
SELECT AVG(salary) FROM employees;
若表中部分 salaryNULL,该查询仍返回其余有效记录的均值,体现聚合函数的容错性。
重复数据的影响
使用 COUNT(*) 会包含所有行,而 COUNT(column) 忽略列值为 NULL 的行。重复值则会被正常计入。
函数NULL 处理重复值处理
COUNT(*)计入计入
AVG(col)忽略计入
SUM(col)忽略计入
合理理解这些行为有助于编写精确的数据分析查询。

4.3 使用窗口函数扩展聚合分析能力

传统聚合函数(如 SUM、COUNT、AVG)将多行数据归约为单个值,而窗口函数在保留原始行的基础上执行跨行计算,极大增强了分析能力。
窗口函数基本结构
SELECT 
  order_date,
  sales,
  AVG(sales) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM sales_data;
该语句计算每条记录前两日及当日的移动平均销售额。OVER() 定义窗口范围:ORDER BY 指定排序逻辑,ROWS BETWEEN ... 确定行边界。
常用场景与函数类型
  • RANK(), DENSE_RANK():实现分组内排名
  • SUM() OVER(PARTITION BY):按类别累计求和
  • LAG()/LEAD():访问前后行数据,适用于同比/环比分析

4.4 综合案例:多维度统计报表的SQL实现

在企业数据分析中,多维度统计报表是决策支持的核心工具。通过SQL对销售、用户、时间等多维数据进行聚合分析,可直观呈现业务趋势。
核心查询结构设计
SELECT 
    EXTRACT(YEAR FROM order_date) AS year,
    product_category,
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount,
    AVG(amount) AS avg_order_value
FROM sales 
WHERE order_date >= '2023-01-01'
GROUP BY CUBE(year, product_category)
ORDER BY year, product_category;
该查询使用CUBE生成年份与产品类别的全维度组合,涵盖总计、小计及明细层级,满足多维分析需求。
结果维度说明
字段含义
year订单年份
product_category产品类别
total_amount累计销售额
avg_order_value客单价

第五章:五道高频面试题全解析与逆袭指南

反转链表的递归与迭代实现

链表面试题中,“反转单链表”出现频率极高。掌握两种实现方式是关键:

// 迭代法
func reverseList(head *ListNode) *ListNode {
    var prev *ListNode
    curr := head
    for curr != nil {
        next := curr.Next
        curr.Next = prev
        prev = curr
        curr = next
    }
    return prev
}
如何检测系统中的环形链表
  • 使用快慢指针(Floyd算法),快指针每次走两步,慢指针走一步
  • 若存在环,二者终将相遇
  • 相遇后重置一个指针至头节点,再次相遇点即为环入口
动态规划解最长递增子序列

经典DP问题,状态转移方程为:dp[i] = max(dp[i], dp[j] + 1),其中 j < i 且 nums[j] < nums[i]

输入数组DP数组演变结果长度
[10,9,2,5,3,7][1,1,1,2,2,3]3
MySQL索引失效场景分析
  1. 使用函数或表达式操作索引列,如 WHERE YEAR(create_time) = 2023
  2. 违反最左前缀原则,在联合索引中跳过前置字段
  3. 使用 OR 连接非索引字段
  4. 隐式类型转换导致索引无法命中
手写Promise.all实现
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Expected an array'));
    }
    const results = [];
    let completed = 0;
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(value => {
        results[i] = value;
        if (++completed === promises.length) {
          resolve(results);
        }
      }).catch(reject);
    }
  });
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值