目录

  • 第六章:决胜秋招
    • Section A
      • 练习一: 各部门工资最高的员工(难度:中等)
      • 练习二: 换座位(难度:中等)
      • 练习三: 分数排名(难度:中等)
      • 练习四:连续出现的数字(难度:中等)
      • 练习五:树节点 (难度:中等)
      • 练习六:至少有五名直接下属的经理 (难度:中等)
      • 练习七:查询回答率最高的问题 (难度:中等)
      • 练习八:各部门前3高工资的员工(难度:中等)
      • 练习九:平面上最近距离 (难度: 困难)
      • 练习十:行程和用户(难度:困难)
    • Section B
      • 练习一:行转列
      • 练习二:列转行
      • 练习三:谁是明星带货主播?
      • 练习四:MySQL 中如何查看sql语句的执行计划?可以看到哪些信息?
      • 练习五:解释一下 SQL 数据库中 ACID 是指什么
    • Section C
      • 练习一:行转列
      • 练习二:列转行
      • 练习三:连续登录
      • 练习四:用户购买商品推荐
      • 练习五:hive 数据倾斜的产生原因及优化策略?
      • 练习六:LEFT JOIN 是否可能会出现多出的行?为什么?
  • 学习参考

第六章:决胜秋招

Section A

练习一: 各部门工资最高的员工(难度:中等)

创建Employee 表,包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id。

+----+-------+--------+--------------+
| Id | Name  | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1  | Joe   | 70000  | 1            |
| 2  | Henry | 80000  | 2            |
| 3  | Sam   | 60000  | 2            |
| 4  | Max   | 90000  | 1            |
+----+-------+--------+--------------+
CREATE TABLE Employee
(id          INT      NOT NULL ,
name      VARCHAR(50) NOT NULL ,
salary       INT      NOT NULL ,
departmentid INT      NOT NULL ,
PRIMARY KEY (id));INSERT INTO Employee VALUES(1,'Joe',70000,1);
INSERT INTO Employee VALUES(2,'Henry',80000,2);
INSERT INTO Employee VALUES(3,'Sam',60000,2);
INSERT INTO Employee VALUES(4,'Max',90000,1);SELECT * FROM Employee;

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

+----+----------+
| Id | Name     |
+----+----------+
| 1  | IT       |
| 2  | Sales    |
+----+----------+
CREATE TABLE Department
(id          INT      NOT NULL ,
name      VARCHAR(50) NOT NULL ,
PRIMARY KEY (id));INSERT INTO Department VALUES(1,'IT');
INSERT INTO Department VALUES(2,'Sales');SELECT * FROM Department ;

编写一个 SQL 查询,找出每个部门工资最高的员工。例如,根据上述给定的表格,Max 在 IT 部门有最高工资,Henry 在 Sales 部门有最高工资。

+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT         | Max      | 90000  |
| Sales      | Henry    | 80000  |
+------------+----------+--------+
SELECTa.department, ee.name , ee.salary
FROM
(SELECT MAX(e.salary) as salary, d.name as department, e.departmentidFROM employee eLEFT JOIN department d on e.departmentid = d.idGROUP BY d.name
)a
LEFT JOIN employee ee on ee.salary = a.salary and ee.departmentid = a.departmentid

练习二: 换座位(难度:中等)

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

其中纵列的id是连续递增的

小美想改变相邻俩学生的座位。

你能不能帮她写一个 SQL query 来输出小美想要的结果呢?

请创建如下所示seat表:

示例:

+---------+---------+
|    id   | student |
+---------+---------+
|    1    | Abbot   |
|    2    | Doris   |
|    3    | Emerson |
|    4    | Green   |
|    5    | Jeames  |
+---------+---------+
CREATE TABLE seat
(id        INT      NOT NULL ,
student VARCHAR(20) NOT NULL ,
PRIMARY KEY (id));INSERT INTO seat VALUES(1,'Abbot');
INSERT INTO seat VALUES(2,'Doris');
INSERT INTO seat VALUES(3,'Emerson');
INSERT INTO seat VALUES(4,'Green');
INSERT INTO seat VALUES(5,'Jeames');SELECT * FROM seat;

假如数据输入的是上表,则输出结果如下:

+---------+---------+
|    id   | student |
+---------+---------+
|    1    | Doris   |
|    2    | Abbot   |
|    3    | Green   |
|    4    | Emerson |
|    5    | Jeames  |
+---------+---------+

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

SELECTCASE WHEN id = (SELECT MAX(id) FROM seat) THEN idWHEN MOD(id,2)=1 THEN id+1WHEN MOD(id,2)=0 THEN id-1ELSE NULL END AS id,student
FROM seat
ORDER BY id;

练习三: 分数排名(难度:中等)

假设在某次期末考试中,二年级四个班的平均成绩分别是 93、93、93、91

+-------+-----------+
| class | score_avg |
+-------+-----------+
|    1  |       93  |
|    2  |       93  |
|    3  |       93  |
|    4  |       91  |
+-------+-----------+
CREATE TABLE score
(class     INT NOT NULL ,
score_avg  INT NOT NULL ,
PRIMARY KEY (class));INSERT INTO score VALUES(1, 93);
INSERT INTO score VALUES(2, 93);
INSERT INTO score VALUES(3, 93);
INSERT INTO score VALUES(4, 91);SELECT * FROM score;

目前有如下三种排序结果,请根据查询结果书写出查询用 sql

+-------+-----------+-------+-------+-------+
| class | score_avg | rank1 | rank2 | rank3 |
+-------+-----------+-------+-------+-------+
|     1 |        93 |     1 |     1 |     1 |
|     2 |        93 |     1 |     1 |     2 |
|     3 |        93 |     1 |     1 |     3 |
|     4 |        91 |     4 |     2 |     4 |
+-------+-----------+-------+-------+-------+
SELECT class,score_avg,RANK() OVER (ORDER BY score_avg DESC) AS rank1,DENSE_RANK() OVER (ORDER BY score_avg DESC) AS rank2,ROW_NUMBER() OVER (ORDER BY score_avg DESC) AS rank3
FROM  score;

练习四:连续出现的数字(难度:中等)

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

+----+-----+
| Id | Num |
+----+-----+
| 1  |  1  |
| 2  |  1  |
| 3  |  1  |
| 4  |  2  |
| 5  |  1  |
| 6  |  2  |
| 7  |  2  |
+----+-----+
CREATE TABLE logs
(id INT NOT NULL ,
num INT NOT NULL ,
PRIMARY KEY (id));INSERT INTO logs VALUES (1, 1);
INSERT INTO logs VALUES (2, 1);
INSERT INTO logs VALUES (3, 1);
INSERT INTO logs VALUES (4, 2);
INSERT INTO logs VALUES (5, 1);
INSERT INTO logs VALUES (6, 2);
INSERT INTO logs VALUES (7, 2);SELECT * FROM logs;

例如,给定上面的 Logs 表, 1 是唯一连续出现至少三次的数字。

+-----------------+
| ConsecutiveNums |
+-----------------+
| 1               |
+-----------------+
SELECT DISTINCT a.num AS ConsecutiveNums
FROMLOGS a, LOGS b, LOGS c
WHEREa.id = b.id - 1 AND b.id = c.id - 1 AND a.num = b.num AND b.num = c.num;

练习五:树节点 (难度:中等)

对于tree表,id是树节点的标识,p_id是其父节点的id

+----+------+
| id | p_id |
+----+------+
| 1  | null |
| 2  | 1    |
| 3  | 1    |
| 4  | 2    |
| 5  | 2    |
+----+------+

每个节点都是以下三种类型中的一种:

  • Root: 如果节点是根节点。
  • Leaf: 如果节点是叶子节点。
  • Inner: 如果节点既不是根节点也不是叶子节点。
CREATE TABLE tree
(id  INT NOT NULL ,
p_id INT ,
PRIMARY KEY (id));INSERT INTO tree VALUES (1, null);
INSERT INTO tree VALUES (2, 1);
INSERT INTO tree VALUES (3, 1);
INSERT INTO tree VALUES (4, 2);
INSERT INTO tree VALUES (5, 2);SELECT * FROM tree;

写一条查询语句打印节点id及对应的节点类型。按照节点id排序。上面例子的对应结果为:

+----+------+
| id | Type |
+----+------+
| 1  | Root |
| 2  | Inner|
| 3  | Leaf |
| 4  | Leaf |
| 5  | Leaf |
+----+------+

说明

  • 节点’1’是根节点,因为它的父节点为NULL,有’2’和’3’两个子节点。
  • 节点’2’是内部节点,因为它的父节点是’1’,有子节点’4’和’5’。
  • 节点’3’,‘4’,'5’是叶子节点,因为它们有父节点但没有子节点。

下面是树的图形:

    1         /   \ 2    3
/ \
4  5

注意

如果一个树只有一个节点,只需要输出根节点属性。

SELECT id,CASE WHEN p_id IS NULL THEN 'Root'WHEN id IN (SELECT p_id FROM tree) THEN 'Inner'ELSE 'Leaf' END AS Type
FROM tree;

练习六:至少有五名直接下属的经理 (难度:中等)

Employee表包含所有员工及其上级的信息。每位员工都有一个Id,并且还有一个对应主管的Id(ManagerId)。

+------+----------+-----------+----------+
|Id    |Name      |Department |ManagerId |
+------+----------+-----------+----------+
|101   |John      |A          |null      |
|102   |Dan       |A          |101       |
|103   |James     |A          |101       |
|104   |Amy       |A          |101       |
|105   |Anne      |A          |101       |
|106   |Ron       |B          |101       |
+------+----------+-----------+----------+
CREATE TABLE Employee2
(id           INT      NOT NULL ,
name       varchar(20) NOT NULL ,
department varchar(20),
managerid     INT,
PRIMARY KEY (id));INSERT INTO Employee2 VALUES (101,  'John', 'A', null);
INSERT INTO Employee2 VALUES (102,   'Dan', 'A', 101);
INSERT INTO Employee2 VALUES (103, 'James', 'A', 101);
INSERT INTO Employee2 VALUES (104,   'Amy', 'A', 101);
INSERT INTO Employee2 VALUES (105,  'Anne', 'A', 101);
INSERT INTO Employee2 VALUES (106,   'Ron', 'B', 101);SELECT * FROM Employee2;

针对Employee表,写一条SQL语句找出有5个下属的主管。对于上面的表,结果应输出:

+-------+
| Name  |
+-------+
| John  |
+-------+

注意:

没有人向自己汇报。

SELECT e1.NAME
FROM Employee2 e1
JOIN (SELECT manageridFROM Employee2GROUP BY manageridHAVING count(id) = 5) t1
ON e1.id = t1.managerid;

练习七:查询回答率最高的问题 (难度:中等)

求出survey_log表中回答率最高的问题,表格的字段有:uid, action, question_id, answer_id, q_num, timestamp

uid是用户id;action的值为:“show”, “answer”, “skip”;当action是"answer"时,answer_id不为空,相反,当action是"show"和"skip"时为空(null);q_num是问题的数字序号。

写一条sql语句找出回答率(show 出现次数 / answer 出现次数)最高的 question_id

举例:

输入

uid action question_id answer_id q_num timestamp
5 show 285 null 1 123
5 answer 285 124124 1 124
5 show 369 null 2 125
5 skip 369 null 2 126

输出

question_id
285

说明

问题285的回答率为1/1,然而问题369的回答率是0/1,所以输出是285。

注意:

最高回答率的意思是:同一个问题出现的次数中回答的比例。

CREATE TABLE survey_log (uid INT,action VARCHAR (20),question_id INT,answer_id INT,q_num INT,TIMESTAMP INT
);INSERT INTO survey_log VALUES (5, 'show', 285, NULL, 1, 123);
INSERT INTO survey_log VALUES (5, 'answer', 285, 124124, 1, 124);
INSERT INTO survey_log VALUES (5, 'show', 369, NULL, 2, 125);
INSERT INTO survey_log VALUES (5, 'skip', 369, NULL, 2, 126);SELECT * FROM survey_log;SELECT a.question_id
FROM (SELECT question_id,sum(CASE WHEN answer_id IS NOT NULL THEN 1 ELSE 0 END) / sum(CASE WHEN action = 'show' THEN 1 ELSE 0 END) AS ratioFROM survey_logGROUP BY question_idORDER BY ratio DESCLIMIT 1) a;

练习八:各部门前3高工资的员工(难度:中等)

将练习一中的 employee 表清空,重新插入以下数据(也可以复制练习一中的 employee 表,再插入第5、第6行数据):

+----+-------+--------+--------------+
| Id | Name  | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1  | Joe   | 70000  | 1            |
| 2  | Henry | 80000  | 2            |
| 3  | Sam   | 60000  | 2            |
| 4  | Max   | 90000  | 1            |
| 5  | Janet | 69000  | 1            |
| 6  | Randy | 85000  | 1            |
+----+-------+--------+--------------+

编写一个 SQL 查询,找出每个部门工资前三高的员工。例如,根据上述给定的表格,查询结果应返回:

+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT         | Max      | 90000  |
| IT         | Randy    | 85000  |
| IT         | Joe      | 70000  |
| Sales      | Henry    | 80000  |
| Sales      | Sam      | 60000  |
+------------+----------+--------+

此外,请考虑实现各部门前N高工资的员工功能。

-- 建表语句
CREATE TABLE employee9 SELECT * FROM employee;
SELECT * FROM employee9;

INSERT INTO employee9 VALUES(5, 'Janet', 69000, 1);
INSERT INTO employee9 VALUES(6, 'Randy', 85000, 1);SELECT * FROM employee9 ;

-- 窗口函数解法
SELECT t.department, t.employee, t.salary
FROM(SELECT d.name AS department, e.name AS employee, e.salary,DENSE_RANK() OVER ( PARTITION BY d.name ORDER BY e.salary) AS ranknFROM employee9 eJOIN department dON e.departmentid = d.id) t
WHERE t.rankn <= 3
ORDER BY department, salary DESC;

练习九:平面上最近距离 (难度: 困难)

point_2d表包含一个平面内一些点(超过两个)的坐标值(x,y)。

写一条查询语句求出这些点中的最短距离并保留2位小数。

|x   | y  |
|----|----|
| -1 | -1 |
|  0 |  0 |
| -1 | -2 |

最短距离是1,从点(-1,-1)到点(-1,-2)。所以输出结果为:

| shortest |

1.00

+--------+
|shortest|
+--------+
|1.00    |
+--------+

**注意:**所有点的最大距离小于10000。

-- 建表语句
-- 建表语句
DROP TABLE IF EXISTS point_2d;CREATE TABLE point_2d (x INT,y INT
);INSERT INTO point_2d VALUES(-1, -1);
INSERT INTO point_2d VALUES( 0,  0);
INSERT INTO point_2d VALUES(-1, -2);SELECT * FROM point_2d;SELECTMIN(ROUND(POW(POW(ABS(p1.x-p2.x),2)+POW(ABS(p1.y-p2.y),2),0.5),2)) AS shortest
FROM point_2d p1
JOIN point_2d p2
ON p1.x!=p2.x
OR p1.y!=p2.y;

练习十:行程和用户(难度:困难)

Trips 表中存所有出租车的行程信息。每段行程有唯一键 Id,Client_Id 和 Driver_Id 是 Users 表中 Users_Id 的外键。Status 是枚举类型,枚举成员为 (‘completed’, ‘cancelled_by_driver’, ‘cancelled_by_client’)。

Id Client_Id Driver_Id City_Id Status Request_at
1 1 10 1 completed 2013-10-1
2 2 11 1 cancelled_by_driver 2013-10-1
3 3 12 6 completed 2013-10-1
4 4 13 6 cancelled_by_client 2013-10-1
5 1 10 1 completed 2013-10-2
6 2 11 6 completed 2013-10-2
7 3 12 6 completed 2013-10-2
8 2 12 12 completed 2013-10-3
9 3 10 12 completed 2013-10-3
10 4 13 12 cancelled_by_driver 2013-10-3

Users 表存所有用户。每个用户有唯一键 Users_Id。Banned 表示这个用户是否被禁止,Role 则是一个表示(‘client’, ‘driver’, ‘partner’)的枚举类型。

+----------+--------+--------+
| Users_Id | Banned |  Role  |
+----------+--------+--------+
|    1     |   No   | client |
|    2     |   Yes  | client |
|    3     |   No   | client |
|    4     |   No   | client |
|    10    |   No   | driver |
|    11    |   No   | driver |
|    12    |   No   | driver |
|    13    |   No   | driver |
+----------+--------+--------+

写一段 SQL 语句查出2013年10月1日2013年10月3日期间非禁止用户的取消率。基于上表,你的 SQL 语句应返回如下结果,取消率(Cancellation Rate)保留两位小数。

+------------+-------------------+
|     Day    | Cancellation Rate |
+------------+-------------------+
| 2013-10-01 |       0.33        |
| 2013-10-02 |       0.00        |
| 2013-10-03 |       0.50        |
+------------+-------------------+
-- create table
DROP TABLE if EXISTS Trips;
CREATE TABLE Trips
(Id INT,
Client_Id INT,
Driver_Id INT,
City_Id INT,
Status VARCHAR(30),
Request_at DATE,
PRIMARY KEY (Id));INSERT INTO Trips VALUES (1, 1, 10, 1, 'completed', '2013-10-1');
INSERT INTO Trips VALUES (2, 2, 11, 1, 'cancelled_by_driver', '2013-10-1');
INSERT INTO Trips VALUES (3, 3, 12, 6, 'completed', '2013-10-1');
INSERT INTO Trips VALUES (4, 4, 13, 6, 'cancelled_by_client', '2013-10-1');
INSERT INTO Trips VALUES (5, 1, 10, 1, 'completed', '2013-10-2');
INSERT INTO Trips VALUES (6, 2, 11, 6, 'completed', '2013-10-2');
INSERT INTO Trips VALUES (7, 3, 12, 6, 'completed', '2013-10-2');
INSERT INTO Trips VALUES (8, 2, 12, 12, 'completed', '2013-10-3');
INSERT INTO Trips VALUES (9, 3, 10, 12, 'completed', '2013-10-3');
INSERT INTO Trips VALUES (10, 4, 13, 12, 'cancelled_by_driver', '2013-10-3');SELECT * FROM Trips;DROP TABLE if EXISTS Users ;
CREATE TABLE Users
(Users_Id  INT,Banned    VARCHAR(30),Role      VARCHAR(30),
PRIMARY KEY (Users_Id));INSERT INTO Users VALUES (1,    'No',  'client');
INSERT INTO Users VALUES (2,    'Yes', 'client');
INSERT INTO Users VALUES (3,    'No',  'client');
INSERT INTO Users VALUES (4,    'No',  'client');
INSERT INTO Users VALUES (10,   'No',  'driver');
INSERT INTO Users VALUES (11,   'No',  'driver');
INSERT INTO Users VALUES (12,   'No',  'driver');
INSERT INTO Users VALUES (13,   'No',  'driver');SELECT * FROM Users;-- QUERY
SELECT TTT.Request_at,-- 3.分别对符合条件的分子和分母求和,并保留2位小数ROUND(SUM(CASE WHEN TTT.Status like 'cancelled%'AND TTT.Banned1 != 'YES'AND TTT.Banned2 != 'YES' THEN 1 ELSE 0 END)/SUM(CASE WHEN TTT.Banned1 != 'YES'AND TTT.Banned2 != 'YES' THEN 1 ELSE 0 END),2)AS "Cancellation Rate"
FROM
(
-- 2.获取 driver 用户 Banned 标识SELECT TT.Request_at, TT.Status, TT.Banned AS Banned1, UU.Banned AS Banned2FROM(-- 1.获取 cilent 用户 Banned 标识SELECT T.Request_at, T.Driver_Id, T.Status, U.Banned, U.Role FROM Trips TJOIN Users U ON T.Client_Id = U.Users_Id AND U.Role = 'client'WHERE T.Request_at BETWEEN '2013-10-01' AND '2020-10-03') TTJOIN Users UU ON TT.Driver_Id = UU.Users_Id AND UU.Role = 'driver') TTTGROUP BY TTT.Request_at

Section B

练习一:行转列

假设 A B C 三位小朋友期末考试成绩如下所示:

+-----+-----------+------|
| name|   subject |score |
+-----+-----------+------|
|  A  |  chinese  |  99  |
|  A  |  math     |  98  |
|  A  |  english  |  97  |
|  B  |  chinese  |  92  |
|  B  |  math     |  91  |
|  B  |  english  |  90  |
|  C  |  chinese  |  88  |
|  C  |  math     |  87  |
|  C  |  english  |  86  |
+-----+-----------+------|

请使用 SQL 代码将以上成绩转换为如下格式:

+-----+-----------+------|---------|
| name|   chinese | math | english |
+-----+-----------+------|---------|
|  A  |     99    |  98  |    97   |
|  B  |     92    |  91  |    90   |
|  C  |     88    |  87  |    86   |
+-----+-----------+------|---------|
-- create table
DROP TABLE if EXISTS score2;
CREATE TABLE score2
(name   VARCHAR(30) NOT NULL ,
subject VARCHAR(30) NOT NULL ,
score   INT);INSERT INTO score2 VALUES ('A', 'chinese', 99);
INSERT INTO score2 VALUES ('A', 'math',    98);
INSERT INTO score2 VALUES ('A', 'english', 97);
INSERT INTO score2 VALUES ('B', 'chinese', 92);
INSERT INTO score2 VALUES ('B', 'math',    91);
INSERT INTO score2 VALUES ('B', 'english', 90);
INSERT INTO score2 VALUES ('C', 'chinese', 88);
INSERT INTO score2 VALUES ('C', 'math',    87);
INSERT INTO score2 VALUES ('C', 'english', 86);SELECT * FROM score2;SELECT name,SUM(CASE WHEN subject = 'chinese' THEN score ELSE null END) as chinese,SUM(CASE WHEN subject = 'math'    THEN score ELSE null END) as math,SUM(CASE WHEN subject = 'english' THEN score ELSE null END) as english
FROM score2
GROUP BY name;

练习二:列转行

假设 A B C 三位小朋友期末考试成绩如下所示:

+-----+-----------+------|---------|
| name|   chinese | math | english |
+-----+-----------+------|---------|
|  A  |     99    |  98  |    97   |
|  B  |     92    |  91  |    90   |
|  C  |     88    |  87  |    86   |
+-----+-----------+------|---------|

请使用 SQL 代码将以上成绩转换为如下格式:

+-----+-----------+------|
| name|   subject |score |
+-----+-----------+------|
|  A  |  chinese  |  99  |
|  A  |  math     |  98  |
|  A  |  english  |  97  |
|  B  |  chinese  |  92  |
|  B  |  math     |  91  |
|  B  |  english  |  90  |
|  C  |  chinese  |  88  |
|  C  |  math     |  87  |
|  C  |  english  |  86  |
+-----+-----------+------|
-- create table
DROP TABLE if EXISTS score22;
CREATE TABLE score22
(name   VARCHAR(30) NOT NULL ,
chinese  INT,
math     INT,
english  INT);INSERT INTO score22 VALUES ('A', 99, 98, 97);
INSERT INTO score22 VALUES ('B', 92, 91, 90);
INSERT INTO score22 VALUES ('C', 88, 87, 86);SELECT * FROM score22;SELECT name, 'chinese' as `subject`, `chinese` as `score`
FROM score22
UNION ALL
SELECT name, 'math' as `subject`  , `math` as `score`
FROM score22
UNION ALL
SELECT name, 'english' as `subject`, `english` as `score`
FROM score22
ORDER BY name;

练习三:谁是明星带货主播?

假设,某平台2021年主播带货销售额日统计数据如下:

表名 anchor_sales

+-------------+------------+---------|
| anchor_name |     date   |  sales  |
+-------------+------------+---------|
|      A      |  20210101  |  40000  |
|      B      |  20210101  |  80000  |
|      A      |  20210102  |  10000  |
|      C      |  20210102  |  90000  |
|      A      |  20210103  |   7500  |
|      C      |  20210103  |  80000  |
+-------------+------------+---------|

定义:如果某主播的某日销售额占比达到该平台当日销售总额的 90% 及以上,则称该主播为明星主播,当天也称为明星主播日。

请使用 SQL 完成如下计算:

a. 2021年有多少个明星主播日?

b. 2021年有多少个明星主播?

-- create table
DROP TABLE if EXISTS anchor_sales;
CREATE TABLE anchor_sales
(anchor_name  VARCHAR(30) NOT NULL ,
date     DATETIME,
sales       INT);INSERT INTO anchor_sales VALUES ('A', 20210101, 40000);
INSERT INTO anchor_sales VALUES ('B', 20210101, 80000);
INSERT INTO anchor_sales VALUES ('A', 20210102, 10000);
INSERT INTO anchor_sales VALUES ('C', 20210102, 90000);
INSERT INTO anchor_sales VALUES ('A', 20210103, 7500);
INSERT INTO anchor_sales VALUES ('C', 20210103, 80000);SELECT * FROM anchor_sales;-- a
SELECT COUNT(DISTINCT anchor_name)
FROM (
SELECT * FROM (
SELECT t1.anchor_name, t1.date, t1.sales / t2.sum_sales as ratio
FROM anchor_sales t1
JOIN (SELECT date, sum(sales) AS sum_sales FROM anchor_sales GROUP BY date) t2
ON t1.date = t2.date ) t3
WHERE t3.ratio >= 0.9) t4;-- b
SELECT COUNT(DISTINCT date)
FROM (
SELECT * FROM (
SELECT t1.anchor_name, t1.date, t1.sales / t2.sum_sales as ratio
FROM anchor_sales t1
JOIN (SELECT date, sum(sales) AS sum_sales FROM anchor_sales GROUP BY date) t2
ON t1.date = t2.date ) t3
WHERE t3.ratio >= 0.9) t4;


练习四:MySQL 中如何查看sql语句的执行计划?可以看到哪些信息?

MySQL 在执行的 sql 语句之前加上 EXPLAIN 就可以看到其执行计划了。
各列的含义如下:

  • id: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
  • select_type: SELECT 查询的类型.
  • table: 查询的是哪个表
  • partitions: 匹配的分区
  • type: join 类型
  • possible_keys: 此次查询中可能选用的索引
  • key: 此次查询中确切使用到的索引.
  • ref: 哪个字段或常数与 key 一起被使用
  • rows: 显示此查询一共扫描了多少行. 这个是一个估计值.
  • filtered: 表示此查询条件所过滤的数据的百分比
  • extra: 额外的信息

重点字段介绍如下:

  • select_type:
    select_type 表示了查询的类型, 它的常用取值有:

    • SIMPLE, 表示此查询不包含 UNION 查询或子查询(最常见的查询类型)
    • PRIMARY, 表示此查询是最外层的查询
    • UNION, 表示此查询是 UNION 的第二或随后的查询
    • DEPENDENT UNION, UNION 中的第二个或后面的查询语句, 取决于外面的查询
    • UNION RESULT, UNION 的结果
    • SUBQUERY, 子查询中的第一个 SELECT
    • DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.
  • Table
    表示查询涉及的表或衍生表
  • Type
    type 字段比较重要, 它提供了判断查询是否高效的重要依据依据. 通过 type 字段, 我们判断此次查询是 全表扫描 还是 索引扫描 等。
    type 常用的取值有:

    • system
      表中只有一条数据. 这个类型是特殊的 const 类型。
    • const
      针对主键或唯一索引的等值查询扫描, 最多只返回一行数据. const 查询速度非常快, 因为它仅仅读取一次即可。
    • eq_ref
      此类型通常出现在多表的 join 查询, 表示对于前表的每一个结果, 都只能匹配到后表的一行结果. 并且查询的比较操作通常是 =, 查询效率较高
    • ref
    • 此类型通常出现在多表的 join 查询, 针对于非唯一或非主键索引, 或者是使用了 最左前缀规则索引的查询。
    • range: 表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中。当 type 是 range 时, 那么 EXPLAIN 输出的 ref 字段为 NULL, 并且 key_len 字段是此次查询中使用到的索引的最长的那个。
    • index: 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据。index 类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示 Using index。
    • ALL: 表示全表扫描, 这个类型的查询是性能最差的查询之一. 通常来说, 我们的查询不应该出现 ALL 类型的查询, 因为这样的查询在数据量大的情况下, 对数据库的性能是巨大的灾难. 如一个查询是 ALL 类型查询, 那么一般来说可以对相应的字段添加索引来避免。
      type 类型的性能比较
      通常来说, 不同的 type 类型的性能关系如下:
      ALL < index < range ~ index_merge < ref < eq_ref < const < system
      ALL 类型因为是全表扫描, 因此在相同的查询条件下, 它是速度最慢的。
      而 index 类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比 ALL 类型的稍快。
      后面的几种类型都是利用了索引来查询数据, 因此可以过滤部分或大部分数据, 因此查询效率就比较高了。
      一般来说,保证查询至少达到 range 级别,最好能达到 ref。
      练习五
      解释 ACID 之前,首先要了解一个概念,那就是 事务 ,所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。
      ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability).这是可靠数据库所应具备的几个特性。

练习五:解释一下 SQL 数据库中 ACID 是指什么

  • 原子性
    原子性是指事务是一个不可再分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性
    一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
  • 隔离性
    多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。
  • 持久性
    持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
    即使出现了任何事故比如断电等,事务一旦提交,则持久化保存在数据库中。

Section C

练习一:行转列

假设有如下比赛结果:

+--------------+-----------+
|    cdate     |   result  |
+--------------+-----------+
|  2021-01-01  |     胜    |
|  2021-01-01  |     胜    |
|  2021-01-01  |     负    |
|  2021-01-03  |     胜    |
|  2021-01-03  |     负    |
|  2021-01-03  |     负    |
+------------+-------------+

请使用 SQL 将比赛结果转换为如下形式:

+--------------+-----+-----|
|  比赛日期     | 胜  | 负  |
+--------------+-----------+
|  2021-01-01  |  2  |  1  |
|  2021-01-03  |  1  |  2  |
+------------+-----------+
-- create table
DROP TABLE if EXISTS row_col;
CREATE TABLE row_col
(cdate  DATE,
result  VARCHAR(30));INSERT INTO row_col VALUES ('2021-01-01', '胜');
INSERT INTO row_col VALUES ('2021-01-01', '胜');
INSERT INTO row_col VALUES ('2021-01-01', '负');
INSERT INTO row_col VALUES ('2021-01-03', '胜');
INSERT INTO row_col VALUES ('2021-01-03', '负');
INSERT INTO row_col VALUES ('2021-01-03', '负');SELECT * FROM row_col;SELECT cdate AS `比赛日期`,SUM(CASE WHEN result = '胜' THEN 1 ELSE 0 END ) AS `胜`,SUM(CASE WHEN result = '负' THEN 1 ELSE 0 END ) AS `负`FROM row_col
GROUP BY cdate;

练习二:列转行

假设有如下比赛结果:

+--------------+-----+-----|
|  比赛日期     | 胜  | 负  |
+--------------+-----------+
|  2021-01-01  |  4  |  1  |
|  2021-01-03  |  1  |  4  |
+------------+-----------+

请使用 SQL 将比赛结果转换为如下形式:

+--------------+-----------+
|    cdate     |   result  |
+--------------+-----------+
|  2021-01-01  |     胜    |
|  2021-01-01  |     胜    |
|  2021-01-01  |     胜    |
|  2021-01-01  |     胜    |
|  2021-01-01  |     负    |
|  2021-01-03  |     胜    |
|  2021-01-03  |     负    |
|  2021-01-03  |     负    |
|  2021-01-03  |     负    |
|  2021-01-03  |     负    |
+------------+-------------+

简单方法:

-- create table
DROP TABLE if EXISTS col_row;
CREATE TABLE col_row
(比赛日期  DATE,
胜  INT,
负  INT);INSERT INTO col_row VALUES ('2021-01-01', 4, 1);
INSERT INTO col_row VALUES ('2021-01-03', 1, 4);SELECT * FROM col_row;SELECTa.`比赛日期` AS cdate, '胜' AS result
FROMcol_row AS a
INNER JOIN mysql.help_topic AS b ON b.help_topic_id < a.`胜`
UNION ALL
SELECTa.`比赛日期` AS cdate, '负' AS result
FROMcol_row AS a
INNER JOIN mysql.help_topic AS b ON b.help_topic_id < a.`负`
ORDER BYcdate, result;

练习三:连续登录

有用户表行为记录表t_act_records表,包含两个字段:uid(用户ID),imp_date(日期)

  1. 计算2021年每个月,每个用户连续登录的最多天数
  2. 计算2021年每个月,连续2天都有登录的用户名单
  3. 计算2021年每个月,连续5天都有登录的用户数

构造表mysql如下:

DROP TABLE if EXISTS t_act_records;
CREATE TABLE t_act_records
(uid  VARCHAR(20),
imp_date DATE);INSERT INTO t_act_records VALUES('u1001', 20210101);
INSERT INTO t_act_records VALUES('u1002', 20210101);
INSERT INTO t_act_records VALUES('u1003', 20210101);
INSERT INTO t_act_records VALUES('u1003', 20210102);
INSERT INTO t_act_records VALUES('u1004', 20210101);
INSERT INTO t_act_records VALUES('u1004', 20210102);
INSERT INTO t_act_records VALUES('u1004', 20210103);
INSERT INTO t_act_records VALUES('u1004', 20210104);
INSERT INTO t_act_records VALUES('u1004', 20210105);

可以参考 MySQL 连续登录通用计算模型

-- 1. 计算2021年每个月,每个用户连续登录的最多天数
SELECT t2.uid, t2.check_period, MAX(t2.continous_days) AS max_continous_days
FROM (select t1.uid,min(t1.imp_date) `start_time`,max(t1.imp_date) `end_time`,count(1) as continous_days,month(t1.imp_date) as check_period,DATEDIFF(t1.imp_date, '2021-01-01' ) + (select count(1) from t_act_records t2 where t2.uid = t1.uid and DATEDIFF(t2.imp_date, t1.imp_date)>=0) as flagfrom t_act_records t1group by t1.uid,flag) t2
GROUP BY t2.uid, t2.check_period;

-- 2. 计算2021年每个月,连续2天都有登录的用户名单
SELECT t2.uid, t2.check_period
FROM (select t1.uid,min(t1.imp_date) `start_time`,max(t1.imp_date) `end_time`,count(1) as continous_days,month(t1.imp_date) as check_period,DATEDIFF(t1.imp_date, '2021-01-01' ) + (select count(1) from t_act_records t2 where t2.uid = t1.uid and DATEDIFF(t2.imp_date, t1.imp_date)>=0) as flagfrom t_act_records t1group by t1.uid,flag) t2
WHERE t2.continous_days >= 2;

-- 3. 计算2021年每个月,连续5天都有登录的用户数
SELECT t2.check_period, count(distinct t2.uid) AS continous_5days
FROM (select t1.uid,min(t1.imp_date) `start_time`,max(t1.imp_date) `end_time`,count(1) as continous_days,month(t1.imp_date) as check_period, DATEDIFF(t1.imp_date, '2021-01-01' ) + (select count(1) from t_act_records t2 where t2.uid = t1.uid and DATEDIFF(t2.imp_date, t1.imp_date)>=0) as flagfrom t_act_records t1group by t1.uid,flag) t2
WHERE t2.continous_days >= 5
GROUP BY t2.check_period;

练习四:用户购买商品推荐

假设现在需要根据算法给每个 user_id 推荐购买商品,推荐算法比较简单,推荐和他相似的用户购买过的 product 即可,说明如下:

  • 排除用户自己购买过的商品
  • 相似用户定义:曾经购买过 2 种或 2 种以上的相同的商品

输入表:orders

+---------+------------+
| user_id | product_id |
+---------+------------+
|     123 |          1 |
|     123 |          2 |
|     123 |          3 |
|     456 |          1 |
|     456 |          2 |
|     456 |          4 |
+---------+------------+

输出表:

+---------+------------+
| user_id | product_id |
+---------+------------+
|     123 |          4 |
|     456 |          3 |
+---------+------------+
DROP TABLE if EXISTS orders;
CREATE TABLE orders
(user_id    INT,
product_id  INT);INSERT INTO orders VALUES (123, 1);
INSERT INTO orders VALUES (123, 2);
INSERT INTO orders VALUES (123, 3);
INSERT INTO orders VALUES (456, 1);
INSERT INTO orders VALUES (456, 2);
INSERT INTO orders VALUES (456, 4);WITH t1 AS
(
SELECT a.user_id , a.product_id , b.user_id AS buid, b.product_id AS bpid
FROM orders a
LEFT JOIN orders b
ON a.product_id = b.product_id AND a.user_id <> b.user_id),
t2 AS
(
SELECT user_id, buid,count(bpid) AS bpcnt FROM t1
GROUP BY user_id, buid
HAVING bpcnt >= 2)SELECT t2.user_id, t1.product_id
FROM t2
LEFT JOIN t1
ON t2.buid = t1.user_id
WHERE t1.bpid IS NULL;

练习五:hive 数据倾斜的产生原因及优化策略?

首先,什么是数据倾斜?
数据倾斜就是:由于数据分布不均匀,造成数据大量的集中到一点,造成数据热点,常见现象是:任务进度长时间维持在 99%或者 100%的附近,查看任务监控页面,发现只有少量 reduce 子任务未完成,因为其处理的数据量和其他的 reduce 差异过大。 单一 reduce 处理的记录数和平均记录数相差太大,通常达到好几倍之多,最长时间远大 于平均时长。

Hive 数据倾斜产生的原因主要有如下几条:
关键词
场景
影响后果

JOIN
其中一个表较小,但是 key 比较集中
分发到一个或几个 Reduce 上的数据远高于平均值
大表与大表,但是分桶的判断字段中,0值或控制过多
这些空值都有一个 Reduce 处理,导致该 Reduce 执行的非常慢
GROUP BY
维多不均衡,某值的数量过多
处理某值的 Reduce 非常耗时
Count distinct
某特殊值过多
处理此特殊值的 Reduce 非常耗时

优化策略:

  • 空值产生的数据倾斜
    策略1:为空的列值不参与关联
select * from log a join user b on a.user_id is not null and a.user_id = b.user_id
union all
select * from log c where c.user_id is null;

策略2:赋予空值新的 key 值

select * from log a left outer join user b on
case when a.user_id is null then concat('hive',rand()) else a.user_id end = b.user_id
  • 不同数据类型产生的数据倾斜
    把数字类型 id 转换成 string 类型的 id
select * from user a left outer join log b on b.user_id = cast(a.user_id as string)
  • 大小表关联产生的数据倾斜
    使用map join解决小表关联大表造成的数据倾斜问题。(这个方法使用的频率很高。)
    map join 概念:将其中做连接的小表(全量数据)分发到所有 MapTask 端进行 Join,从 而避免了 reduceTask,前提要求是内存足以装下该全量数据。
    MapJoin 具体用法:
select /* +mapjoin(a) */ a.id aid, name, age from a join b on a.id = b.id;select /* +mapjoin(movies) */ a.title, b.rating from movies a join ratings b on a.movieid =
b.movieid;

在 hive0.11 版本以后会自动开启 map join 优化,由两个参数控制:
set hive.auto.convert.join=true; //设置 MapJoin 优化自动开启
set hive.mapjoin.smalltable.filesize=25000000 //设置小表不超过多大时开启 mapjoin 优化
如果是大大表关联呢?那就大事化小,小事化了。把大表切分成小表,然后分别 map join
那么如果小表不大不小,那该如何处理呢???
使用 map join 解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用的频率非常 高,但如果小表很大,大到 map join 会出现 bug 或异常,这时就需要特别的处理。

练习六:LEFT JOIN 是否可能会出现多出的行?为什么?

假设 t1 表有6行(关联列 name 有2行为空),t2 表有6行(关联列 name 有3行为空),

那么 SELECT * FROM t1 LEFT JOIN t2 on t1.name = t2.name 会返回多少行结果?

可以参考下图

10 行结果。
因为 t1 表的 2 个空行和 t2 表的 3 个空行进行叉乘,得到了 6 行,加上 t1 表中 name 非空的 4 行,最终得到 10 行。(在 on 条件中 ,比较运算符 = 两边的值都是 NULL 时,返回 true,所以会变为 6 行。

学习参考

学习参考:https://github.com/datawhalechina/wonderful-sql

SQL学习笔记6-决胜秋招相关推荐

  1. 判断题:oracle自带的sql语言环境是pl/sql,Oracle之PL/SQL学习笔记之数据类型(三)

    Oracle之PL/SQL学习笔记之数据类型(三) 所有的编程语言中变量是使用最频繁的.PL/SQL作为一个面向过程的数据库编程语言同样少不了变量,利用变量可以把PL/SQL块需要的参数传递进来,做到 ...

  2. Interview:算法岗位面试—2019秋招校园招聘—算法工程师【机器学习、深度学习(偏图像)】秋招感悟:初期阶段的傲娇→中期阶段的紧张→后期阶段的蜕变

    ML岗位面试:2019秋招&校园招聘-算法工程师[机器学习.深度学习(偏图像)]秋招感悟:初期阶段的傲娇→中期阶段的紧张→后期阶段的蜕变 Interview:算法岗位面试-2019秋招& ...

  3. Spark学习笔记(7)---Spark SQL学习笔记

    Spark SQL学习笔记 Spark SQL学习笔记设计到很多代码操作,所以就放在github, https://github.com/yangtong123/RoadOfStudySpark/bl ...

  4. Oracle之PL/SQL学习笔记之有名块练习

    2019独角兽企业重金招聘Python工程师标准>>> Oracle之PL/SQL学习笔记之有名块练习 存储过程案例: 案例1: 根据雇员姓名跟新雇员工资,如果雇员不存在输出没有该雇 ...

  5. 【LittleXi】sql学习笔记

    [LittleXi]sql学习笔记 数据类型 INT 整数型 VARCHAR 字符串 DECIMAL(a,b) 浮点型(a代表位数,b代表小数位位数) BLOB 图片.影片.档案 DATA 日期 XX ...

  6. 探针一号的SQL学习笔记

    SQL学习笔记 文章目录 SQL学习笔记 1.问题 2.SQL基本语句 3.SQL基本数据查询 4.SQL复杂点的数据查询 5.DML操作 6.数据表操作 1.问题 什么是数据库? 是一个仓库,可以按 ...

  7. SQL学习笔记_Aliyun4

    SQL学习笔记_Aliyun4 本笔记为阿里云天池龙珠计划SQL训练营的学习内容,链接为:https://tianchi.aliyun.com/specials/promotion/aicampsql ...

  8. 简简单单 My SQL 学习笔记(2)——分组和简单数据的查询

    初始数据 创建表(要记得先选择好我们的数据库 use+数据库名) create table student( studentno int(4) primary key not null auto_in ...

  9. SQL学习笔记之二:QUOTENAME函数

    SQL学习笔记之二:QUOTENAME函数 --SQL学习笔记二 --函数QUOTENAME --功能:返回带有分隔符的Unicode 字符串,分隔符的加入可使输入的字符串成为有效的Microsoft ...

最新文章

  1. 数据蒋堂 | 大数据计算语法的SQL化
  2. 动态规划——方格取数(hdu1565)
  3. linux怎么判断全局符号,Linux下全局符号覆盖有关问题
  4. gitlab+jenkins+ansible集成持续发布
  5. BZOJ1934: [Shoi2007]Vote 善意的投票
  6. 智能家居通信协议科普,什么户型选择什么产品一文看懂
  7. 【Python入门教程】第04篇 Hello World程序
  8. 51单片机用PID算法温度控制器毕业设计 完整资料,Matlab作图仿真源码
  9. Unity HTC vive移动定位器的开发使用
  10. 数据中台与数据湖概念认知
  11. 水果店如何写文案,做水果店发的文案
  12. PHP接入微信官方支付(native·APIv3)
  13. html中右侧三角形代码,纯CSS绘制三角形(各种角度)
  14. STM32F4xx FPU和DSP库的使用
  15. 去年一个百万级的小软件项目经验分享,20来个功能模块,项目不太好做有些棘手
  16. 软件需求工程 高校教学平台 需求工程计划
  17. [Codevs] 一塔湖图
  18. 【java】在线支付
  19. word 的使用(七) —— 绘图工具
  20. 张钹院士:人工智能独角兽为何不赚钱

热门文章

  1. 查询省会python
  2. jadx工具介绍及使用
  3. 屏蔽csdn百度推广广告
  4. 日历控件(bootstrap-datetimepicker.js)
  5. STM32CubeIDE配置使用
  6. 世界七大数学难题——千年大奖问题(转载)
  7. Matplotlib 中等高线图(contour)的绘制
  8. 硬盘柱面损坏怎么办_硬盘0柱面损坏数据恢复(老牌数据恢复)
  9. 温莎大学的计算机科学,温莎大学荣誉计算机科学专业本科.pdf
  10. 并发编程之 ThreadLocal 源码剖析