SQL笔试题总结

文章目录

  • 前言
  • 一、列转行
    • 题目:将表Student
    • 转化为下面的形式展示
    • 先放答案
    • 逐步剖析
  • 二、row_number() over() 的使用
    • 题目:统计订单交易表(orders)每个商品交易金额最高的那一条数据
    • 先放答案
    • 逐步剖析
  • 三、逐行累加
    • 题目:还是订单交易表(orders),对商品按照月份累加汇总。比如,一月份显示一月的交易金额,二月份显示一月份+二月份的交易金额
    • 先放答案
    • 逐步剖析

  

  

  

  

前言

  

本篇文章将总结博主在笔试或面试过程中碰到的比较有难度或经典的sql题目。笔试考察的大都不是很难,不像工作中那样好几个嵌套子查询,很多个表关联…可能行数很少,但绝对得多想。如果之前没碰见过,第一次做肯定会漏洞百出。会持续更新的~

  
  
  

一、列转行

  

题目:将表Student

name subject score
张三 语文 78.0
张三 数学 88.0
张三 英语 98.0
李四 语文 89.0
李四 数学 76.0
李四 英语 90.0
王五 语文 66.0

转化为下面的形式展示

name 语文 数学 英语 总分
张三 78 88 98 264
李四 89 76 90 255
王五 99 0 0 66

  

先放答案

SELECT `name`,语文,数学,英语,总分
FROM(
	SELECT
	`name`,
	SUM(IF(SUBJECT = '语文',score,0)) AS 语文,
	SUM(IF(SUBJECT = '数学',score,0)) AS 数学,
	SUM(IF(SUBJECT = '英语',score,0)) AS 英语
	FROM student
	GROUP BY `name`)t1
LEFT JOIN(
	SELECT `name`,SUM(score) AS 总分
	FROM student
	GROUP BY `name`)t2
USING(`name`);

  

逐步剖析

1、先看总分,计算很简单,就是按名字group by 之后 sum(分数) 即可

SELECT `name`,SUM(score) AS 总分
FROM student
GROUP BY `name`
name 总分
张三 264
李四 255
王五 66

得出这样的结果
  
2、本题精髓就是怎么把 subject 这一列数据转化成一行列名。可以拿 subject 列中的数据做 if 判断的条件,创建一张只有 name列 不变,后几列是 语数英 的新表

SELECT
`name`,
IF(SUBJECT = '语文',score,0) AS 语文,
IF(SUBJECT = '数学',score,0) AS 数学,
IF(SUBJECT = '英语',score,0) AS 英语
FROM student
name 语文 数学 英语
张三 78.0 0 0
张三 0 88.0 0
张三 0 0 98.0
李四 89.0 0 0
李四 0 76.0 0
李四 0 0 90.0
王五 66.0 0 0

  
3、离最终答案不远了,用 group by + sum 就能变成没人一行且语数英三列数据合并

SELECT
`name`,
SUM(IF(SUBJECT = '语文',score,0)) AS 语文,
SUM(IF(SUBJECT = '数学',score,0)) AS 数学,
SUM(IF(SUBJECT = '英语',score,0)) AS 英语
FROM student
GROUP BY `name`
name 语文 数学 英语
张三 78 88 98
李四 89 76 90
王五 99 0 0

这条sql就是本题精髓!!! 怕直接看看不懂,所以拆开分两步写了。

  
4、最后一步简单,把各科表和总分表关联起来就行,刚才那俩分离的sql写一起

SELECT `name`,语文,数学,英语,总分
FROM(
	SELECT
	`name`,
	SUM(IF(SUBJECT = '语文',score,0)) AS 语文,
	SUM(IF(SUBJECT = '数学',score,0)) AS 数学,
	SUM(IF(SUBJECT = '英语',score,0)) AS 英语
	FROM student
	GROUP BY `name`)t1
LEFT JOIN(
	SELECT `name`,SUM(score) AS 总分
	FROM student
	GROUP BY `name`)t2
USING(`name`);

  
  
  

二、row_number() over() 的使用

  

row_number() over() 在面试中经常和 rank() over()、dense_rank() over() 一起被问,在笔试中也经常需要用到,甚至在工作中也经常见到,反正是相当的重要!
在工作中其实用的是 row_number() over() 的变种,用 @ 。因为公司大都是MySQL5,但窗口函数在MySQL8中才出现,所以工作中都是 row_number 的思想,@的写法。关于@的使用有时间我写写。
  

题目:统计订单交易表(orders)每个商品交易金额最高的那一条数据

item_id trade_date trade_amount
A 2021/3/1 1500
A 2021/3/9 2500
A 2021/2/22 1000
B 2021/3/5 3000
B 2021/5/9 5000
B 2021/6/22 800

item_id=商品代码  trade_date=交易日期  trade_amount=交易金额

  

先放答案

SELECT 
	item_id,
	trade_date,
	trade_amount
FROM(
	SELECT 
		item_id,
		trade_date,
		trade_amount,
		row_number() over(PARTITION BY item_id ORDER BY trade_amount DESC) AS sort
	FROM orders)t
WHERE sort = 1
item_id trade_date trade_amount
A 2021/3/9 2500
B 2021/5/9 5000

  

逐步剖析

1、因为要求每个商品的金额最高,所以分组排序必不可少。为什么不能用 group by 然后 max 呢?因为要求的是金额最高的那一条数据,而不是求最高金额,那一行数据的其他信息得保留下来。而group by 后的其他字段不用聚合函数是没法保存的,就丢失了,跟题意不符。

举个栗子吧:

SELECT item_id,MAX(trade_amount)
FROM orders
GROUP BY item_id

SQL笔试题总结_第1张图片
虽然能找到最高金额,但这样就缺字段了

还有这样:

SELECT item_id,trade_date,MAX(trade_amount)
FROM orders
GROUP BY item_id

这样写直接报错,因为 group by 了 item_id 这一列,那么它之外的列如果不用聚合函数直接报错

这样也是不对:

SELECT item_id,trade_date,MAX(trade_amount)
FROM orders
GROUP BY item_id,trade_date

虽然不报错,但是
SQL笔试题总结_第2张图片
只有 item_id 和 trade_date 都相同的才能分为一组,不符合题意

  
所以要用到 row_number() over() 进行分组排序:

SELECT 
	item_id,
	trade_date,
	trade_amount,
	row_number() over(PARTITION BY item_id ORDER BY trade_amount DESC) AS sort
FROM orders
item_id trade_date trade_amount sort
A 2021/3/9 2500 1
A 2021/3/1 1500 2
A 2021/2/22 1000 3
B 2021/5/9 5000 1
B 2021/3/5 3000 2
B 2021/6/22 800 3

  
2、最后子查询即可

SELECT 
	item_id,
	trade_date,
	trade_amount
FROM(
	SELECT 
		item_id,
		trade_date,
		trade_amount,
		row_number() over(PARTITION BY item_id ORDER BY trade_amount DESC) AS sort
	FROM orders)t
WHERE sort = 1

  
  
  

三、逐行累加

  

题目:还是订单交易表(orders),对商品按照月份累加汇总。比如,一月份显示一月的交易金额,二月份显示一月份+二月份的交易金额

展示结果应该为:

item_id trade_date trade_amount
A 2021/2 1000
A 2021/3 5000
B 2021/3 3000
B 2021/5 8000
B 2021/6 8800

  

先放答案

SELECT
	t1.item_id AS item_id,
	t1.month AS trade_date,
	SUM(t2.trade_amount) AS trade_amount
FROM(
	SELECT
		item_id,
		DATE_FORMAT(trade_date,'%Y-%m') AS `month`,
		SUM(trade_amount) trade_amount 
	FROM orders 
	GROUP BY item_id,DATE_FORMAT(trade_date,'%Y-%m')
	ORDER BY item_id,`month`)t1
LEFT JOIN(
	SELECT
		item_id,
		DATE_FORMAT(trade_date,'%Y-%m') AS `month`,
		SUM(trade_amount) trade_amount 
	FROM orders 
	GROUP BY item_id,DATE_FORMAT(trade_date,'%Y-%m')
	ORDER BY item_id,`month`)t2
ON t1.item_id = t2.item_id
WHERE t2.month <= t1.month
GROUP BY t1.item_id,t1.month

  

逐步剖析

1、题目说:按照月份累加汇总。怎么按照月份呢,按月份分组,把相同月份的金额加起来

SELECT
	item_id,
	DATE_FORMAT(trade_date,'%Y-%m') AS `month`,
	SUM(trade_amount) trade_amount 
FROM orders 
GROUP BY item_id,DATE_FORMAT(trade_date,'%Y-%m')
ORDER BY item_id,`month`
item_id trade_date trade_amount
A 2021/2 1000
A 2021/3 4000
B 2021/3 3000
B 2021/5 5000
B 2021/6 800

因为要求不同商品不同月份汇总,所以 group by 后要用两个字段分组。

  
2、到这里已经跟结果很接近了,只要能按照 item_id(商品代码)分组然后累加即可,我首先想到了sum() over() 窗口函数

SELECT 
	item_id,
	`month`,
	SUM(trade_amount) over(PARTITION BY item_id) sums
FROM (
	SELECT
		item_id,
		DATE_FORMAT(trade_date,'%Y-%m') AS `month`,
		SUM(trade_amount) trade_amount 
	FROM orders 
	GROUP BY item_id,DATE_FORMAT(trade_date,'%Y-%m')
	ORDER BY item_id,`month`)t
GROUP BY item_id,`month`

SQL笔试题总结_第3张图片
但是 sum 窗口函数只能把分组内的总和计算出来填到每一列,没法做到逐行累加。到这我已经没有思路了,我就去求助了一下经理@红糖番薯,才知道可以用自连接。

我先自连接试试:

SELECT
*
FROM(
	SELECT
		item_id,
		DATE_FORMAT(trade_date,'%Y-%m') AS `month`,
		SUM(trade_amount) trade_amount 
	FROM orders 
	GROUP BY item_id,DATE_FORMAT(trade_date,'%Y-%m')
	ORDER BY item_id,`month`)t1
LEFT JOIN(
	SELECT
		item_id,
		DATE_FORMAT(trade_date,'%Y-%m') AS `month`,
		SUM(trade_amount) trade_amount 
	FROM orders 
	GROUP BY item_id,DATE_FORMAT(trade_date,'%Y-%m')
	ORDER BY item_id,`month`)t2
ON t1.item_id = t2.item_id

SQL笔试题总结_第4张图片
看似杂乱无章,其实有点东西

题目要求:一月份显示一月的金额,二月份显示一月份+二月份的金额

那么上面自连接的 sql 在最后也要加一个筛选条件

WHERE t2.month <= t1.month

这样使得表右半段的数据,即 t2 的数据不会超出 t1 指定的月份范围,不加时可能 t1 是第一月,而表的后半段 t2 是第二月这个情况。
加上后,表前半段 t1 的数据是某月,而后半段 t2 就是 <= t1 指定的月了,那么把 t2 月份的金额相加就是累积金额了。
怎么分组呢?按表前半段 t1 的月份分组,每一组中的表后半段 t2 就是 <= t1 的所有月份,金额和就是累积值。

SELECT
	t1.item_id AS item_id,
	t1.month AS trade_date,
	SUM(t2.trade_amount) AS trade_amount
FROM(
	SELECT
		item_id,
		DATE_FORMAT(trade_date,'%Y-%m') AS `month`,
		SUM(trade_amount) trade_amount 
	FROM orders 
	GROUP BY item_id,DATE_FORMAT(trade_date,'%Y-%m')
	ORDER BY item_id,`month`)t1
LEFT JOIN(
	SELECT
		item_id,
		DATE_FORMAT(trade_date,'%Y-%m') AS `month`,
		SUM(trade_amount) trade_amount 
	FROM orders 
	GROUP BY item_id,DATE_FORMAT(trade_date,'%Y-%m')
	ORDER BY item_id,`month`)t2
ON t1.item_id = t2.item_id
WHERE t2.month <= t1.month
GROUP BY t1.item_id,t1.month

精髓就是 group by t1 的两列,但 sum t2 的金额

你可能感兴趣的:(sql,面试,数据库)