LeetCode高频 SQL 50 题(基础版)—— 聚合函数

目录

聚合函数

620. 有趣的电影

1251. 平均售价

1075. 项目员工 I

1633. 各赛事的用户注册率

1211. 查询结果的质量和占比


聚合函数

620. 有趣的电影

表:cinema

+----------------+----------+
| Column Name    | Type     |
+----------------+----------+
| id             | int      |
| movie          | varchar  |
| description    | varchar  |
| rating         | float    |
+----------------+----------+
id 是该表的主键(具有唯一值的列)。
每行包含有关电影名称、类型和评级的信息。
评级为 [0,10] 范围内的小数点后 2 位浮点数。

编写解决方案,找出所有影片描述为 boring (不无聊) 的并且 id 为奇数 的影片。

返回结果按 rating 降序排列

结果格式如下示例。

示例 1:

输入:
+---------+-----------+--------------+-----------+
|   id    | movie     |  description |  rating   |
+---------+-----------+--------------+-----------+
|   1     | War       |   great 3D   |   8.9     |
|   2     | Science   |   fiction    |   8.5     |
|   3     | irish     |   boring     |   6.2     |
|   4     | Ice song  |   Fantacy    |   8.6     |
|   5     | House card|   Interesting|   9.1     |
+---------+-----------+--------------+-----------+
输出:
+---------+-----------+--------------+-----------+
|   id    | movie     |  description |  rating   |
+---------+-----------+--------------+-----------+
|   5     | House card|   Interesting|   9.1     |
|   1     | War       |   great 3D   |   8.9     |
+---------+-----------+--------------+-----------+
解释:
我们有三部电影,它们的 id 是奇数:1、3 和 5。id = 3 的电影是 boring 的,所以我们不把它包括在答案中。
select *
from cinema
where mod(id, 2) = 1 and description != 'boring'
order by rating DESC
;
  • mod(id, 2) = 1:使用MOD函数判断电影 ID 是否为奇数。MOD(id, 2)返回id除以 2 的余数,余数为 1 表示 ID 是奇数。该条件用于筛选出奇数 ID 的电影(通常对应单号座位或场次)。

  • description != 'boring':排除描述为'boring'(乏味)的电影,确保只返回描述不为乏味的记录。

  • ORDER BY rating DESC表示按电影评分(rating列)进行降序排列,评分最高的电影排在最前。

1251. 平均售价

表:Prices

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| start_date    | date    |
| end_date      | date    |
| price         | int     |
+---------------+---------+
(product_id,start_date,end_date) 是 prices 表的主键(具有唯一值的列的组合)。
prices 表的每一行表示的是某个产品在一段时期内的价格。
每个产品的对应时间段是不会重叠的,这也意味着同一个产品的价格时段不会出现交叉。

表:UnitsSold

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| purchase_date | date    |
| units         | int     |
+---------------+---------+
该表可能包含重复数据。
该表的每一行表示的是每种产品的出售日期,单位和产品 id。

编写解决方案以查找每种产品的平均售价。average_price 应该 四舍五入到小数点后两位。如果产品没有任何售出,则假设其平均售价为 0。

返回结果表 无顺序要求

结果格式如下例所示。

示例 1:

输入:
Prices table:
+------------+------------+------------+--------+
| product_id | start_date | end_date   | price  |
+------------+------------+------------+--------+
| 1          | 2019-02-17 | 2019-02-28 | 5      |
| 1          | 2019-03-01 | 2019-03-22 | 20     |
| 2          | 2019-02-01 | 2019-02-20 | 15     |
| 2          | 2019-02-21 | 2019-03-31 | 30     |
+------------+------------+------------+--------+
UnitsSold table:
+------------+---------------+-------+
| product_id | purchase_date | units |
+------------+---------------+-------+
| 1          | 2019-02-25    | 100   |
| 1          | 2019-03-01    | 15    |
| 2          | 2019-02-10    | 200   |
| 2          | 2019-03-22    | 30    |
+------------+---------------+-------+
输出:
+------------+---------------+
| product_id | average_price |
+------------+---------------+
| 1          | 6.96          |
| 2          | 16.96         |
+------------+---------------+
解释:
平均售价 = 产品总价 / 销售的产品数量。
产品 1 的平均售价 = ((100 * 5)+(15 * 20) )/ 115 = 6.96
产品 2 的平均售价 = ((200 * 15)+(30 * 30) )/ 230 = 16.96
SELECT
    product_id,
    IFNULL(Round(SUM(sales) / SUM(units), 2), 0) AS average_price
FROM (
    SELECT
        Prices.product_id AS product_id,
        Prices.price * UnitsSold.units AS sales,
        UnitsSold.units AS units
    FROM Prices 
    LEFT JOIN UnitsSold ON Prices.product_id = UnitsSold.product_id
    AND (UnitsSold.purchase_date BETWEEN Prices.start_date AND Prices.end_date)
) T
GROUP BY product_id
  • 方法一:JOIN

    • 思路:本题需要计算每个产品的平均售价,平均售价 = 销售总额 / 总数量,因此我们只需要计算除每个产品的销售总额和总数量即可。

    • 总数量可以直接使用 UnitsSold 计算得出,使用 GROUP BY 和 SUM 函数即可:

      SELECT product_id, SUM(units) FROM UnitsSold GROUP BY product_id
    • 因为每个产品不同时期的售价不同,因此在计算销售总额之前要先分别计算每个价格的销售总额。每个价格的销售总额对应时间内的价格∗对应时间内的数量。因为价格和时间在 Prices 表中,数量在 UnitsSold 表中,这两个表通过 product_id 关联,那么我们就可以使用 LEFT JOIN 将两个表连接,通过 WHERE 查询对应时间内每个产品的价格和数量,并计算出对应的销售总额。

      SELECT
          Prices.product_id AS product_id,
          Prices.price * UnitsSold.units AS sales,
          UnitsSold.units AS units
      FROM Prices 
      LEFT JOIN UnitsSold 
      ON 
      Prices.product_id = UnitsSold.product_id
      AND 
      (UnitsSold.purchase_date BETWEEN Prices.start_date AND Prices.end_date)
    • 计算出产品每个价格的销售总额后,同样的使用 SUM 函数计算出产品所有时间的销售总额,然后除以总数量并使用 ROUND 函数保留两位小数即可。

1075. 项目员工 I

项目表 Project

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| project_id  | int     |
| employee_id | int     |
+-------------+---------+
主键为 (project_id, employee_id)。
employee_id 是员工表 Employee 表的外键。
这张表的每一行表示 employee_id 的员工正在 project_id 的项目上工作。

员工表 Employee

+------------------+---------+
| Column Name      | Type    |
+------------------+---------+
| employee_id      | int     |
| name             | varchar |
| experience_years | int     |
+------------------+---------+
主键是 employee_id。数据保证 experience_years 非空。
这张表的每一行包含一个员工的信息。

请写一个 SQL 语句,查询每一个项目中员工的 平均 工作年限,精确到小数点后两位

任意 顺序返回结果表。

查询结果的格式如下:

示例 1:

输入:
Project 表:
+-------------+-------------+
| project_id  | employee_id |
+-------------+-------------+
| 1           | 1           |
| 1           | 2           |
| 1           | 3           |
| 2           | 1           |
| 2           | 4           |
+-------------+-------------+
​
Employee 表:
+-------------+--------+------------------+
| employee_id | name   | experience_years |
+-------------+--------+------------------+
| 1           | Khaled | 3                |
| 2           | Ali    | 2                |
| 3           | John   | 1                |
| 4           | Doe    | 2                |
+-------------+--------+------------------+
​
输出:
+-------------+---------------+
| project_id  | average_years |
+-------------+---------------+
| 1           | 2.00          |
| 2           | 2.50          |
+-------------+---------------+
解释:第一个项目中,员工的平均工作年限是 (3 + 2 + 1) / 3 = 2.00;第二个项目中,员工的平均工作年限是 (3 + 2) / 2 = 2.50
  1. 每一个项目中员工(GROUP BY分组)

  2. 平均工作年限(AVG函数)

  3. 精确到小数点后两位(ROUND函数)

SELECT
    project_id,
    ROUND(AVG(e.experience_years),2)  AS average_years
FROM
    Project as p 
LEFT JOIN
    Employee as e
ON
    p.employee_id = e.employee_id
GROUP BY
    p.project_id;

1633. 各赛事的用户注册率

用户表: Users

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| user_id     | int     |
| user_name   | varchar |
+-------------+---------+
user_id 是该表的主键(具有唯一值的列)。
该表中的每行包括用户 ID 和用户名。

注册表: Register

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| contest_id  | int     |
| user_id     | int     |
+-------------+---------+
(contest_id, user_id) 是该表的主键(具有唯一值的列的组合)。
该表中的每行包含用户的 ID 和他们注册的赛事。

编写解决方案统计出各赛事的用户注册百分率,保留两位小数。

返回的结果表按 percentage降序 排序,若相同则按 contest_id升序 排序。

返回结果如下示例所示。

示例 1:

输入:
Users 表:
+---------+-----------+
| user_id | user_name |
+---------+-----------+
| 6       | Alice     |
| 2       | Bob       |
| 7       | Alex      |
+---------+-----------+
​
Register 表:
+------------+---------+
| contest_id | user_id |
+------------+---------+
| 215        | 6       |
| 209        | 2       |
| 208        | 2       |
| 210        | 6       |
| 208        | 6       |
| 209        | 7       |
| 209        | 6       |
| 215        | 7       |
| 208        | 7       |
| 210        | 2       |
| 207        | 2       |
| 210        | 7       |
+------------+---------+
输出:
+------------+------------+
| contest_id | percentage |
+------------+------------+
| 208        | 100.0      |
| 209        | 100.0      |
| 210        | 100.0      |
| 215        | 66.67      |
| 207        | 33.33      |
+------------+------------+
解释:
所有用户都注册了 208、209 和 210 赛事,因此这些赛事的注册率为 100% ,我们按 contest_id 的降序排序加入结果表中。
Alice 和 Alex 注册了 215 赛事,注册率为 ((2/3) * 100) = 66.67%
Bob 注册了 207 赛事,注册率为 ((1/3) * 100) = 33.33%
  • 首先我们观察这两个表以及问题,可以发现:

    1. 不同赛事是需要进行分别统计的

    2. 两个表虽然有共同的字段,但是并不需要连接。

      • 因为统计一门赛事注册的user_id在Register表中

      • 统计user_id总数量在Users表中

      • 两者都是可以独立查询出来的

  • 那么梳理一下做题步骤:

    1. 不同赛事进行分组

    2. 分别统计一门赛事中注册了的user_id数量、user_id总数量

    3. 保留两位小数

    4. 结果按percentage降序排序,相同按contest_id的升序排序

# Write your MySQL query statement below
select contest_id , round(count(user_id) * 100/ (select count(*) from users), 2) percentage 
from Register
group by contest_id
order by percentage desc, contest_id

1211. 查询结果的质量和占比

Queries 表:

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| query_name  | varchar |
| result      | varchar |
| position    | int     |
| rating      | int     |
+-------------+---------+
此表可能有重复的行。
此表包含了一些从数据库中收集的查询信息。
“位置”(position)列的值为 1 到 500 。
“评分”(rating)列的值为 1 到 5 。评分小于 3 的查询被定义为质量很差的查询。

将查询结果的质量 quality 定义为:

各查询结果的评分与其位置之间比率的平均值。rating/position

将劣质查询百分比 poor_query_percentage 定义为:

评分小于 3 的查询结果占全部查询结果的百分比。

编写解决方案,找出每次的 query_namequalitypoor_query_percentage

qualitypoor_query_percentage 都应 四舍五入到小数点后两位

任意顺序 返回结果表。

结果格式如下所示:

示例 1:

输入:
Queries table:
+------------+-------------------+----------+--------+
| query_name | result            | position | rating |
+------------+-------------------+----------+--------+
| Dog        | Golden Retriever  | 1        | 5      |
| Dog        | German Shepherd   | 2        | 5      |
| Dog        | Mule              | 200      | 1      |
| Cat        | Shirazi           | 5        | 2      |
| Cat        | Siamese           | 3        | 3      |
| Cat        | Sphynx            | 7        | 4      |
+------------+-------------------+----------+--------+
输出:
+------------+---------+-----------------------+
| query_name | quality | poor_query_percentage |
+------------+---------+-----------------------+
| Dog        | 2.50    | 33.33                 |
| Cat        | 0.66    | 33.33                 |
+------------+---------+-----------------------+
解释:
Dog 查询结果的质量为 ((5 / 1) + (5 / 2) + (1 / 200)) / 3 = 2.50
Dog 查询结果的劣质查询百分比为 (1 / 3) * 100 = 33.33
​
Cat 查询结果的质量为 ((2 / 5) + (3 / 3) + (4 / 7)) / 3 = 0.66
Cat 查询结果的劣质查询百分比为 (1 / 3) * 100 = 33.33

select
    query_name,
    round(avg(rating/position),2) quality,
    round(100 * avg(rating < 3),2) poor_query_percentage
from
    Queries
group by
    query_name;
  • avg(rating < 3)

    1. 针对每一条记录,判断 rating < 3 这个条件是否成立,成立则记为 1,不成立则记为 0

    2. 对所有记录的判断结果进行求和。

    3. 用求和的结果除以记录的总数,得到平均值,这个平均值就是低质量查询的比例。

 

<think>嗯,用户让我介绍一下SQL面试的常见问。好的,首先我得回想一下自己在准备SQL面试时碰到的问,或者网上常见的目类型。可能用户正在准备面试,需要系统性的整理。他们可能想了解不同难度的问,从基础到高级,或者是具体的例子,比如联结、聚合函数、窗口函数这些。 首先,我应该分门别类,把问分成几个大类,比如基础语法、联结操作、聚合与分组、子查询、窗口函数、优化等。然后每个类别下举几个典型的问,最好配上例子和解答,这样用户更容易理解。 用户可能还需要知道面试中常考的场景,比如排名、分数计算、处理重复数据等。比如,求每个部门的最高工资,或者删除重复记录这些。还有,关于索引和优化的问,面试官可能会问如何优化慢查询,或者索引的类型和选择。 需要注意用户提到的系统级指令,数学公式要用正确的格式,但这里可能用不上太多数学,不过如果有例子涉及到计算的话,可能需要用LaTeX。不过SQL可能更多是代码示例,所以可能需要用代码块。但用户没有特别提到代码格式,所以可能需要按常规方式处理。 然后,用户需要结构清晰的回答,逐步解决问。所以应该分点列出各个主,每个主下给出问、示例和解答。比如在联结操作部分,可以讲内联结、左联结的区别,并给出例子。在聚合函数部分,GROUP BY和HAVING的使用场景,如何处理NULL值等。 还有,用户可能对事务和ACID属性有疑问,所以需要包括事务相关的问,比如隔离级别,脏读、幻读这些概念。此外,关于DDL、DML的区别,也是基础的一部分。 最后,优化和索引部分,可能需要解释执行计划,如何分析查询效率,常见的优化手段,比如避免SELECT *,使用索引等。 需要检查有没有遗漏的重要主,比如窗口函数中的RANK、ROW_NUMBER的区别,或者CASE WHEN的应用场景。还有,如何处理NULL值在聚合函数中的影响,比如COUNT(*)和COUNT(列名)的区别。 总之,结构应该清晰,涵盖主要考点,每个问有示例和解答,帮助用户逐步理解。可能需要避免过于深入某个点,保持整体平衡,覆盖高频。</think>以下是SQL面试常见问的分类整理及解析,帮助您系统化准备: --- ### 一、基础语法与查询 1. **SELECT与过滤** - 问:如何查询表中某列满足条件的记录? - 示例: ```sql SELECT name, age FROM users WHERE age > 18 AND city = '北京'; ``` - 考点:`WHERE`条件组合、运算符优先级。 2. **排序与去重** - 问:如何按工资降序排列并去重? - 示例: ```sql SELECT DISTINCT salary FROM employees ORDER BY salary DESC; ``` - 考点:`DISTINCT`、`ORDER BY`。 --- ### 二、联结操作(JOIN) 1. **内联结 vs 左联结** - 问:查询所有员工及其部门名称(包括无部门员工) - 示例: ```sql SELECT e.name, d.dept_name FROM employees e LEFT JOIN departments d ON e.dept_id = d.id; ``` - 考点:`LEFT JOIN`保留左表全部记录。 2. **多表联结** - 问:查询订单详情(客户名、产品名、数量) - 示例: ```sql SELECT c.name, p.product_name, o.quantity FROM orders o JOIN customers c ON o.customer_id = c.id JOIN products p ON o.product_id = p.id; ``` - 考点:多表关联逻辑。 --- ### 三、聚合与分组 1. **GROUP BY与HAVING** - 问:统计每个部门工资超过1万元的员工数量 - 示例: ```sql SELECT dept_id, COUNT(*) FROM employees WHERE salary > 10000 GROUP BY dept_id HAVING COUNT(*) > 5; ``` - 考点:`WHERE`过滤行,`HAVING`过滤分组。 2. **聚合函数处理NULL** - 问:`COUNT(*)`与`COUNT(column)`的区别? - 答案:`COUNT(*)`统计所有行,`COUNT(column)`忽略NULL值。 --- ### 四、子查询与复杂查询 1. **相关子查询** - 问:查询工资高于部门平均工资的员工 - 示例: ```sql SELECT name, salary FROM employees e1 WHERE salary > (SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id); ``` - 考点:子查询与主查询的关联。 2. **EXISTS vs IN** - 问:查询有订单的客户 - 示例: ```sql SELECT name FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id); ``` - 考点:`EXISTS`效率通常优于`IN`。 --- ### 五、窗口函数 1. **排名问** - 问:按部门分组计算工资排名 - 示例: ```sql SELECT name, dept_id, salary, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rank FROM employees; ``` - 考点:`RANK()`、`ROW_NUMBER()`、`DENSE_RANK()`的区别。 2. **滑动窗口计算** - 问:计算每个员工前3个月的累计工资 - 示例: ```sql SELECT name, month, salary, SUM(salary) OVER (PARTITION BY name ORDER BY month ROWS 2 PRECEDING) AS rolling_sum FROM salary_table; ``` --- ### 六、数据操作与事务 1. **删除重复数据** - 问:删除表中重复记录(保留ID最小的一条) - 示例: ```sql DELETE FROM users WHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY email); ``` 2. **事务ACID属性** - 问:解释事务的隔离级别(如脏读、幻读) - 答案:读未提交 → 读已提交 → 可重复读 → 串行化(隔离性逐级增强)。 --- ### 七、性能优化 1. **索引设计** - 问:何时使用联合索引? - 答案:遵循最左前缀原则,高频查询条件优先。 2. **EXPLAIN分析** - 问:如何优化慢查询? - 步骤:用`EXPLAIN`查看执行计划,关注`type`(扫描方式)、`key`(使用索引)等。 --- ### 高频示例 1. **TOP N问** ```sql -- 查询每个部门工资前三高的员工 SELECT * FROM ( SELECT name, dept_id, salary, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rank FROM employees ) t WHERE rank <= 3; ``` 2. **行列转换** ```sql -- 将行数据转换为列(如统计各季度销售额) SELECT product_id, SUM(CASE WHEN quarter = 'Q1' THEN amount END) AS Q1, SUM(CASE WHEN quarter = 'Q2' THEN amount END) AS Q2 FROM sales GROUP BY product_id; ``` --- 掌握以上核心问后,建议结合《SQL必知必会》等书籍练习,并熟悉LeetCode/SQLZoo等平台实战。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值