文章目录
1. 第一段错误代码
1.1 题目地址:SQL81 牛客的课程订单分析(五)
SELECT t.user_id,
MIN(t.date),
DATEADD(DAY, 1, MIN(t.date)), # MySQL不支持
# DATE_ADD(MIN(t.date), INTERVAL 1 DAY), # MySQL支持
t.cnt
FROM (SELECT *,
COUNT(*) OVER(PARTITION BY user_id) cnt
FROM order_info
WHERE date > '2025-10-15'
AND status = 'completed'
AND product_name IN ('C++', 'Java', 'Python')
) t
WHERE t.cnt >= 2
GROUP BY t.user_id
ORDER BY t.user_id ASC
1.2 报错
Execution Error
SQL_ERROR_INFO: 'FUNCTION DATEADD does not exist'
1.3 解释
后来查询后得知,MySQL中对应DATEADD()的用法是DATE_ADD(),比如求日期date后面的一天:
SQL:DATEADD(DAY, 1, date)
MySQL:DATE_ADD(date, DAY, 1)
1.4 解决
将相应部分改为 DATE_ADD(MIN(t.date), INTERVAL 1 DAY)即可。
问题是该题其实还有一个bug,虽然代码运行没有问题,但是不符合题目要求:
因为使用日期加一得到的结果并非一定是实际表格中该行数据的下一条数据!(这里实际上还是要使用排序窗口函数:ROW_NUMBER() OVER())
该题正确题解如下:
SELECT t.user_id,
MIN(t.date) first_buy_date,
# DATEADD(DAY, 1, MIN(t.date)), # MySQL不支持
# DATE_ADD(MIN(t.date), INTERVAL 1 DAY), # MySQL支持
MAX(t.date) second_buy_date,
t.cnt
FROM (SELECT *,
COUNT(*) OVER(PARTITION BY user_id) cnt,
ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY date) rk_num
FROM order_info
WHERE date > '2025-10-15'
AND status = 'completed'
AND product_name IN ('C++', 'Java', 'Python')
) t
WHERE t.cnt >= 2 AND t.rk_num <= 2
GROUP BY t.user_id
ORDER BY t.user_id ASC
1.5 反思
这题过程中还遇到很多其他小问题,在这里记录一下:
1、使用排序 RANK()窗口函数时,使别名 rank 时发生了报错,应该选择用 rk,或者给rank加引号(SQL的别名最好避开使用一些关键字)
2、关于窗口函数 OVER()的用法小结
OVER()函数的四种情况:
(1)聚合函数(以cout为例)
① COUNT() OVER():总工资
② COUNT() OVER (PARTITION BY group):每个组的总工资
③ COUNT() OVER(PARTITION BY group ORDER BY date):每个组的累积总工资
④ COUNT() OVER(ORDER BY date) :累积总工资
(2)排序
也是同理:PARTITION BY 用于分区,ORDER BY 用于累计,但是有一点区别:排序窗口函数OVER()中必须使用ORDER BY,否则报错!</font.
2. 第二段错误代码
2.1 题目地址:SQL82 牛客的课程订单分析(六)
# 写出一个sql语句查询在2025-10-15以后,
# 同一个用户下单2个以及2个以上状态为购买成功的
# C++课程或Java课程或Python课程的订单id,是否拼团以及客户端名字信息,
# 最后一列如果是非拼团订单,则显示对应客户端名字,
# 如果是拼团订单,则显示NULL,并且按照order_info的id升序排序
# order_info
# client
SELECT t.id,
t.is_group_buy,
IF(t.is_group_buy = 'Yes', NULL, t.name) AS client_name
FROM (SELECT *,
COUNT(*) OVER(PARTITION BY user_id) AS cnt
FROM order_info o
LEFT JOIN client c
ON o.client_id = c.id
WHERE date > '2025-10-15'
AND status = 'completed'
AND product_name IN ('C++', 'Java', 'Python')
) AS t
WHERE t.cnt >= 2
ORDER BY t.id
2.2 报错
Execution Error
SQL_ERROR_INFO: "Duplicate column name 'id'"
2.3 解释
报错原因是参与子查询中参与JOIN的两个表存在相同名称的字段,且并非连接字段,即下图的id

2.4 解决
表格中有相同字段对的情况下,在select 字段的时候需要在其前面指定表格名。比如 SELECT xxx改为SELECT a.xxx
正解如下:
# 写出一个sql语句查询在2025-10-15以后,
# 同一个用户下单2个以及2个以上状态为购买成功的
# C++课程或Java课程或Python课程的订单id,是否拼团以及客户端名字信息,
# 最后一列如果是非拼团订单,则显示对应客户端名字,
# 如果是拼团订单,则显示NULL,并且按照order_info的id升序排序
# order_info
# client
SELECT t.id,
t.is_group_buy,
IF(t.is_group_buy = 'Yes', NULL, t.name) AS client_name
FROM (SELECT o.*, c.name, # 这里并非全选使用 *,而是指定表格o,为了排除表格c中重复的字段id(也可以选择在外表使用JOIN)
COUNT(*) OVER(PARTITION BY user_id) AS cnt
FROM order_info o
LEFT JOIN client c # 这里不能使用 INNER JOIN
ON o.client_id = c.id
WHERE date > '2025-10-15'
AND status = 'completed'
AND product_name IN ('C++', 'Java', 'Python')
) AS t
WHERE t.cnt >= 2
ORDER BY t.id
2.5 反思
其实这里问题的根源是我将JOIN放在了FROM后面的子查询中,而子查询的SELECT中使用了全选*,这是一条新手经验:使用了JOIN,就要注意对重复字段进行指定表格,包括*
;
或者说,为了避免这种错误,最好将表格连接操作放在外表当中(而不是在子查询中使用JOIN操作)
第三段错误代码
题目地址:SQL79 牛客的课程订单分析(三)
# # 写出一个sql语句查询在2025-10-15以后,
# # 同一个用户下单2个以及2个以上
# # 状态为购买成功的C++课程或Java课程或Python课程的订单信息,
# # 并且按照order_info的id升序排序
SELECT *
FROM order_info
WHERE date > '2025-10-15'
AND (SELECT COUNT(*) OVER(PARTITION BY user_id ORDER BY product_name)
FROM order_info ) >= 2
AND status = 'completed'
AND product_name IN ('C++', 'Java', 'Python')
ORDER BY user_id ASC
报错
SQL_ERROR_INFO: 'Subquery returns more than 1 row'
解释
把窗口函数理解成为了一个数值,虽然窗口函数是在子查询中使用,但是其返回值本质上是”一列“数据,所以这里报错子查询返回超过了一行。
解决
将窗口函数所在的子查询放在FROM 语句之后,再使用外层 SELECT 去取值
(其实上述代码还有一个BUG,即窗口函数中OVER()函数的用法错误:若统计总数而不是累计总数,则不能在OVER()函数中加ORDER BY,这里不多做赘述)
使用窗口函数的正解如下:
# 方法二:窗口函数
SELECT t.id, t.user_id, t.product_name, t.status, t.client_id, t.date
FROM (
SELECT *,
COUNT(*) OVER(PARTITION BY user_id ORDER BY product_name) AS rank
FROM order_info
WHERE date > '2025-10-15'
AND status = 'completed'
AND product_name IN ('C++', 'Java', 'Python')
) AS t
WHERE t.rank >= 2
ORDER BY t.id ASC
反思
SQL的逻辑跟其他编程语言还是有些不一样,后续需要深入了解其”循环“的底层逻辑。
十分重要:一路上的经验小结
一、对SQL基本句型的本质理解
SELECT的本质目的是列过滤,用于选出目标字段;
WHERE的本质目的是行过滤,用于筛选出符合指定条件的所有行;
JOIN的本质目的是进行列合并 + 行过滤,其本质是循环,用于多表连接;
GROUP BY的本质目的是分组,也即分区+去重,且改变了表格本身;
HAVING的本质目的是在分区内进行行过滤
窗口函数的本质是类似开挂或者站在上帝视角,在不影响表格本身的情况下获取“影藏”信息,比如分区排序相关的信息
二、对SQL执行顺序的理解
三、其它汇总(一些常见问题)
SELECT相关
- SELECT 实际执行在 FROM、JOIN和 GROUP BY 之后,因此这些在SELECT前面的操作理论上都无法使用SELECT中的别名
GROUP BY
- 使用GROUP BY 之后,SELECT语句后面的字段要么是分组字段,要么是聚合字段,不能出现其它多余字段
HAVING
- HAVING 后可以直接跟聚合函数,这是SQL在WHERE之外增加HAVING语句的原因(参考链接:点击)
JOIN
- 使用 JOIN 的第一步就是判断是该使用INNER JOIN 还是 LEFT JOIN(最常用这两种)。
- JOIN 本质核心是循环(loop)。
LEFT JOIN:返回包括左表中的所有记录和右表中联结字段相等的记录。
是针对左表的每一行,循环匹配右表,如果on条件匹配到了,就添加到结果集中,相当于两重for循环,既完成了列合并,又完成了行过滤。注意,On后面的条件对Left的表没有作用,只对Right的表有过滤作用INNER JOIN:只返回两个表中联结字段相等的行。其本质应该可以理解为跟LEFT JOIN 一样,只不过LEFT 表这次也会进行筛选。
- JOIN 之后的表格t 可能存在重复字段的情况,这种情况在使用字段的时候必须先指定表格名,比如
SELECT t.*、SELECT t.id- 为了避免(使用
SELECT *)造成字段可能重复的情况,可以考虑优先将JOIN放在外表当中(而不是子查询中),或者时刻牢记这条标准:使用了JOIN就指定所有用到的字段
条件函数
- 只能用在 SELECT 之后,不可单独使用;如需在WHERE等语句使用条件函数的值,只能先将其放在一个子查询中,再进行SELECT取值
窗口函数
- 只能用在 SELECT 之后,不可单独使用;如需在WHERE等语句使用条件函数的值,只能先将其放在一个子查询中,再进行SELECT取值(注意:不是所有情况的窗口函数都要放在子查询,而是需要将从窗口函数放在
WHERE中使用的时候)- 无论那种变种,窗口函数返回的是”一列“数据,而不像聚合函数有可能是一个数值,因此这个子查询一般是位于 FROM 后面,将窗口函数用于子查询SELECT之后,就给表格新增了一列数据,想要使用则使用上层SELECT去选取(理解这一点至关重要)
- 排序窗口函数必须在OVER()中使用
ORDER BY
子查询
- 关联子查询的用法
- 子查询使用位置的灵活使用:① FROM后面(增加新的字段到表格中) ② WHERE中(单独限定某个或某些字段)——参考链接:点击
特别注意:
- 聚合函数、窗口函数等不能单独使用,必须放在 SELECT语句中,所以想要在WHERE等语句使用这两类函数的值进行行过滤,则必须将其放在一个子查询中先获取这些聚合值或者窗口函数的值
- 窗口函数的返回值是一列数据
- HAVING 后可以直接跟聚合函数
参考:
1954

被折叠的 条评论
为什么被折叠?



