SQL刷题笔记-leetcode

184. 部门工资最高的员工

Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id。Department 表包含公司所有部门的信息。编写一个 SQL 查询,找出每个部门工资最高的员工。

SELECT b.name as Department, a.Name as Employee, a.salary as Salary
FROM employee a JOIN department b ON a.departmentid = b.id
# 注意点:如果where条件只是salary in子查询,则会选到A部门salary(非A部门max) = B部门max(salary)

WHERE (a.departmentid,a.salary) IN (
    SELECT departmentid, max(salary)
    FROM employee
    GROUP BY departmentid)

知识点:

  1. 可以用 ’ (a,b) in 子查询 ‘ 的方式同时查询两个字段。
627. 变更性别 CASE WHEN IF

表Salary中,sex这一列的值是ENUM类型,只能从(‘m’,‘f’)中取。

请你编写一个 SQL 查询来交换所有的 ‘f’ 和 ‘m’ (即,将所有 ‘f’ 变为 ‘m’ ,反之亦然),仅使用 单个 update 语句 ,且不产生中间临时表。

注意,你必须仅使用一条 update 语句,且 不能 使用 select 语句。

-- 方法一:CASE WHEN
UPDATE salary
SET
    sex = CASE sex
        WHEN 'm' THEN 'f'
        ELSE 'm'
    END;  -- 必须要有end。

-- 方法二:IF
update salary set sex=IF(sex='f','m','f');

知识点:

  1. CASE WHEN

    1) 第一种 格式 : 简单Case函数 :

    格式说明

    case 列名

    when 条件值1 then 选择项1

    when 条件值2 then 选项2…

    else 默认值 end

    2)第二种 格式 :Case搜索函数

    格式说明

    case

    when 列名= 条件值1 then 选择项1

    when 列名=条件值2 then 选项2…

    else 默认值 end

    提示:通常我们在写Case When的语句的时候,会容易忘记 end 这个结束,一定要记得哟!

    比较: 两种格式,可以实现相同的功能。

    简单Case函数的写法相对比较简洁,但是和Case搜索函数相比,功能方面会有些限制,比如写判断式。还有一个需要注意的问题,Case函数只返回第一个符合条件的值,剩下的Case部分将会被自动忽略。

  2. IF(expression,a, b):如果 expression 为True,则返回 a,否则返回 b

1179. 重新格式化部门表

部门表 Department:(id, month)是表的联合主键,这个表格有关于每个部门每月收入的信息。

编写一个 SQL 查询来重新格式化表,使得新的表中有一个部门 id 列和一些对应 每个月 的收入(revenue)列。

查询结果示例:
SQL刷题笔记-leetcode_第1张图片

select id,
    sum(case month when 'Jan' then revenue end) as Jan_Revenue,
    sum(case month when 'Feb' then revenue end) as Feb_Revenue,
    sum(case month when 'Mar' then revenue end) as Mar_Revenue,
    sum(case month when 'Apr' then revenue end) as Apr_Revenue,
    sum(case month when 'May' then revenue end) as May_Revenue,
    sum(case month when 'Jun' then revenue end) as Jun_Revenue,
    sum(case month when 'Jul' then revenue end) as Jul_Revenue,
    sum(case month when 'Aug' then revenue end) as Aug_Revenue,
    sum(case month when 'Sep' then revenue end) as Sep_Revenue,
    sum(case month when 'Oct' then revenue end) as Oct_Revenue,
    sum(case month when 'Nov' then revenue end) as Nov_Revenue,
    sum(case month when 'Dec' then revenue end) as Dec_Revenue
from Department
group by id
# 此题中sum处用max也是可以的。

知识点:

  1. GROUP BY 的理解

    在MySQL中,允许下面这样的写法:

    SELECT id,revenue  -- 输出的revenue的结果是每个id的第一个值
    FROM Department
    GROUP BY id
    

    即在select子句中出现了group by子句中没有出现的列名revenue,而这种写法在SQL标准中是没有的,在MySQL以外的大部分数据库中也是不支持的,因为逻辑上没有意义。

    sum(case month when 'Jan' then revenue end) as Jan_Revenue
    -- 该代码的意思即为将所有的revenue聚合处理,处理方法是如果month的值是Jan,那么结果就是revenue,否则忽略。
    

    因为根据题目描述我们可以知道,每个月份最多只会出现一次,所以用max取出那个唯一值就可以了,所以也可以用max来处理。

  2. CASE WHEN 的理解

    当一个单元格中有多个数据时,case when只会提取当中的第一个数据。

    以CASE WHEN month=‘Feb’ THEN revenue END 为例,当id=1时,它只会提取month对应单元格里的第一个数据,即Jan,它不等于Feb,所以找不到Feb对应的revenue,所以返回NULL。(可以试试把我上面答案里的sum()统统去掉,执行结果与预期不一样。错就错在当id=1时,Feb_Revenue和Mar_Revenue的值变成了NULL)

    那该如何解决单元格内含多个数据的情况呢?答案就是使用聚合函数,聚合函数就用来输入多个数据,输出一个数据的。如SUM()或MAX(),而每个聚合函数的输入就是每一个多数据的单元格。

    以SUM(CASE WHEN month=‘Feb’ THEN revenue END) 为例,当id=1时,它提取的Jan、Feb、Mar,从中找到了符合条件的Feb,并最终返回对应的revenue的值,即7000。

176. 第二高的薪水 IFNULL()LIMIT

编写一个 SQL 查询,获取 Employee 表中第二高的薪水(Salary) 。

如果不存在第二高的薪水,那么查询应返回 null

# 方法一:通过子查询找到最大值,然后再找出小于最大值的最大值
select max(salary) SecondHighestSalary
from employee
where salary < (select max(salary) from employee )

# 方法二:用ifnull函数和limit offset函数
SELECT
    IFNULL(
      (SELECT DISTINCT Salary
       FROM Employee
       ORDER BY Salary DESC
        LIMIT 1,1),  # LIMIT 1,1 等同于 'LIMIT 1 OFFSET 1'
    NULL) AS SecondHighestSalary

知识点:

  1. IFNULL(a,b) 函数用于判断第一个表达式是否为null,如果为null,则返回b,如果不为null,则返回a。

  2. LIMIT子句 可以被用于强制SELECT语句返回指定的记录数。LIMIT接收一个或两个参数,参数必须是整数常量。如果给定2个参数,第一个参数制定第一个返回记录行的偏移量,第二个参数制定返回记录行的最大数目。注意:初始记录行的偏移量是0,不是1。为了与 PostgreSQL 兼容,MySQL 也支持句法: LIMIT # OFFSET #。

    例1:mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15

    例2:mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.

177. 第N高的薪水

编写一个 SQL 查询,获取 Employee 表中第 n 高的薪水(Salary)。

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN 
    SET N = N - 1;
    RETURN ( # Write your MySQL query statement below.
    SELECT ifnull( ( SELECT DISTINCT salary FROM Employee    
    ORDER BY salary DESC LIMIT N, 1 ), NULL ) );
END

知识点:

  1. LIMIT里面不能做运算,所以要在RETURN前先处理下N的值,SET N = N-1
178. 分数排名

编写一个 SQL 查询来实现分数排名。

如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。

# 根据题目要求 分数相同时排名相同,且名词连续,因此适用`dense_rank()`排序函数
select Score, dense_rank() over(order by Score desc) `Rank`
from Scores
# 注意:由于Rank是mysql中的关键字,所以在用其作为别名的时候,需要用 '' 或者用 ``

-- 另一种不用排序函数的做法:
select a.Score as Score,
  (select count(distinct b.Score) 
   from Scores b 
   where b.Score >= a.Score) as `Rank`
from Scores a
order by a.Score DESC

知识点:

  1. 四大排名函数

    1)ROW_NUMBER()

    Row_number() 在排名时,序号连续不重复,即使遇到表中的两个一样的数值亦是如此。

    2)RANK()

    Rank() 函数会把要求排序的值相同的归为一组且每组序号一样,排序不连续

    3)DENSE_RANK()

    Dense_rank() 函数会把要求排序的值相同的归为一组且每组序号一样,且排序是连续的

    4)NTILE()

    Ntile(group_num) 将所有记录分成group_num个组,每组序号一样

180. 连续出现的数字

编写一个 SQL 查询,查找所有至少连续出现三次的数字。

返回的结果表中的数据可以按 任意顺序 排列。

# 连续出现的意味着相同数字的 Id 是连着的,由于这题问的是至少连续出现 3 次,我们使用 Logs 并检查是否有 3 个连续的相同数字。同时我们需要添加关键字 DISTINCT ,因为如果一个数字连续出现超过 3 次,会返回重复元素。
SELECT DISTINCT
    l1.Num AS ConsecutiveNums
FROM
    Logs l1,
    Logs l2,
    Logs l3
WHERE
    l1.Id = l2.Id - 1
    AND l2.Id = l3.Id - 1
    AND l1.Num = l2.Num
    AND l2.Num = l3.Num
626. 换座位

小美是一所中学的信息科技老师,她有一张 seat 座位表,平时用来储存学生名字和与他们相对应的座位 id。

其中纵列的 id 是连续递增的,小美想改变相邻俩学生的座位。你能不能帮她写一个 SQL query 来输出小美想要的结果呢?

如果学生人数是奇数,则不需要改变最后一个同学的座位。

# 方法一:使用 CASE,对于所有座位 id 是奇数的学生,修改其 id 为 id+1,如果最后一个座位 id 也是奇数,则最后一个座位 id 不修改。对于所有座位 id 是偶数的学生,修改其 id 为 id-1。
SELECT
    (CASE
        WHEN MOD(id, 2) != 0 AND counts != id THEN id + 1
        WHEN MOD(id, 2) != 0 AND counts = id THEN id
        ELSE id - 1
    END) AS id,
    student
FROM
    seat,
    (SELECT
        COUNT(*) AS counts
    FROM
        seat) AS seat_counts
ORDER BY id ASC;

## 这个做法不对,因为case when只返回第一个结果。
SELECT
    (CASE
        WHEN MOD(id, 2) != 0 AND count(id) != id THEN id + 1
        WHEN MOD(id, 2) != 0 AND count(id) = id THEN id
        ELSE id - 1
    END) AS id, student
FROM seat
ORDER BY id ASC;

# 方法二:使用位操作和 COALESCE(),使用 (id+1)^1-1 计算交换后每个学生的座位 id。
SELECT
    s1.id, COALESCE(s2.student, s1.student) AS student
FROM
    seat s1
        LEFT JOIN
    seat s2 ON ((s1.id + 1) ^ 1) - 1 = s2.id
ORDER BY s1.id;

知识点:

  1. COALESCE(a, b, ...)

    • 用途:返回列表中第一个非null的值,如果列表中的所有值都是null,则返回null。

    • 参数:a, b, …实要测试的值,所有这些值类型必须相同或为null,且参数至少要有一个,否则会引发异常。

    • 返回值:返回值类型和参数类型相同。

185. 部门工资前三高的所有员工

Employee 表包含所有员工信息,每个员工有其对应的工号 Id,姓名 Name,工资 Salary 和部门编号 DepartmentId

Department 表包含公司所有部门的信息。

编写一个 SQL 查询,找出每个部门获得前三高工资的所有员工。

SELECT
    d.Name AS 'Department', e1.Name AS 'Employee', e1.Salary
FROM
    Employee e1
        JOIN
    Department d ON e1.DepartmentId = d.Id
WHERE
    3 > (SELECT
            COUNT(DISTINCT e2.Salary)
        FROM
            Employee e2
        WHERE
            e2.Salary > e1.Salary
                AND e1.DepartmentId = e2.DepartmentId
        )
;
262. 行程和用户

有两张表,trips和users。

trips表,含id, client_id, driver_id, city_id, status, request_at;

users表,含 user_id, banned, role

写一段 SQL 语句查出 “2013-10-01” 至 “2013-10-03” 期间非禁止用户(乘客和司机都必须未被禁止)的取消率。非禁止用户即 Banned 为 No 的用户,禁止用户即 Banned 为 Yes 的用户。

取消率 的计算方式如下:(被司机或乘客取消的非禁止用户生成的订单数量) / (非禁止用户生成的订单总数)。

返回结果表中的数据可以按任意顺序组织。其中取消率 Cancellation Rate 需要四舍五入保留 两位小数 。

输出:result表,含 day, cancellation rate

-- 复杂版本,分别计算出取消的数据及总订单数,再并表计算。
select b.day Day, round(ifnull(a.cancelledtrip,0) / b.totaltrip , 2)  as "Cancellation Rate"
from
         (select t.request_at Day, count(*) totaltrip
         from trips t
             left join users u1 on t.client_id = u1.users_id
             left join users u2 on t.driver_id = u2.users_id
         where  u1.banned <> 'Yes' and u2.banned <> 'Yes'
                 and t.request_at between '2013-10-01' and '2013-10-03'
         group by t.request_at) b
 left join
         (select t.request_at Day, count(*) cancelledtrip
         from trips t
             left join users u1 on t.client_id = u1.users_id
             left join users u2 on t.driver_id = u2.users_id
         where  u1.banned <> 'Yes' and u2.banned <> 'Yes'
                 and t.request_at between '2013-10-01' and '2013-10-03'
                 and t.status like "cancelled%"
         group by t.request_at) a
 on b.day = a.day
 order by b.day

-- 基于以上复杂版本,可以使用sum-if进行统计,简化代码
select t.request_at Day, round(sum(IF(t.status = 'completed',0,1)) / count(*) ,2) 'Cancellation Rate'
from trips t
    left join users u1 on t.client_id = u1.users_id
    left join users u2 on t.driver_id = u2.users_id
where  u1.banned <> 'Yes' and u2.banned <> 'Yes'
        and t.request_at between '2013-10-01' and '2013-10-03'
group by t.request_at
order by t.request_at

知识点:

  1. 两种基于某个条件的数据统计方法:

    # 方法一:sum+if
    sum(IF(t.status = 'completed',0,1)
    
    # 方法二:sum+case when
    sum(	case t.status 
          when 'cancelled_by_driver' then 1 
          when 'cancelled_by_client' then 1
          else 0 end
    		)
    

你可能感兴趣的:(数据分析,sql)