SQL快速入门40题-牛客
1.查询所有列
题目描述:现在运营想要查看用户信息表中所有的数据,请你取出相应结果。
星号*的使用
1 | select * |
2.查询多列
题目描述:现在运营想要用户的设备id对应的性别、年龄和学校的数据,请你取出相应数据。
1 | select |
3.结果去重
题目描述:现在运营想要查看用户来自于哪些学校,请从用户信息表中取出学校的去重数据。
写法一(distinct去重):
1 | select |
写法二(group by去重):
1 | select |
4.查询结果限制返回行数
题目描述:现在运营只需要查看前2个用户明细设备ID数据,请你从用户信息表user_profile中取出相应结果。
关键字limit
1 | select |
5.将查询后的列重新命名
题目描述:需要查看前2个用户明细设备ID数据,并将列user_infos_example’,请你从用户信息表取出相应结果。
关键字as
1 | select |
6.查询学校是北大的学生信息
关键字where
1 | select |
7.查找年龄大于24岁的用户信息
age:int可用> < =符号
1 | select |
8.查找某个年龄段的用户信息
题目描述:现在运营想要针对20岁及以上且23岁及以下的用户开展分析,请你取出满足条件的设备ID、性别、年龄。
1 | select |
9.查找去除复旦大学的用户信息
题目描述:现在运营想要查看除复旦大学以外的所有用户明细,请你取出相应数据。
- university:
varchar,字符串要用单引号''包裹起来 - 不等于:
!=
1 | select |
10.用where过滤空值练习
题目描述:想要对用户的年龄分布开展分析,在分析时想要剔除没有获取到年龄的用户,请你取出所有年龄值不为空的用户的设备ID、性别、年龄、学校的信息。
- 取字段的非空值使用:is not null
1 | select |
11.操作符练习(1)
题目描述:现在运营想要找到男性且GPA在3.5以上(不包括3.5)的用户进行调研,请你取出相应数据。
- gender:
varchar - gpa:
float
1 | select |
12.操作符练习(2)
题目描述:现在运营想要找到学校为北大或GPA在3.7以上(不包括3.7)的用户进行调研,请你取出相应数据(使用OR实现)。
- university:
varchar - gpa:
float
1 | select |
13.Where in
题目描述:现在运营想要找到学校为北大、复旦和山大的用户进行调研,请你取出相应数据。
写法一:
1 | select |
写法二(通常使用这个写法,写法一只是便于理解):
1 | select |
14.操作符混合运用
题目描述:找到GPA在3.5以上(不包括3.5)的山东大学用户 或 GPA在3.8以上(不包括3.8)的复旦大学用户进行调研,请你取出相应数据。
==优先级==:NOT > AND > OR,可以用括号()改变优先级
1 | select |
加不加括号都行:
1 | where |
15.查看学校名称中含北京的用户
题目描述:现在运营想要查看所有大学中带有北京的用户的信息,请你取出相应数据。
- 关键字
like - 占位符(通配符)
%
1 | select |
16.查找GPA最高值
题目描述:现在运营想要知道复旦大学学生GPA最高值是多少,请你取出相应数据。
执行顺序是先where再from(准确的说这里是max函数的使用)
1 | select |
- excel里也是先筛选再聚合??为什么捏?
- 好处是:先筛选之后数据量就变少了,然后再计算(或者更复杂的操作),数据量越少,执行速度就快,大概率
17.计算男生人数以及他们的平均GPA
函数count()
可以是
count(字段):统计某个列中非 NULL 值的数量count(*):统计表中的总行数(包括NULL)count(distinct 字段id):统计id去重后的非NULL值数量
1 | select |
- 历史背景
COUNT(1)是早期数据库优化不佳时的”民间偏方”- 现代数据库优化器会自动将两者识别为等效操作
- 推荐使用
COUNT(*):标准、规范、易读
18.分组计算练习题
分组聚合
1 | select |
- 分组:严格指
group by子句(分组操作) - 分组后,核心聚合函数5个
count():计数sum():求和avg():平均值
19.分组过滤练习题
题目:现在运营想查看每个学校用户的平均发贴和回帖情况,寻找低活跃度学校进行重点运营,请取出平均发贴数低于5的学校或平均回帖数小于20的学校。
在excel里会怎么做:先求出平均发帖和回帖情况(用数据透视表做),然后再筛选符合条件的
- 关键字:
having- having是在聚合之后的数据里进行筛选
1 | select |
解法2:可以用子查询
不过having存在,就是让你可以少写一层子查询,仅此
1 | select |
20.分组排序练习
关键字:order by
默认升序排列asc;降序加desc,如 order by 2 desc
1 | select |
21.表连接
excel里XLOOKUP,只连接某个字段
关键字join,表连接
- 左连接:
left join,保证左表完整性(左表所有记录都保留),左表不动,右表记录根据匹配条件”拼”到左表上,右表找到匹配记录 → 左右表字段合并显示,右表没找到匹配记录 → 左表字段正常显示,右表字段显示为NULL- 左表所有行都在结果中
- 右表只有匹配上的行才会出现
- 右表无法匹配的行(右表有但左表没有)确实会被丢弃
- 匹配不上时右表字段值为NULL
- 右连接:同理
- 全连接:为空也保留,左右都保留
- 内连接:join,只要一边没连上全都丢掉
1)只连接指定列:
1 | select |
2)连接所有列:
1 | select |
先连接,后筛选(右表的university字段)
1 | select |
==22==.统计每个学校的答过题的用户的平均题数
题目:运营想要了解每个学校答过题的用户平均答题数量情况,请你取出数据。
说明:某学校用户平均答题数量计算方式为该学校用户答题总次数除以答过题的不同用户个数
- 用户信息表user_profile
- 答题情况明细表question_practice_detail
1 | select |
23.事实表vs维度表
(这题有详细解题步骤,因为开始变难了)
题目:运营想要计算一些参加了答题的不同学校、不同难度的用户平均答题量,请你写SQL取出相应数据
事实表、维度表、信息表?
- 事实表:带着业务的?
- 这里的用户信息表user_profile是一张维度表
bug?刚刚在牛客提交,明明是一样的答案提交错误,等了一会就可以了
1 | select |
怎么区分事实表和维度表
给你个字段,问两个问题:
1. 这个字段能进行数学运算吗?
- 能算 → 事实(如:销售额可以求和)
- 不能算 → 进入下一题
2. 这个字段用来分组/筛选吗?
- 用来分组 → 维度(如:按”省份”分组统计)
- 不用来分组 → 可能是维度属性
真实案例
订单数据:
- 订单金额、数量、折扣 → 事实表
- 用户姓名、商品名称、下单日期、城市 → 维度表
事实表和维度表在查询时为什么要区分
数据量差距 = 生死差距
- 事实表:1亿行(订单明细)
- 维度表:1万行(商品信息)
如果不区分,你可能把1亿行 × 1亿行做JOIN,系统直接卡死。
区分后,1亿行 JOIN 1万行,数据库可以轻松优化。
24统计每个用户的平均刷题数
24题与23题只有些许不同
题目:运营想要查看参加了答题的山东大学的用户在不同难度下的平均答题题目数,请取出相应数据
- 要在join的on就把“山东大学”的用户筛选出来→打了题∩山东大学
1 | select |
25.查找山东大学或者性别为男生的信息
题目:现在运营想要分别查看学校为山东大学或者性别为男性的用户的device_id、gender、age和gpa数据,请取出相应结果,结果不去重。根据示例,你的查询应返回以下结果(注意输出的顺序,先输出学校为山东大学再输出性别为男生的信息)
关键字:union,上下拼接(列的数量要匹配)
- union:会帮你去重
- union all:不去重
1 | select |
为什么使用
union all而不是where university = '山东大学' or gender = 'male'- 结果不去重
- 顺序,先山大再男性
union all的经典用法
可以合并不同表的”同类”数据,且支持字段计算转换1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17SELECT
order_id,
amount,
'正向订单' AS order_type,
create_time
FROM formal_orders
WHERE create_date BETWEEN '2024-01-01' AND '2024-01-07'
UNION ALL
SELECT
refund_id AS order_id,
-amount AS amount, -- 退款为负值
'退款订单' AS order_type,
refund_time AS create_time
FROM refund_orders
WHERE refund_date BETWEEN '2024-01-01' AND '2024-01-07';
26.计算25岁以上和以下的用户数量
题目:现在运营想要将用户划分为25岁以下和25岁及以上两个年龄段,分别查看这两个年龄段用户数量。本题注意:age为null 也记为 25岁以下
1 | select |
excel中,用if函数做辅助列
=IF(判断条件, 条件成立时的值, 条件不成立时的值) |
27.查看不同年龄段的用户明细
case when的使用
当条件有三个或以上的时候就不用if了,改用case when
1 | select |
28.day() left()
题目:现在运营想要计算出2021年8月每天用户练习题目的数量,请取出相应数据。
date:date
1 | select |
==SQL29== 计算用户的平均次日留存率
1 | with tmp as( |
- 步骤1:获取每个用户每天的刷题日期(去重)
步骤2:通过自连接,将每个用户每天的记录与第二天的记录连接起来。
步骤3:统计所有的记录数作为分母,统计第二天有记录的记录数作为分子,然后计算比例。 - 自连接说的是inner join吗
- 我们通常说自连接(self-join)是指同一个表自己和自己连接,可以使用INNER JOIN,也可以使用LEFT JOIN等
30.substring_index()
题目:现在运营举办了一场比赛,收到了一些参赛申请,表数据记录形式如下所示,现在运营想要统计每个性别的用户分别有多少参赛者,请取出相应结果
profile:varchar
1 | select |
substring_index()的使用- 用于根据分隔符提取子字符串
substring_index(str, delim, count)
参数说明:str:要处理的原始字符串delim:分隔符(可以是单个字符或多个字符)count:计数值- 正数:从左向右查找,返回第
count个分隔符之前的所有字符 - 负数:从右向左查找,返回第
abs(count)个分隔符之后的所有字符 - 零:返回空字符串
- 正数:从左向右查找,返回第
31.提取博客URL中的用户名
题目:对于申请参与比赛的用户,blog_url字段中url字符后的字符串为用户个人博客的用户名,现在运营想要把用户的个人博客用户字段提取出单独记录为一个新的字段,请取出所需数据。
1 | select |
32.截取出年龄
题目:现在运营举办了一场比赛,收到了一些参赛申请,表数据记录形式如下所示,现在运营想要统计每个年龄的用户分别有多少参赛者,请取出相应结果
profile 字段的格式是:身高,体重,年龄,性别
1 | select |
- 如果不是最前/最后一个字段的话,substring_index()就要写嵌套
substring_index(substring_index(profile, ',', 正数第几个), ',', -1) as 字段名 |
- 示例:对于
profile = '180cm,75kg,27,male':
1 | -- 提取第一个字段(身高): |
33.找出每个学校GPA最低的同学
这33题有很多的解法,主要还是关注对表格结构(结果和原表)的分析和理解
题目:现在运营想要找到每个学校gpa最低的同学来做调研,请你取出每个学校的最低gpa。输出结果按university升序排序
如果一个学校有多个最低值—>使用窗口函数
解法一:子查询
1 | select |
子查询的使用
- 记得加别名
解法二:join,性能更好
1 | select |
==SQL34== 统计复旦用户8月练题情况
题目: 现在运营想要了解复旦大学的每个用户在8月份练习的总题目数和回答正确的题目数情况,请取出相应明细数据,对于在8月份没有练习过的用户,答题数结果返回0
- 对象:复旦大学的每个用户(就像前面的题会有:答过题的用户)
1 | select |
- 日期在t2
- join时,在on子句就要过滤!
- 如果把日期条件放在
where子句中,left join会退化成inner join,无法显示未练习的用户
- question_cnt数做了多少题
- right_question_cnt把答对的题标记为1,把这些1相加
步骤1:筛选复旦大学用户
从用户表 user_profile 中过滤出 university 为“复旦大学”的用户。
1 | SELECT |
步骤2:关联答题表并限制时间范围
使用 左连接 将用户表与答题表 question_practice_detail 关联,并限定答题时间为8月份(date 在 2021-08-01 到 2021-08-31 之间)。
⚠️ 注意:时间条件必须放在 LEFT JOIN 的 ON 子句中,而不是 WHERE 子句,否则会错误过滤掉无答题记录的用户。
1 | SELECT |
步骤3:聚合统计答题数
通过 COUNT 统计总题数,使用 SUM(CASE ...) 统计正确题数。
🔑 关键点:
COUNT(q.question_id)会自动忽略NULL,因此无答题记录的用户结果为0。SUM(CASE WHEN q.result = 'right' THEN 1 ELSE 0 END)对正确结果计数,无记录时结果为0。
35.浙大不同难度题目的正确率
题目:现在运营想要了解浙江大学的用户在不同难度题目下答题的正确率情况,请取出相应数据,并按照准确率升序输出。
1 | select |
1. 致命错误:COUNT(IF(...))用法错误
count(if(t1.result='right', 1, 0)) -- 错误! |
IF()返回0时,COUNT()也会计入0,因为COUNT只忽略NULL,不忽略0- 这会导致分子 = 分母,正确率永远为1.0000
正确写法:
1 | -- 方法1:IF返回NULL |
2.子查询先筛选→再join关联
1 | join ( |
36.查找后排序
题目:现在运营想要取出用户信息表中的用户设备ID和用户年龄,请取出相应数据,并按照年龄升序排序。
1 | select |
37.查找后多列排序
题目:现在运营想要取出用户信息表中的device_id、年龄和gpa数据,并先按照gpa升序排序,再按照年龄升序排序输出,请取出相应数据。
1 | select |
38.查找后降序排列
题目:现在运营想要取出用户信息表中对应的数据,并先按照gpa降序排列、gpa相同的按照年龄降序排序输出,请取出相应数据。
1 | select |
39.21年8月份练题总数
题目: 现在运营想要了解2021年8月份所有练习过题目的总用户数和练习过题目的总次数,请取出相应结果
1 | select |
40.正则表达式regexp
题目:在一张contacts表中,存储了用户的联系信息。请查询出所有符合以下条件的电话号码,并按id升序输出所有字段:
- 电话号码必须是 10 位数字。
- 电话号码的第一位不能以 0 开头。
- 电话号码的格式可以是连续的 10 位数字,或以-分隔的格式(如123-456-7890)。
- 读题:
- – 10位数字且非0开头
- – 3-3-4格式电话号码
1 | select * |
regexp在MySQL中用于正则表达式匹配,正确的用法是:字段名 REGEXP ‘正则表达式’
where phone_number regexp '^([1-9][0-9]{9}|[1-9][0-9]{2}-[0-9]{3}-[0-9]{4})$' |
语法分解:
^- 字符串开始()- 分组|- 或逻辑(匹配左侧或右侧模式)不能有空格!!![1-9]- 非零数字[0-9]{9}- 恰好9个数字[0-9]{2}- 恰好2个数字[0-9]{3}- 恰好3个数字[0-9]{4}- 恰好4个数字$- 字符串结束
正则表达式中的空格是有特殊含义的——它们会被当作字面意义上的字符来匹配!
1 | -- ❌ 错误示例(管道符周围有空格) |
常用的
1 | count(*) -- 总行数 |
- 优先使用GROUP BY去重:比DISTINCT性能更好
