1 推荐工具
在线运行SQL
2 阿里一面
3 百度一面
4 一道百度校招二面sql题
力推一个在线运行sql的网站:https://www.json.cn/runcode/run_sql/
有一张学生表stu,包含sid, std_name,course_name,score等几个字段,数据如下,要求查询出每门课程排名前三名的学生记录,
/*sid, std_name,course_name,score1 小明 语文 902 小明 语文 923 小明 语文 934 小明 语文 945 小泓 数学 926 小泓 英文 937 小刚 科学 94语文 94-93-92数学*/CREATE TABLE stu (sid int,std_name varchar(255),course_name varchar(255),score int
);INSERT INTO stu VALUES (1, 'Tom', 'Chinese', 90);
INSERT INTO stu VALUES (2, 'Tom2', 'Chinese', 91);
INSERT INTO stu VALUES (3, 'Tom3', 'Chinese', 92);
INSERT INTO stu VALUES (4, 'Tom4', 'Chinese', 93);INSERT INTO stu VALUES (5, 'Tom5', 'English', 93);
INSERT INTO stu VALUES (6, 'Tom6', 'English', 94);INSERT INTO stu VALUES (7, 'Tom7', 'Math', 91);
4.1 方法一(我写的,面试官不是很认可):使用group_concat函数(缺点是只能限制拼接后的最长字符串,不能限制拼接的元素数量)
【SQL学习笔记一】Group_Concat()
// 先堆
// select course_name,group_concat(std_name,"-"),group_concat(score, "-") from (select * from stu order by score desc) as tmp group by course_name// 协会站点,入会当会员//表
/*学生表、部门表、学生-部门表、新闻表*/// (select * from stu order by course_name desc, score desc) as tmp
4.2 方法二:使用子查询
SELECT s1.sid, s1.std_name, s1.course_name, s1.score
FROM stu s1
WHERE (SELECT COUNT(DISTINCT s2.score) FROM stu s2 WHERE s2.course_name = s1.course_name AND s2.score > s1.score
) < 3
ORDER BY s1.course_name, s1.score DESC;
4.2.1 sql语句解析
当然可以。这是一种子查询的方法,它使用自连接来获取每个课程的前三名学生的成绩。以下是对这个查询的详细解释:
-
主查询:
主查询是对
stu
表的查询,它选取了学生的sid
、std_name
、course_name
和score
。SELECT s1.sid, s1.std_name, s1.course_name, s1.score FROM stu s1
-
子查询:
对于主查询中的每个学生(或记录),都会运行一次子查询。子查询的目的是计算当前学生的成绩在其课程中的排名。
SELECT COUNT(DISTINCT s2.score) FROM stu s2 WHERE s2.course_name = s1.course_name AND s2.score > s1.score
这里,我们正在数那些在同一课程中但成绩高于当前学生(
s1
)的其他学生(s2
)的数量。因此,如果子查询的结果为0,这意味着当前学生是这门课的第一名;如果结果为1,这意味着当前学生是第二名,以此类推。 -
WHERE条件:
WHERE (... ) < 3
该条件确保我们只选取排名在前三名的学生。只有当子查询的结果为0、1或2时(即该学生的成绩在其课程中排名前三),该学生的记录才会被选中。
-
ORDER BY子句:
ORDER BY s1.course_name, s1.score DESC;
这个子句首先按课程名称对结果进行排序,然后在每个课程内部,按学生的成绩降序排列。
简单来说,这个查询为每个学生计算他们在自己的课程中的成绩排名,然后选取排名在前三名的学生。
4.2.2 子查询的使用位置
子查询并不仅限于WHERE
子句中。它们可以出现在以下位置:
SELECT
子句中,作为派生列。FROM
子句中,作为派生表。WHERE
子句中,与操作符(如IN
,EXISTS
)一起使用。HAVING
子句中。JOIN
操作中。
4.2.3 子查询、EXISTS
和IN
的关系
我的笔记(Sql语句中IN和exists的区别及应用)
-
子查询与
IN
: 当你需要比较一个值是否在一组值中时,可以使用子查询与IN
结合。SELECT employee_id FROM employees WHERE department_id IN (SELECT department_id FROM departments WHERE location = 'New York');
-
子查询与
EXISTS
: 当你需要检查一个子集是否存在时,可以使用EXISTS
。它是一个半关联操作,通常与关联条件结合使用。SELECT customer_id FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);
EXISTS
通常更高效,因为它在找到第一个匹配项后就会停止搜索。
N 和 EXISTS 的区别及应用场景
-
区别:
-
如你所说,
IN
和EXISTS
关键字在执行查询时使用的策略是不同的。具体执行策略和数据库优化器的实现有关,但大致的概念是:IN
子查询首先被执行,然后将得到的结果集返回给主查询,之后主查询进行过滤。- 对于
EXISTS
子查询,通常外层的每一行数据都会检查子查询是否存在满足条件的记录。
-
-
应用场景:
IN
更适合子查询返回的结果集小,而主查询的数据集大并且具有适当的索引的场景。EXISTS
在外部查询的数据集较小而子查询的数据集较大并且具有适当索引的情况下表现得更好。
-
关于效率:
- 这两者之间没有固定的“哪个总是更快”的规则,它取决于数据的特性和查询的具体情境。通常,最佳的做法是编写两种查询并检查执行计划或实际执行时间。
4.3 方法三:使用窗口函数:
如上文中的ROW_NUMBER()
方法(能够保证强排名,如果两个人并列第三,只返回其中number号靠前的那一个)。除此之外,你还可以使用RANK()
【并列的前三名都会返回】或DENSE_RANK()
。
WITH RankedScores AS (SELECT sid,std_name,course_name,score,ROW_NUMBER() OVER(PARTITION BY course_name ORDER BY score DESC) AS rankingFROM stu
)
SELECT sid,std_name,course_name,score
FROM RankedScores
WHERE ranking <= 3;
4.3.1 什么是窗口函数
窗口函数是SQL的高级功能,允许在结果集的一组行上执行计算,这组行与当前行有某种关系。换句话说,窗口函数为当前行定义了一个"窗口",并根据这个窗口计算值。这些函数与普通的聚合函数(如SUM、AVG)不同,因为窗口函数不会减少结果集的行数;它们为每一行提供计算结果。
窗口函数通常与OVER
子句一起使用,OVER
子句定义了为每一行计算函数值的窗口。
4.3.2 解释 WITH RankedScores AS ...
这是一个常用的SQL特性,叫做"公共表表达式" (Common Table Expression, 简称CTE)。CTE为查询提供了一种定义临时结果集的方式,这个结果集只存在查询的执行期间,并且它可以在之后的查询中引用。
在给定的查询中,CTE RankedScores
为stu
表中的每个学生按照course_name
进行分区,并为每个学生计算一个名次ranking
,名次基于他们的score
从高到低排序。
解释CTE中的查询:
RANK()
: 是一个窗口函数,为每一行提供一个名次。OVER(PARTITION BY course_name ORDER BY score DESC)
: 定义了一个窗口。这里,PARTITION BY course_name
意味着为每门课程单独计算名次。ORDER BY score DESC
意味着名次是基于分数从高到低排序的。AS ranking
: 将窗口函数的结果命名为ranking
。
所以,这个CTE为每位学生提供了一个名次,基于他们在特定课程中的分数。之后的主查询从这个CTE中选择名次在前三的学生。
4.3.3 常见的窗口函数
ROW_NUMBER()
: 对每个窗口中的行进行编号。RANK()
: 在窗口内为行分配一个唯一的排名,排名并列的情况下会留下空缺。DENSE_RANK()
: 与RANK()
相似,但不会在排名中留下空缺。NTILE(n)
: 将窗口内的行分为n个大致相等的部分,并为每部分的行提供一个编号。LAG(column, n)
: 返回当前行前n行的值。LEAD(column, n)
: 返回当前行后n行的值。FIRST_VALUE(column)
: 返回窗口内的第一个值。LAST_VALUE(column)
: 返回窗口内的最后一个值。SUM(column)
,AVG(column)
,MIN(column)
,MAX(column)
: 在窗口内计算总和、平均值、最小值和最大值。
4.3.4 举一个例子告诉rank(), dense_rank()和row_number()等窗口函数的区别
1 rank()
WITH RankedScores AS (SELECT sid,std_name,course_name,score,RANK() OVER(PARTITION BY course_name ORDER BY score DESC) AS rankingFROM stu
)
SELECT sid,std_name,course_name,score,ranking
FROM RankedScores
WHERE ranking <= 3;
查询结果:
3|Tom3|Chinese|93|1
4|Tom4|Chinese|93|1
2|Tom2|Chinese|91|3
6|Tom6|English|94|1
5|Tom5|English|93|2
7|Tom7|Math|91|1
2 dense_rank()
WITH RankedScores AS (SELECT sid,std_name,course_name,score,DENSE_RANK() OVER(PARTITION BY course_name ORDER BY score DESC) AS rankingFROM stu
)
SELECT sid,std_name,course_name,score,ranking
FROM RankedScores
WHERE ranking <= 3;
查询结果:
3|Tom3|Chinese|93|1
4|Tom4|Chinese|93|1
2|Tom2|Chinese|91|2
1|Tom|Chinese|90|3
6|Tom6|English|94|1
5|Tom5|English|93|2
7|Tom7|Math|91|1
3 ROW_NUMBER()
WITH RankedScores AS (SELECT sid,std_name,course_name,score,ROW_NUMBER() OVER(PARTITION BY course_name ORDER BY score DESC) AS rankingFROM stu
)
SELECT sid,std_name,course_name,score,ranking
FROM RankedScores
WHERE ranking <= 3;
3|Tom3|Chinese|93|1
4|Tom4|Chinese|93|2
2|Tom2|Chinese|91|3
6|Tom6|English|94|1
5|Tom5|English|93|2
7|Tom7|Math|91|1
4.3.5 `SUM(column)`, `AVG(column)`, `MIN(column)`, `MAX(column)`这些函数是对窗口内所有的记录生效的吧 2 `LAG(column, n)`, `LEAD(column, n)`和`NTILE(n)`,分别给几个demo
-
是的,
SUM(column)
,AVG(column)
,MIN(column)
,MAX(column)
这些函数都是对窗口内的所有记录生效的,而具体的窗口范围由PARTITION BY
和ORDER BY
决定。 -
DEMO:
-
LAG(column, n): 这个函数用于返回当前行上方n行的值。如果没有这么多行,它将返回NULL。
SELECT column,LAG(column, 1) OVER (ORDER BY some_column) AS prev_value FROM table_name;
这将给出当前行和前一行的值。
-
LEAD(column, n): 与
LAG
类似,但返回当前行下方n行的值。SELECT column,LEAD(column, 1) OVER (ORDER BY some_column) AS next_value FROM table_name;
这将给出当前行和下一行的值。
-
NTILE(n): 这个函数用于将结果集分成n个大致相等的部分,并为每部分的行分配一个编号。
SELECTcolumn,NTILE(4) OVER (ORDER BY some_column) AS quartile FROM table_name;
这将为每行分配一个从1到4的值,表示它在哪个四分位数中。
-
请注意,这些DEMO只是基本用法的示例,实际应用可能会更复杂,取决于具体的数据和需求。
4.4 方法四:在某些数据库(如MySQL)中,可以使用用户定义的变量来计算每个课程的排名。
SET @ranking = 0;
SET @prev_course = '';
SELECT sid, std_name, course_name, score
FROM (SELECT sid, std_name, course_name, score,@ranking := IF(@prev_course = course_name, @ranking + 1, 1) AS ranking,@prev_course := course_nameFROM stuORDER BY course_name, score DESC
) AS RankedScores
WHERE ranking <= 3;
4.4.1 解释一下用法
这个查询是使用MySQL的用户定义变量来实现学生的成绩排名功能。它的目标是为每门课程中的学生按分数降序排名,并只选择排名前三的学生。
我来逐步解释这个查询:
1. 设置用户定义变量
SET @ranking = 0;
SET @prev_course = '';
这里,我们初始化两个用户定义变量:
@ranking
:用于存储当前学生的排名。@prev_course
:用于存储上一行的课程名,这样我们可以检查当前行的课程是否与上一行的课程相同。
2. 子查询
SELECT sid, std_name, course_name, score,@ranking := IF(@prev_course = course_name, @ranking + 1, 1) AS ranking,@prev_course := course_name
FROM stu
ORDER BY course_name, score DESC
这个子查询做了以下几件事:
- 首先,它对结果集进行排序,使得同一门课程的学生按其分数降序排列。
- 使用
IF
函数来检查当前行的课程名是否与前一行的课程名相同。如果相同,@ranking
加1;如果不同,@ranking
重置为1。 - 更新
@prev_course
为当前行的课程名。
3. 主查询
SELECT sid, std_name, course_name, score
FROM ... AS RankedScores
WHERE ranking <= 3;
这个主查询简单地从子查询的结果中选择排名前三的学生记录。
总的来说,这个查询使用MySQL的用户定义变量为每门课程的学生分数进行排名,并只选择排名前三的记录。
5 MySQL 8的新特性:
MySQL 8的新特性:
MySQL 8在2018年发布,并带来了许多新的特性和改进:
-
数据字典事务性:以前,MySQL中的元数据存储在多个文件和非事务性表中。在MySQL 8.0中,有了一个新的数据字典,所有的元数据都存储在单独的事务性表中。
-
增强的UTF-8支持:默认的字符集从
latin1
更改为utf8mb4
,支持更多的字符集,如Emoji、各种符号等。 -
窗口函数:这为SQL在MySQL中提供了标准的窗口函数,如
ROW_NUMBER()
,LEAD()
,LAG()
,等等。 -
公共表表达式 (Common Table Expressions, CTEs):通过使用
WITH
子句,MySQL 8.0开始支持CTEs。 -
原子DDL语句:DDL操作(如创建、更改、删除表或索引)现在是原子的,并完全支持事务。
-
角色支持:MySQL 8.0添加了对SQL角色的原生支持。
-
性能模式:一个新的性能查询工具,用于监控服务器和查询的性能。
-
JSON增强:对JSON数据类型的增强和改进。
-
优化器改进:查询执行计划的改进、直方图的引入等。
-
其他安全、复制和性能的改进。
4.5 类似于这种难度的sql,你还能再多出几个嘛
当然可以,这里是一些类似难度的SQL题目以及它们的背景描述:
-
获取每个员工的工资排名:
假设您有一个employees
表格,其中包含id
,name
, 和salary
字段。您需要编写一个SQL查询来获取每个员工的工资排名,不考虑并列情况。 -
查找顾客购买最多商品的日期:
假设您有一个orders
表格,其中包含id
,customer_id
,product_id
, 和order_date
字段。您的任务是确定哪一天有最多的不同客户下单。 -
查找没有订单的顾客:
假设您有两个表格,customers
和orders
。您需要列出那些至今还没有下单的客户。 -
获取每个产品的总销售额:
您有一个products
表和一个sales
表。您需要为每种产品计算总销售额。 -
查找连续三天访问网站的用户:
您有一个user_visits
表,其中包含user_id
和visit_date
字段。您的任务是找出连续三天都访问网站的用户。 -
查找上一个月的最佳销售员:
您有一个sales
表和一个employees
表。您需要找出上个月销售额最高的员工。
这只是几个SQL难题的例子,实际上有无数的可能性和场景,您可以根据具体的数据模型和需求来定制。