题目描述
编写一个 SQL 查询,获取 Employee
表中第二高的薪水(Salary) 。
表1:Employee (Primary Key: Id)
Id | Salary |
---|---|
1 | 100 |
2 | 200 |
3 | 300 |
例如上述 Employee
表,SQL查询应该返回 200
作为第二高的薪水。如果不存在第二高的薪水,那么查询应返回 null
。
SecondHighestSalary |
---|
200 |
相关知识
- SQL中使用ORDER BY进行排序,降序排列使用DESC
- LIMIT n,m 第一个参数指定第一个返回记录的偏移量,第二个参数指定返回记录行的最大数目,起始行偏移量是0
解题过程
-
编写SQL
SELECT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1
-
提交后,答案错误
-
my output
{ "headers":[ "Salary" ], "values":[ [ 200 ] ] }
-
standard output
{ "headers":[ "SecondHighestSalary" ], "values":[ [ 200 ] ] }
-
返回的字段名需要更改
-
-
修改sql,使用AS关键字可以给字段起一个别名,并用别名返回
SELECT Salary AS SecondHighestSalary FROM Employee ORDER BY Salary DESC LIMIT 1,1
-
提交,解答错误
-
my output
{ "headers":[ "SecondHighestSalary" ], "values":[ ] }
-
standard output
{ "headers":[ "SecondHighestSalary" ], "values":[ [ null ] ] }
-
sql查询无结果时,默认返回的是空白,而不是空值。
-
-
查询资料,发现可以通过ifnull方法进行替换,
然而查询记录为空并不是NULL,所以这个方法没用 -
多次尝试后,编写sql
WITH CTE AS (SELECT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1) SELECT CASE(SELECT COUNT(*) FROM CTE) WHEN 1 THEN (SELECT Salary FROM CTE) ELSE NULL END AS SecondHighestSalary
- 公用表表达式允许使用命名的临时结果集,通过允许在SELECT和某些其他语句前面使用WITH子句来实现,格式为WITH CTEname AS (query),这样就可以避免多次重复执行这个查询
- 使用CASE WHEN END控制结构,来实现当查询结果为空时,把[]替换为[null]
-
提交,出现了新的错误
-
input
{ "headers":{ "Employee":[ "Id", "Salary" ] }, "rows":{ "Employee":[ [ 1, 100 ], [ 2, 100 ] ] } }
-
my output
{ "headers":[ "SecondHighestSalary" ], "values":[ [ 100 ] ] }
-
standard output
{ "headers":[ "SecondHighestSalary" ], "values":[ [ null ] ] }
-
可以看出这是因为忘记了去除重复值
-
-
使用DISTINCT去除重复值,构建sql,DISTINCT需要在select的字段名前修饰
WITH CTE AS (SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1) SELECT CASE(SELECT COUNT(*) FROM CTE) WHEN 1 THEN (SELECT Salary FROM CTE) ELSE NULL END AS SecondHighestSalary
学习总结
-
公用表表达式(CTE)
不能在同一查询中两次引用派生表,因为查询会根据派生表的引用次数计算两次或多次,这会引发严重的性能问题
-
假如想要了解每年工资相对前一年的增长百分比,工资表为salaries表,因为要对比相邻两年的涨幅,就需要进行笛卡尔积,而要进行笛卡尔积的表需要对目前的表进行处理,因为目前的表中一行是每次发工资的条目,而不是一年一个条目,对数据处理完后形成一个派生表,之后进行笛卡尔积。从下面的代码中可以看到,q1和q2其实是同样的内容,但是却要每个单独计算一遍,浪费了一半的计算量。
SELECT q1.year; q2.year AS next_year; q1.sum; q2.sum AS next_sum; 100*(q2.sum-q1.sum)/q1.sum AS percent FROM (SELECT year(from_date) as year,sum(salary) as sum FROM salaries GROUP BY year) AS q1, (SELECT year(from_date) as year,sum(salary) as sum FROM salaries GROUP BY year) AS q2 WHERE q1.year=q2.year-1;
-
公式表表达式正是用来解决这个问题,它允许在SELECT等语句前使用WITH语句来对临时结果集进行命名。
-
在命名之后,本次SQL中就可以通过这个名字来访问临时结果集,而无需再次计算,上面的代码也就可以精简为
WITH CTE AS (SELECT year(from_date) as year,sum(salary) as sum FROM salaries GROUP BY year) SELECT FROM CTE AS q1, CTE AS q2 WHERE q1.year=q2.year-1;
-
-
数据库中的NULL
-
NULL、空字符串的长度比较
-
sql
SELECT LENGTH(NULL),LENGTH(''),LENGTH(233),LENGTH('6666')
-
执行结果
LENGTH(NULL) LENGTH(‘’) LENGTH(0) LENGTH(‘6666’) NULL 0 1 4 -
可以看出NULL并不是0,也不是空字符串
-
-
NULL是未定义的或未知的
-
判断是否为NULL时需要使用ISNULL()函数,而不能用等号和不等号进行判断
-
当数据表中某个字段允许为NULL时,会使用额外的比特位标识当前元组的这个属性是否为NULL
-
NULL参与任何运算的结果均是NULL,例如判断NULL是否等于NULL
-
如下sql,执行结果为1
SELECT (2=2)
-
而如下sql,执行结果为NULL
SELECT (NULL=NULL)
-
-
对于一件语句,其他语言基本是二值逻辑,即真和假,而在数据库中会有真、假、未知真假三种状态,而NULL就用来表示这个状态
-
当查询表之后,一条记录都没有时,是NULL的状态
-
数据表的某个元组的某个属性也可能是NULL的状态
-
如果想实现NULL to something,可以使用IFNULL(字段名,表达式)来对字段为NULL时进行某些操作
-
*在数据库中代表匹配所有,此时会匹配NULL;当指定某个字段进行某些操作时,如CONUT(),就会忽略NULL
-
之前提交出错的sql
-
尝试IF()出错,IF()函数接收三个表达式,如果第一个参数为真,则执行第二个参数并作为结果返回,否则执行第三个参数并作为结果返回
-
sql
SELECT DISTINCT IF(Salary,Salary,NULL) AS SecondHighestSalary FROM Employee ORDER BY Salary DESC LIMIT 1,1
-
它在结果不为空时正常返回,在结果为空时没有替换为NULL
-
当删掉FROM….LIMIT 1,1这些语句,即不从表中查询时,将判断语句改为2<1,结果可以正常替换为NULL
-
修改sql
SELECT IF( (SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1), (SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1), NULL ) AS SecondHighestSalary
-
提交sql,出现了新的错误
-
input
{ "headers":{ "Employee":[ "Id", "Salary" ] }, "rows":{ "Employee":[ [ 3, 1 ], [ 2, 0 ], [ 1, 0 ] ] } }
-
my output
{ "headers":[ "SecondHighestSalary" ], "values":[ [ null ] ] }
-
可以看出这里的第二高是0,而IF语句把第一个语句的返回值0当作了执行结果为假,导致跳转到条件为假的第三个表达式执行,所以错误输出了NULL
-
-
修改sql
SELECT IF( (SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1) IS NOT NULL, (SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1), NULL ) AS SecondHighestSalary
-
提交,答案正确
-
但是本次提交的sql浪费了接近一半的计算,分别是IF()的第一个表达式和第二个表达式,它们两次查表的结果完全一样,却执行了两遍
-
使用公用表表达式进行改进
WITH CTE AS (SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1) SELECT IF( (SELECT COUNT(*) FROM CTE)=0, NULL, (SELECT Salary FROM CTE) ) AS SecondHighestSalary
-
-
使用IFNULL()函数,这个函数接收两个表达式,如果第一个参数不为NULL,则返回第一个参数的执行结果,否则返回第二个参数的执行结果
-
sql
SELECT DISTINCT IFNULL( Salary,NULL) AS SecondHighestSalary FROM Employee ORDER BY Salary DESC LIMIT 1,1
-
my output
{ "headers":[ "SecondHighestSalary" ], "values":[ ] }
-
standard output
{ "headers":[ "SecondHighestSalary" ], "values":[ null ] }
-
仍然是没有完成替换,查看题解,发现题解中一旦有了IFNULL()函数就不再有FROM WHERE子句
SELECT IFNULL( (SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1), NULL ) AS SecondHighestSalary
-
-