SQL128 统计2021年未完成试卷作答数大于1的有效用户
题目理解
SQL128 未完成试卷数大于1的有效用户
我们需要统计2021年每个未完成试卷作答数大于1的有效用户的数据。有效用户的定义是:
- 完成(提交了,有分数)试卷作答数至少为1
- 未完成(未提交,没有分数)试卷作答数小于5
输出结果需要包含:
- 用户ID(uid)
- 未完成试卷作答数(incomplete_cnt)
- 完成试卷作答数(complete_cnt)
- 作答过的试卷tag集合(detail),格式为
{日期:tag}
,用分号连接
解题思路
- 筛选2021年的数据:只考虑2021年的作答记录
- 关联试卷信息:通过exam_id关联exam_record和examination_info表
- 区分完成和未完成:根据submit_time是否为NULL判断是否完成
- 计算统计指标:
- 未完成数:submit_time为NULL的记录数
- 完成数:submit_time不为NULL的记录数
- 过滤有效用户:
- 未完成数 > 1
- 未完成数 < 5
- 完成数 ≥ 1
- 格式化输出:将日期和tag组合成要求的格式
SQL解析
WITH user_exam_records AS (SELECTuid,exam_id,tag,DATE(start_time) AS start_time,DATE(submit_time) AS submit_timeFROMexam_recordJOIN examination_info USING (exam_id)WHEREstart_time >= '2021-01-01'AND start_time <= '2021-12-31'
)
SELECTuid,SUM(IF(submit_time IS NULL, 1, 0)) AS incomplete_cnt,SUM(IF(submit_time IS NULL, 0, 1)) AS complete_cnt,GROUP_CONCAT(DISTINCT CONCAT_WS(':', start_time, tag) SEPARATOR ';') AS detail
FROMuser_exam_records
GROUP BYuid
HAVINGincomplete_cnt > 1AND incomplete_cnt < 5AND complete_cnt >= 1
ORDER BYincomplete_cnt DESC
关键点说明
-
临时表user_exam_records:
- 筛选2021年的数据
- 关联试卷信息表获取tag
- 提取start_time和submit_time的日期部分
-
统计计算:
SUM(IF(submit_time IS NULL, 1, 0))
计算未完成数SUM(IF(submit_time IS NULL, 0, 1))
计算完成数
-
detail格式化:
- 使用
CONCAT_WS
将日期和tag用冒号连接 - 使用
GROUP_CONCAT
将所有记录用分号连接 DISTINCT
确保不重复
- 使用
-
HAVING条件:
incomplete_cnt > 1
:未完成数大于1incomplete_cnt < 5
:未完成数小于5complete_cnt >= 1
:至少完成1份试卷
知识点扩展
1. CONCAT_WS函数
基本概念
CONCAT_WS是"Concatenate With Separator"的缩写,是SQL中用于连接字符串的函数,与普通CONCAT函数不同,它可以在各个字符串之间自动添加指定的分隔符。
语法
CONCAT_WS(separator, string1, string2, ..., stringN)
参数说明
separator
:必需,用作分隔符的字符串string1, string2, ..., stringN
:要连接的字符串,可以有多个
特点
- 自动添加分隔符:只在字符串之间添加,不会在开头或结尾添加
- 跳过NULL值:如果某个字符串为NULL,会自动跳过,不会在结果中留下多余的分隔符
- 分隔符可以是任意字符串:不仅限于单个字符
示例
SELECT CONCAT_WS('-', '2023', '01', '15'); -- 结果: '2023-01-15'
SELECT CONCAT_WS(':', '姓名', NULL, '张三'); -- 结果: '姓名:张三'
SELECT CONCAT_WS(', ', '苹果', '香蕉', '橙子'); -- 结果: '苹果, 香蕉, 橙子'
与CONCAT的区别
SELECT CONCAT('2023', '-', '01', '-', '15'); -- 需要手动添加分隔符
SELECT CONCAT('姓名', ':', NULL, ':', '张三'); -- 结果: NULL(遇到NULL会返回NULL)
2. GROUP_CONCAT函数
基本概念
GROUP_CONCAT是MySQL中用于将分组后的多行数据合并为一个字符串的聚合函数,常用于需要将分组结果展示为逗号分隔列表的场景。
语法
GROUP_CONCAT([DISTINCT] expr [, expr ...][ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [, col_name ...]][SEPARATOR str_val]
)
参数说明
DISTINCT
:可选,去除重复值expr
:要连接的表达式或列名ORDER BY
:可选,指定连接结果的排序方式SEPARATOR
:可选,指定分隔符,默认为逗号(,)
特点
- 分组连接:通常与GROUP BY子句一起使用
- 灵活排序:可以指定连接结果的排序方式
- 自定义分隔符:可以设置任意分隔符
- 长度限制:受group_concat_max_len系统变量限制,默认1024字节
示例
-- 基本用法
SELECT dept_id, GROUP_CONCAT(employee_name)
FROM employees
GROUP BY dept_id;-- 使用DISTINCT和自定义分隔符
SELECT dept_id, GROUP_CONCAT(DISTINCT employee_name SEPARATOR '|')
FROM employees
GROUP BY dept_id;-- 带排序
SELECT product_id, GROUP_CONCAT(customer_name ORDER BY purchase_date DESC SEPARATOR '; ')
FROM purchases
GROUP BY product_id;-- 多列连接
SELECT order_id, GROUP_CONCAT(CONCAT_WS(':', product_name, quantity))
FROM order_items
GROUP BY order_id;
注意事项
- 如果结果可能很长,需要调整group_concat_max_len的值:
SET SESSION group_concat_max_len = 1000000;
- 在Oracle中类似功能是LISTAGG函数,SQL Server中是STRING_AGG函数
3. 实际应用场景
-
标签系统:将用户的多个标签合并为一个字段
SELECT user_id, GROUP_CONCAT(tag_name SEPARATOR ', ') FROM user_tags GROUP BY user_id;
-
订单商品列表:显示订单中包含的所有商品
SELECT order_id, GROUP_CONCAT(product_name ORDER BY product_id SEPARATOR '\n') FROM order_details GROUP BY order_id;
-
日期和事件合并显示:
SELECT DATE(event_time) AS event_date,GROUP_CONCAT(event_description SEPARATOR '; ') FROM events GROUP BY DATE(event_time);