Compare commits
	
		
			2 Commits
		
	
	
		
			1ed786139f
			...
			cb02a70fcf
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						cb02a70fcf
	
				 | 
					
					
						|||
| 
						
						
							
						
						98b3779e63
	
				 | 
					
					
						
							
								
								
									
										458
									
								
								SQL-窗口函数.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										458
									
								
								SQL-窗口函数.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,458 @@
 | 
			
		||||
# SQL窗口函数
 | 
			
		||||
 | 
			
		||||
### 一. 什么是窗口函数
 | 
			
		||||
 | 
			
		||||
#### 基本含义
 | 
			
		||||
 | 
			
		||||
窗口限定一个范围,它可以理解为满足某些条件的记录集合,窗口函数也就是在窗口范围内执行的函数。
 | 
			
		||||
 | 
			
		||||
#### 基本语法
 | 
			
		||||
 | 
			
		||||
窗口函数有over关键字,指定函数执行的范围,可分为三部分:分组子句(partition by),排序子句(order by),窗口子句(rows)
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
<函数名> over (partition by <分组的列> order by <排序的列> rows between <起始行> and <终止行>)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**注意Mysql8才支持窗口函数**
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 演示表格
 | 
			
		||||
 | 
			
		||||
| cid(班级id) | sname(学生姓名) | score(分数) |
 | 
			
		||||
| ------------ | ----------------- | ------------- |
 | 
			
		||||
| 001          | 张三              | 78            |
 | 
			
		||||
| 001          | 李四              | 82            |
 | 
			
		||||
| 002          | 小明              | 90            |
 | 
			
		||||
| 001          | 王五              | 67            |
 | 
			
		||||
| 002          | 小红              | 85            |
 | 
			
		||||
| 002          | 小刚              | 62            |
 | 
			
		||||
 | 
			
		||||
#### 演示脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
CREATE TABLE SQL_5 (
 | 
			
		||||
       cid varchar(4),
 | 
			
		||||
       sname varchar(4),
 | 
			
		||||
       score int
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
insert into SQL_5 (cid, sname, score) values ('001', '张三', 78);
 | 
			
		||||
insert into SQL_5 (cid, sname, score) values ('001', '李四', 82);
 | 
			
		||||
insert into SQL_5 (cid, sname, score) values ('002', '小明', 90);
 | 
			
		||||
insert into SQL_5 (cid, sname, score) values ('001', '王五', 67);
 | 
			
		||||
insert into SQL_5 (cid, sname, score) values ('002', '小红', 85);
 | 
			
		||||
insert into SQL_5 (cid, sname, score) values ('002', '小刚', 62);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 二. 窗口的确定
 | 
			
		||||
 | 
			
		||||
例子:
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select *, sum(score) over (partition by cid order by score rows between unbounded preceding and unbounded following) as '班级总分' from SQL_5;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 分组子句(partition by)
 | 
			
		||||
 | 
			
		||||
不分组可以写成partition by null或者直接不写
 | 
			
		||||
 | 
			
		||||
后面可以跟多个列, 如 partition by cid, sname
 | 
			
		||||
 | 
			
		||||
**注意 partition by与group by的区别**
 | 
			
		||||
 | 
			
		||||
1)前者不会压缩行数但是后者会
 | 
			
		||||
 | 
			
		||||
2)后者只能选取分组的列和聚合的列
 | 
			
		||||
 | 
			
		||||
也就是说group by 后生成的结果集与原表的行数和列数都不同
 | 
			
		||||
 | 
			
		||||
#### 排序子句(order by)
 | 
			
		||||
 | 
			
		||||
不排序可以写成order by null 或者直接不写
 | 
			
		||||
 | 
			
		||||
asc或不写表示升序,desc表示降序
 | 
			
		||||
 | 
			
		||||
后面可以跟多个列, 如 order by cid, sname
 | 
			
		||||
 | 
			
		||||
#### 窗口子句(rows)
 | 
			
		||||
 | 
			
		||||
窗口子句的描述
 | 
			
		||||
 | 
			
		||||
1) 起始行:   N preceding/unbounded preceding
 | 
			
		||||
 | 
			
		||||
2) 当前行:   current row
 | 
			
		||||
 | 
			
		||||
3) 终止行:   N following/unbounded following
 | 
			
		||||
 | 
			
		||||
举例:
 | 
			
		||||
 | 
			
		||||
rows between unbounded  preceding and current row  从之前所有的行到当前行
 | 
			
		||||
 | 
			
		||||
rows between 2 preceding and current row  从前面两行到当前行
 | 
			
		||||
 | 
			
		||||
rows between current row and unbounded following  从当前行到之后所有的行
 | 
			
		||||
 | 
			
		||||
rows between current row and 1following  从当前行到后面一行
 | 
			
		||||
 | 
			
		||||
**注意:**
 | 
			
		||||
 | 
			
		||||
**排序子句后面缺少窗口子句,窗口规范默认是 rows between unbounded preceding and current row**
 | 
			
		||||
 | 
			
		||||
**排序子句和窗口子句都缺失,窗口规范默认是 rows between unbounded preceding and unbounded following**
 | 
			
		||||
 | 
			
		||||
#### 总体流程
 | 
			
		||||
 | 
			
		||||
1) 通过partition by 和 order by 子句确定大窗口( 定义出上界unbounded preceding和下界unbounded following)
 | 
			
		||||
 | 
			
		||||
2) 通过row 子句针对每一行数据确定小窗口(滑动窗口)
 | 
			
		||||
 | 
			
		||||
3) 对每行的小窗口内的数据执行函数并生成新的列
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 三. 函数分类
 | 
			
		||||
 | 
			
		||||
#### 排序类
 | 
			
		||||
 | 
			
		||||
rank, dense_rank, row_number
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
-- 【排序类】
 | 
			
		||||
-- 按班级分组后打上序号 不考虑并列
 | 
			
		||||
select *, row_number() over (partition by cid order by score desc) as '不可并列排名' from SQL_5;
 | 
			
		||||
-- 按班级分组后作跳跃排名 考虑并列
 | 
			
		||||
select *, rank() over (partition by cid order by score desc) as '跳跃可并列排名' from SQL_5;
 | 
			
		||||
-- 按班级分组后作连续排名 考虑并列
 | 
			
		||||
select *, dense_rank() over (partition by cid order by score desc) as '连续可并列排名' from SQL_5;
 | 
			
		||||
-- 合并起来对比
 | 
			
		||||
select *, row_number() over (partition by cid order by score desc) as '不可并列排名' ,
 | 
			
		||||
          rank() over (partition by cid order by score desc) as '跳跃可并列排名',
 | 
			
		||||
          dense_rank() over (partition by cid order by score desc) as '连续可并列排名'
 | 
			
		||||
from SQL_5;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 聚合类
 | 
			
		||||
 | 
			
		||||
sum. avg, count, max, min
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
-- 【聚合类】
 | 
			
		||||
-- 让同一班级每个学生都知道班级总分是多少
 | 
			
		||||
select *, sum(score) over (partition by cid) as '班级总分' from SQL_5;
 | 
			
		||||
-- 或者可以写成
 | 
			
		||||
select *, sum(score) over (partition by cid rows between unbounded preceding and unbounded following) as '班级总分' from SQL_5;
 | 
			
		||||
 | 
			
		||||
-- 计算同一班级,每个同学和比他分数低的同学的累计总分是多少
 | 
			
		||||
select *, sum(score) over (partition by cid order by score) '累加分数' from SQL_5;
 | 
			
		||||
-- 或者可以写成 其中rows between ... and 是规定窗口大小
 | 
			
		||||
select *, sum(score) over (partition by cid order by score rows between unbounded preceding and current row) as '累加分数' from SQL_5;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 跨行类
 | 
			
		||||
 | 
			
		||||
lag, lead
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
-- 【跨行类】
 | 
			
		||||
-- lag/lead 函数 参数1:比较的列 参数2: 偏移量 参数3:找不到的默认值
 | 
			
		||||
-- 同一班级内,成绩比自己低一名的分数是多少
 | 
			
		||||
select *, lag(score, 1) over (partition by cid order by score) as '低一名的分数' from SQL_5;
 | 
			
		||||
-- 或者写成
 | 
			
		||||
select *, lag(score, 1, 0) over (partition by cid order by score) as '低一名的分数' from SQL_5;
 | 
			
		||||
 | 
			
		||||
-- 同一班级内,成绩比自己高2名的分数是多少
 | 
			
		||||
select *, lead(score, 2) over (partition by cid order by score) as '高两名的分数' from SQL_5;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 四. 相关题目
 | 
			
		||||
 | 
			
		||||
#### 表格
 | 
			
		||||
 | 
			
		||||
| cid  | sname | course | score |
 | 
			
		||||
| ---- | ----- | ------ | ----- |
 | 
			
		||||
| 001  | 张三  | 语文   | 78    |
 | 
			
		||||
| 002  | 小刚  | 语文   | 71    |
 | 
			
		||||
| 001  | 李四  | 数学   | 56    |
 | 
			
		||||
| 002  | 小明  | 数学   | 54    |
 | 
			
		||||
| ...  | ...   | ...    | ...   |
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
CREATE TABLE SQL_6 (
 | 
			
		||||
       cid varchar(4),
 | 
			
		||||
       sname varchar(4),
 | 
			
		||||
       course varchar(10),
 | 
			
		||||
       score int
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '张三', '语文', 78);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小刚', '语文', 71);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '李四', '数学', 56);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '王五', '数学', 97);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小明', '数学', 54);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小刚', '数学', 67);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小红', '数学', 82);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '王五', '语文', 80);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '张三', '数学', 77);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小明', '语文', 58);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小红', '语文', 87);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '李四', '语文', 60);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '张三', '英语', 66);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小刚', '英语', 50);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '李四', '地理', 59);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '王五', '地理', 88);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小明', '地理', 45);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小刚', '地理', 66);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小红', '地理', 82);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '王五', '英语', 81);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '张三', '地理', 77);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小明', '英语', 55);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('002', '小红', '英语', 87);
 | 
			
		||||
insert into SQL_6 (cid, sname, course, score) values ('001', '李四', '英语', 61);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
##### 分组内topN
 | 
			
		||||
 | 
			
		||||
问题1:求出每个学生成绩最高的三条记录
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select * from 
 | 
			
		||||
(
 | 
			
		||||
    select *, row_number() over (partition by sname order by score desc) as rn from SQL_6   
 | 
			
		||||
) temp
 | 
			
		||||
where rn <= 3
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
###### 公式:
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select * from
 | 
			
		||||
(
 | 
			
		||||
	select *, row_number() over (partition by 分组列 order by 比较列) as rn from table
 | 
			
		||||
) as tmp
 | 
			
		||||
where rn <= N;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##### 汇总分析
 | 
			
		||||
 | 
			
		||||
问题2:找出每门课程都高于班级课程平均分的学生
 | 
			
		||||
 | 
			
		||||
可以拆解成以下几个问题:
 | 
			
		||||
 | 
			
		||||
1)求出每个班级,每门课程的平均分
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
with
 | 
			
		||||
-- 1) 求出每个班级,每门课程的平均分
 | 
			
		||||
t1 as
 | 
			
		||||
(
 | 
			
		||||
select *,
 | 
			
		||||
       avg(score) over (partition by cid, course) as 'avg'
 | 
			
		||||
from SQL_6
 | 
			
		||||
),
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2)将学生每门课程的成绩与所在班级的对应课程平均分相减,结果大于0就说明该学生的这门成绩高于课程平均分
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
t2 as (
 | 
			
		||||
    select *,
 | 
			
		||||
           score - avg  as 'del'
 | 
			
		||||
    from t1
 | 
			
		||||
)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3)“找出每门课程都高于班级课程平均分的学生”说明对于学生来说,最小的“相减结果”都是大于0的
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select sname from t2
 | 
			
		||||
group by sname
 | 
			
		||||
having min(del) > 0;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
合并后的SQL语句
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
with
 | 
			
		||||
t1 as
 | 
			
		||||
(
 | 
			
		||||
select *,
 | 
			
		||||
       avg(score) over (partition by cid, course) as 'avg'
 | 
			
		||||
from SQL_6
 | 
			
		||||
),
 | 
			
		||||
t2 as (
 | 
			
		||||
    select *,
 | 
			
		||||
           score - avg  as 'del'
 | 
			
		||||
    from t1
 | 
			
		||||
)
 | 
			
		||||
select sname from t2
 | 
			
		||||
group by sname
 | 
			
		||||
having min(del) > 0;
 | 
			
		||||
 | 
			
		||||
-- 或者
 | 
			
		||||
select sname from (
 | 
			
		||||
                      select *,
 | 
			
		||||
                             score - avg  as 'del'
 | 
			
		||||
                      from (
 | 
			
		||||
                               select *,
 | 
			
		||||
                                      avg(score) over (partition by cid, course) as 'avg'
 | 
			
		||||
                               from SQL_6
 | 
			
		||||
                           ) t1
 | 
			
		||||
                  ) t2
 | 
			
		||||
group by sname
 | 
			
		||||
having min(del) > 0;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 表格
 | 
			
		||||
 | 
			
		||||
| empno | ename | hire_date  | salary | dept_no |
 | 
			
		||||
| ----- | ----- | ---------- | ------ | ------- |
 | 
			
		||||
| 001   | Adam  | 2018-03-01 | 1000   | A       |
 | 
			
		||||
| 002   | Bill  | 2021-03-01 | 1200   | A       |
 | 
			
		||||
| 003   | Cindy | 2016-03-01 | 1500   | A       |
 | 
			
		||||
| 004   | Danny | 2020-03-01 | 5000   | A       |
 | 
			
		||||
| 005   | Eason | 2020-03-01 | 4000   | B       |
 | 
			
		||||
| 006   | Fred  | 2018-03-01 | 3500   | B       |
 | 
			
		||||
| 007   | Gary  | 2017-03-01 | 1800   | B       |
 | 
			
		||||
| 008   | Hugo  | 2020-03-01 | 2500   | B       |
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
CREATE TABLE SQL_7 (
 | 
			
		||||
       empno varchar(4),
 | 
			
		||||
       ename varchar(10),
 | 
			
		||||
       hire_date varchar(10),
 | 
			
		||||
       salary int,
 | 
			
		||||
       dept_no varchar(2)
 | 
			
		||||
);
 | 
			
		||||
insert into SQL_7 (empno, ename, hire_date, salary, dept_no) values ('001', 'Adam', '2018-03-01', 1000, 'A');
 | 
			
		||||
insert into SQL_7 (empno, ename, hire_date, salary, dept_no) values ('002', 'Bill', '2021-03-01', 1200, 'A');
 | 
			
		||||
insert into SQL_7 (empno, ename, hire_date, salary, dept_no) values ('003', 'Cindy', '2016-03-01', 1500, 'A');
 | 
			
		||||
insert into SQL_7 (empno, ename, hire_date, salary, dept_no) values ('004', 'Danny', '2020-03-01', 5000, 'A');
 | 
			
		||||
insert into SQL_7 (empno, ename, hire_date, salary, dept_no) values ('005', 'Eason', '2020-03-01', 4000, 'B');
 | 
			
		||||
insert into SQL_7 (empno, ename, hire_date, salary, dept_no) values ('006', 'Fred', '2018-03-01', 3500, 'B');
 | 
			
		||||
insert into SQL_7 (empno, ename, hire_date, salary, dept_no) values ('007', 'Gary', '2017-03-01', 1800, 'B');
 | 
			
		||||
insert into SQL_7 (empno, ename, hire_date, salary, dept_no) values ('008', 'Hugo', '2020-03-01', 4500, 'B');
 | 
			
		||||
 | 
			
		||||
select * from SQL_7;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
##### 分组内topN
 | 
			
		||||
 | 
			
		||||
问题一:求出每个部门工资最高的前三名员工
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select * from
 | 
			
		||||
    (
 | 
			
		||||
        select *, row_number() over (partition by dept_no order by salary desc) as rn from SQL_7
 | 
			
		||||
    ) as tmp
 | 
			
		||||
where rn <= 3;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##### 汇总分析
 | 
			
		||||
 | 
			
		||||
问题二:计算这些员工的工资占所属部门总工资的百分比
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
with
 | 
			
		||||
t1 as (
 | 
			
		||||
    select * , sum(salary) over (partition by dept_no) as 'sum_sal' from SQL_7
 | 
			
		||||
),
 | 
			
		||||
t2 as (
 | 
			
		||||
    select *, round(salary*100/sum_sal,2) as 'percentage' from t1
 | 
			
		||||
)
 | 
			
		||||
select * from t2;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
问题三:对各部门员工的工资进行从小到大排序,排名前30%为低层,30%-80%为中层,高于80%为高层,并打上标签
 | 
			
		||||
 | 
			
		||||
```label
 | 
			
		||||
with
 | 
			
		||||
    t1 as (
 | 
			
		||||
        select * , row_number() over (partition by dept_no order by salary) as cnt,
 | 
			
		||||
               count(empno) over (partition by dept_no) as 'sum' from SQL_7
 | 
			
		||||
    ),
 | 
			
		||||
    t2 as (
 | 
			
		||||
        select *, round(cnt/sum,2) as 'percentage' from t1
 | 
			
		||||
    ),
 | 
			
		||||
    t3 as (
 | 
			
		||||
        select *, case when percentage <= 0.3 then '低层'
 | 
			
		||||
                       when percentage <= 0.8 then '中层'
 | 
			
		||||
                       when percentage <= 1 then '高层' end as 'label'
 | 
			
		||||
        from t2
 | 
			
		||||
    )
 | 
			
		||||
select empno, ename, hire_date, salary, dept_no, label from t3;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
问题四:统计每年入职总数以及截至本年累计入职总人数(本年总入职人数 + 本年之前所有年的总入职人数之和)
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select year(hire_date) as hire_year, count(empno) as cnt
 | 
			
		||||
    from SQL_7
 | 
			
		||||
    group by year(hire_date) order by hire_year;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
with t1 as (
 | 
			
		||||
    select year(hire_date) as hire_year, count(empno) as cnt from SQL_7 group by year(hire_date) order by hire_year
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
select *, sum(cnt) over(partition by null rows between unbounded preceding and current row) as sum from t1;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 五. 技巧
 | 
			
		||||
 | 
			
		||||
1)分组内topN公式
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select * from
 | 
			
		||||
(
 | 
			
		||||
	select *, row_number() over (partition by 分组列 order by 比较列) as rn from table
 | 
			
		||||
) as tmp
 | 
			
		||||
where rn <= N;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2) 窗口函数 -> 生成辅助列(相当于高级语言的临时变量)
 | 
			
		||||
 | 
			
		||||
3)   with 语句 -> 生成临时表(相当于高级语言的局部方法)
 | 
			
		||||
 | 
			
		||||
       把复杂的问题拆分成多个子问题并用临时表去表达
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										324
									
								
								SQL-行转列与列转行.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								SQL-行转列与列转行.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,324 @@
 | 
			
		||||
# SQL 讲解 —— 行转列 与 列转行
 | 
			
		||||
 | 
			
		||||
## 行转列
 | 
			
		||||
 | 
			
		||||
### 题目1
 | 
			
		||||
 | 
			
		||||
#### 描述
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
name  subject  score
 | 
			
		||||
张三    语文     78
 | 
			
		||||
张三    数学     88
 | 
			
		||||
张三    英语 	 98
 | 
			
		||||
李四    语文     89
 | 
			
		||||
李四    数学     76
 | 
			
		||||
李四    英语     90
 | 
			
		||||
王五    语文	 99
 | 
			
		||||
王五	  数学	 66
 | 
			
		||||
王五	  英语	 91
 | 
			
		||||
 | 
			
		||||
name  语文  数学  英语
 | 
			
		||||
张三   78   88    98
 | 
			
		||||
李四   89   76    90
 | 
			
		||||
王五   99   66    91
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
create table SQL_1
 | 
			
		||||
(
 | 
			
		||||
    name varchar(20),
 | 
			
		||||
    subject varchar(20),
 | 
			
		||||
    score float
 | 
			
		||||
);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('张三', '语文', 78);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('张三', '数学', 88);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('张三', '英语', 98);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('李四', '语文', 89);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('李四', '数学', 76);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('李四', '英语', 90);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('王五', '语文', 99);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('王五', '数学', 66);
 | 
			
		||||
insert into SQL_1 (name, subject, score) values ('王五', '英语', 91);
 | 
			
		||||
select * from SQL_1;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 解题步骤
 | 
			
		||||
 | 
			
		||||
1) 确定分组列,转换列,数据列
 | 
			
		||||
 | 
			
		||||
2) 生成伪列
 | 
			
		||||
 | 
			
		||||
3) 做分组查询
 | 
			
		||||
 | 
			
		||||
4) 选择合适的聚合函数
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 两步法
 | 
			
		||||
 | 
			
		||||
##### 公式:
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select 分组列,
 | 
			
		||||
       聚合函数(m1) as 列名1,
 | 
			
		||||
       聚合函数(m2) as 列名2,
 | 
			
		||||
       聚合函数(m3) as 列名3
 | 
			
		||||
from (select *,
 | 
			
		||||
             case 转换列 when 转换列值1 then 数据列 else ... end as m1,
 | 
			
		||||
             case 转换列 when 转换列值2 then 数据列 else ... end as m2,
 | 
			
		||||
             case 转换列 when 转换列值3 then 数据列 else ... end as m3
 | 
			
		||||
      from 表名) 临时表名
 | 
			
		||||
group by 分组列;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
##### 解题SQL
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select name,
 | 
			
		||||
       sum(m1) as 语文,
 | 
			
		||||
       sum(m2) as 数学,
 | 
			
		||||
       sum(m3) as 英语
 | 
			
		||||
from (select *,
 | 
			
		||||
             case subject when '语文' then score else 0 end as m1,
 | 
			
		||||
             case subject when '数学' then score else 0 end as m2,
 | 
			
		||||
             case subject when '英语' then score else 0 end as m3
 | 
			
		||||
      from sql_1) tmp
 | 
			
		||||
group by name;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 一步法
 | 
			
		||||
 | 
			
		||||
##### 公式:
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select 分组列,
 | 
			
		||||
       聚合函数(case 转换列 when 转换列值1 then 数据列 else ... end) as 列名1,
 | 
			
		||||
	   聚合函数(case 转换列 when 转换列值2 then 数据列 else ... end) as 列名2,
 | 
			
		||||
	   聚合函数(case 转换列 when 转换列值3 then 数据列 else ... end) as 列名3
 | 
			
		||||
	   ...
 | 
			
		||||
from 表名
 | 
			
		||||
group by 分组列;
 | 
			
		||||
 | 
			
		||||
select 分组列,
 | 
			
		||||
       聚合函数(case when 转换列=转换列值1 then 数据列 else ... end) as 列名1,
 | 
			
		||||
	   聚合函数(case when 转换列=转换列值2 then 数据列 else ... end) as 列名2,
 | 
			
		||||
	   聚合函数(case when 转换列=转换列值3 then 数据列 else ... end) as 列名3
 | 
			
		||||
	   ...
 | 
			
		||||
from 表名
 | 
			
		||||
group by 分组列;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
##### 解题SQL
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select name,
 | 
			
		||||
       sum(case subject when '语文' then score else 0 end) as 语文,
 | 
			
		||||
       sum(case subject when '数学' then score else 0 end) as 数学,
 | 
			
		||||
       sum(case subject when '英语' then score else 0 end) as 英语
 | 
			
		||||
from sql_1
 | 
			
		||||
group by name;
 | 
			
		||||
 | 
			
		||||
select name,
 | 
			
		||||
       sum(case when subject = '语文' then score else 0 end) as 语文,
 | 
			
		||||
       sum(case when subject = '数学' then score else 0 end) as 数学,
 | 
			
		||||
       sum(case when subject = '英语' then score else 0 end) as 英语
 | 
			
		||||
from sql_1
 | 
			
		||||
group by name;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 题目2
 | 
			
		||||
 | 
			
		||||
#### 描述
 | 
			
		||||
 | 
			
		||||
```txt
 | 
			
		||||
# 日期        结果
 | 
			
		||||
# 2022-01-01  胜
 | 
			
		||||
# 2022-01-01  胜
 | 
			
		||||
# 2022-01-02  负
 | 
			
		||||
# 2022-01-02  负
 | 
			
		||||
# 2022-01-01  负
 | 
			
		||||
# 2022-01-02  负
 | 
			
		||||
# 2022-01-02  胜
 | 
			
		||||
 | 
			
		||||
# 日期         胜 负
 | 
			
		||||
# 2022-01-01  2  1
 | 
			
		||||
# 2022-01-02  1  3
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
create table SQL_2(
 | 
			
		||||
   ddate varchar(10), result varchar(2)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
insert into SQL_2 (ddate, result) values('2022-01-01','胜');
 | 
			
		||||
insert into SQL_2 (ddate, result) values('2022-01-01','胜');
 | 
			
		||||
insert into SQL_2 (ddate, result) values('2022-01-02','负');
 | 
			
		||||
insert into SQL_2 (ddate, result) values('2022-01-02','负');
 | 
			
		||||
insert into SQL_2 (ddate, result) values('2022-01-01','负');
 | 
			
		||||
insert into SQL_2 (ddate, result) values('2022-01-02','负');
 | 
			
		||||
insert into SQL_2 (ddate, result) values('2022-01-02','胜');
 | 
			
		||||
select * from SQL_2;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 解题SQL
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
select ddate,
 | 
			
		||||
       sum(case when result = '胜' then 1 else 0 end) as 胜,
 | 
			
		||||
       sum(case when result = '负' then 1 else 0 end) as 负
 | 
			
		||||
from sql_2
 | 
			
		||||
group by ddate;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 列转行
 | 
			
		||||
 | 
			
		||||
### 题目3
 | 
			
		||||
 | 
			
		||||
#### 描述
 | 
			
		||||
 | 
			
		||||
```txt
 | 
			
		||||
name  语文  数学  英语 
 | 
			
		||||
张三   78   88    98
 | 
			
		||||
李四   89   76    90
 | 
			
		||||
王五   99   66    91 
 | 
			
		||||
 | 
			
		||||
name  subject  score
 | 
			
		||||
张三    语文     78
 | 
			
		||||
张三    数学     88
 | 
			
		||||
张三    英语 	 98
 | 
			
		||||
李四    语文     89
 | 
			
		||||
李四    数学     76
 | 
			
		||||
李四    英语     90
 | 
			
		||||
王五    语文	 99
 | 
			
		||||
王五	  数学	 66
 | 
			
		||||
王五	  英语	 91
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
CREATE TABLE SQL_3 (
 | 
			
		||||
	name varchar(20),
 | 
			
		||||
    语文 float,
 | 
			
		||||
    数学 float,
 | 
			
		||||
    英语 float
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
insert into SQL_3 (name, '语文', '数学', '英语') values ('张三', 78, 88, 98);
 | 
			
		||||
insert into SQL_3 (name, '语文', '数学', '英语') values ('李四', 89, 76, 90);
 | 
			
		||||
insert into SQL_3 (name, '语文', '数学', '英语') values ('王五', 99, 66, 91);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 解题步骤
 | 
			
		||||
 | 
			
		||||
1) 确定转换列,非转换列
 | 
			
		||||
 | 
			
		||||
2) 生成新列
 | 
			
		||||
 | 
			
		||||
3) 使用union或union all 进行合并
 | 
			
		||||
 | 
			
		||||
4) 根据需要进行order by
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 公式
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
SELECT 非转换列, '转换列1' AS 新转换列名, 转换列1 AS 新数据列名 FROM 表名
 | 
			
		||||
UNION ALL
 | 
			
		||||
SELECT 非转换列, '转换列2' AS 新转换列名, 转换列2 AS 新数据列名 FROM 表名
 | 
			
		||||
UNION ALL
 | 
			
		||||
SELECT 非转换列, '转换列3' AS 新转换列名, 转换列3 AS 新数据列名 FROM 表名
 | 
			
		||||
ORDER BY ...;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 解题SQL
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
SELECT name,'语文' AS subject,语文 AS score FROM SQL_3
 | 
			
		||||
UNION ALL
 | 
			
		||||
SELECT name,'数学' AS subject,数学 AS score FROM SQL_3
 | 
			
		||||
UNION ALL
 | 
			
		||||
SELECT name,'英语' AS subject,英语 AS score FROM SQL_3
 | 
			
		||||
ORDER BY name ASC, subject DESC;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 题目4
 | 
			
		||||
 | 
			
		||||
#### 描述
 | 
			
		||||
 | 
			
		||||
```txt
 | 
			
		||||
Q1     Q2     Q3     Q4
 | 
			
		||||
1000   2000   3000   4000
 | 
			
		||||
 | 
			
		||||
季度   业绩
 | 
			
		||||
Q1    1000
 | 
			
		||||
Q2    2000
 | 
			
		||||
Q3    3000
 | 
			
		||||
Q4    4000
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
CREATE TABLE SQL_4 (
 | 
			
		||||
	Q1 int, Q2 int, Q3 int, Q4 int
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
insert into SQL_4 values (1000, 2000, 3000, 4000);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 解题SQL
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
SELECT  'Q1' AS 季度, Q1 AS 业绩 FROM SQL_4
 | 
			
		||||
UNION ALL
 | 
			
		||||
SELECT 'Q2' AS 季度, Q2 AS 业绩 FROM SQL_4
 | 
			
		||||
UNION ALL
 | 
			
		||||
SELECT 'Q3' AS 季度, Q3 AS 业绩 FROM SQL_4
 | 
			
		||||
UNION ALL
 | 
			
		||||
SELECT 'Q4' AS 季度, Q4 AS 业绩 FROM SQL_4
 | 
			
		||||
ORDER BY 季度;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 技巧:
 | 
			
		||||
 | 
			
		||||
扩展列:select ... as 新列名
 | 
			
		||||
 | 
			
		||||
减少列:直接不写
 | 
			
		||||
 | 
			
		||||
扩展行:union/ union all
 | 
			
		||||
 | 
			
		||||
减少行:  聚合函数
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										231
									
								
								SQL窗口函数(二).md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								SQL窗口函数(二).md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,231 @@
 | 
			
		||||
# SQL窗口函数(二)—— 连续问题
 | 
			
		||||
 | 
			
		||||
### 题目一
 | 
			
		||||
 | 
			
		||||
#### 表格
 | 
			
		||||
 | 
			
		||||
| user_id | login_date |
 | 
			
		||||
| ------- | ---------- |
 | 
			
		||||
| A       | 2022-09-02 |
 | 
			
		||||
| A       | 2022-09-03 |
 | 
			
		||||
| A       | 2022-09-04 |
 | 
			
		||||
| B       | 2021-11-25 |
 | 
			
		||||
| B       | 2021-12-31 |
 | 
			
		||||
| C       | 2022-01-01 |
 | 
			
		||||
| C       | 2022-04-04 |
 | 
			
		||||
| C       | 2022-09-03 |
 | 
			
		||||
| C       | 2022-09-04 |
 | 
			
		||||
| C       | 2022-09-05 |
 | 
			
		||||
| A       | 2022-09-03 |
 | 
			
		||||
| D       | 2022-10-20 |
 | 
			
		||||
| D       | 2022-10-21 |
 | 
			
		||||
| A       | 2022-10-03 |
 | 
			
		||||
| D       | 2022-10-22 |
 | 
			
		||||
| D       | 2022-10-23 |
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
CREATE TABLE SQL_8
 | 
			
		||||
(
 | 
			
		||||
     user_id 	varchar(2),
 | 
			
		||||
     login_date date
 | 
			
		||||
);
 | 
			
		||||
INSERT INTO SQL_8 (user_id,login_date)
 | 
			
		||||
VALUES ('A', '2022-09-02'), ('A', '2022-09-03'), ('A', '2022-09-04'), ('B', '2021-11-25'),
 | 
			
		||||
       ('B', '2021-12-31'), ('C', '2022-01-01'), ('C', '2022-04-04'), ('C', '2022-09-03'),
 | 
			
		||||
       ('C', '2022-09-05'), ('C', '2022-09-04'), ('A', '2022-09-03'), ('D', '2022-10-20'),
 | 
			
		||||
       ('D', '2022-10-21'), ('A', '2022-10-03'), ('D', '2022-10-22'), ('D', '2022-10-23');
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 问题
 | 
			
		||||
 | 
			
		||||
找出这张表中所有的连续3天登录用户
 | 
			
		||||
 | 
			
		||||
#### 分析
 | 
			
		||||
 | 
			
		||||
连续N天登录用户,要求数据行满足以下条件:
 | 
			
		||||
 | 
			
		||||
1) userid 要相同,表示同一用户
 | 
			
		||||
 | 
			
		||||
2) 同一用户每行记录以登录时间从小到大排序
 | 
			
		||||
 | 
			
		||||
3) 后一行记录比前一行记录的登录时间多一天
 | 
			
		||||
 | 
			
		||||
4) 数据行数大于等于N
 | 
			
		||||
 | 
			
		||||
#### 解答
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
-- 方法一
 | 
			
		||||
with t1 as (
 | 
			
		||||
    select distinct user_id, login_date from SQL_8
 | 
			
		||||
),
 | 
			
		||||
t2 as (
 | 
			
		||||
    select *, row_number() over (partition by user_id order by login_date) as rn from t1
 | 
			
		||||
),
 | 
			
		||||
t3 as (
 | 
			
		||||
    select *, DATE_SUB(login_date, interval rn day) as sub from t2
 | 
			
		||||
)
 | 
			
		||||
select distinct user_id from t3 group by user_id, sub having count(user_id) >= 3;
 | 
			
		||||
 | 
			
		||||
-- 方法二
 | 
			
		||||
with t1 as (
 | 
			
		||||
    select distinct user_id, login_date from SQL_8
 | 
			
		||||
),
 | 
			
		||||
t2 as (
 | 
			
		||||
    select *, DATEDIFF(login_date, lag(login_date, 1) over (partition by user_id order by login_date)) as diff from t1
 | 
			
		||||
)
 | 
			
		||||
select user_id from t2 where diff = 1 group by user_id having count(user_id) >= 2;
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 题目二
 | 
			
		||||
 | 
			
		||||
#### 表格
 | 
			
		||||
 | 
			
		||||
| player_id | score | score_time          |
 | 
			
		||||
| --------- | ----- | ------------------- |
 | 
			
		||||
| B3        | 1     | 2022-09-20 19:00:14 |
 | 
			
		||||
| A2        | 1     | 2022-09-20 19:01:04 |
 | 
			
		||||
| A2        | 3     | 2022-09-20 19:01:16 |
 | 
			
		||||
| A2        | 3     | 2022-09-20 19:02:05 |
 | 
			
		||||
| A2        | 2     | 2022-09-20 19:02:25 |
 | 
			
		||||
| B5        | 2     | 2022-09-20 19:02:54 |
 | 
			
		||||
| A4        | 3     | 2022-09-20 19:03:10 |
 | 
			
		||||
| B1        | 2     | 2022-09-20 19:03:34 |
 | 
			
		||||
| B1        | 2     | 2022-09-20 19:03:58 |
 | 
			
		||||
| B1        | 3     | 2022-09-20 19:04:07 |
 | 
			
		||||
| A2        | 1     | 2022-09-20 19:04:19 |
 | 
			
		||||
| A3        | 2     | 2022-09-20 19:04:31 |
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
CREATE TABLE SQL_9
 | 
			
		||||
(
 | 
			
		||||
     player_id 	varchar(2),
 | 
			
		||||
     score      int,
 | 
			
		||||
     score_time datetime
 | 
			
		||||
);
 | 
			
		||||
INSERT INTO SQL_9 (player_id, score, score_time)
 | 
			
		||||
VALUES ('B3', 1, '2022-09-20 19:00:14'), ('A2', 1, '2022-09-20 19:01:04'),
 | 
			
		||||
       ('A2', 3, '2022-09-20 19:01:16'), ('A2', 3, '2022-09-20 19:02:05'),
 | 
			
		||||
       ('A2', 2, '2022-09-20 19:02:25'), ('B3', 2, '2022-09-20 19:02:54'),
 | 
			
		||||
       ('A4', 3, '2022-09-20 19:03:10'), ('B1', 2, '2022-09-20 19:03:34'),
 | 
			
		||||
       ('B1', 2, '2022-09-20 19:03:58'), ('B1', 3, '2022-09-20 19:04:07'),
 | 
			
		||||
       ('A2', 1, '2022-09-20 19:04:19'), ('B3', 2, '2022-09-20 19:04:31');
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 问题
 | 
			
		||||
 | 
			
		||||
统计出连续三次(及以上)为球队得分的球员名单
 | 
			
		||||
 | 
			
		||||
#### 分析
 | 
			
		||||
 | 
			
		||||
连续N次以上为球队得分, 要求数据行满足以下条件:
 | 
			
		||||
 | 
			
		||||
1) player_id 要相同表示同一球员
 | 
			
		||||
 | 
			
		||||
2) 每行记录以得分时间从小到大排序
 | 
			
		||||
 | 
			
		||||
3) 数据行数大于等于N
 | 
			
		||||
 | 
			
		||||
#### 解答
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
-- 方法一
 | 
			
		||||
with t1 as (
 | 
			
		||||
    select *, lag(player_id, 1) over (order by score_time) as last_play_id from SQL_9
 | 
			
		||||
)
 | 
			
		||||
select distinct player_id from t1 where player_id = last_play_id group by player_id having count(player_id) >= 2;
 | 
			
		||||
 | 
			
		||||
-- 方法二
 | 
			
		||||
with t1 as (
 | 
			
		||||
    select *, row_number() over (order by score_time) as rn from SQL_9
 | 
			
		||||
),
 | 
			
		||||
 t2 as (
 | 
			
		||||
     select *, row_number() over (order by score_time) + 1 as rn from SQL_9
 | 
			
		||||
 ),
 | 
			
		||||
 t3 as (
 | 
			
		||||
     select t1.player_id  as player_id from t1 join t2 on t1.rn = t2.rn and t1.player_id = t2.player_id
 | 
			
		||||
 )
 | 
			
		||||
select distinct player_id from t3 group by player_id having count(player_id) >= 2;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 题目三
 | 
			
		||||
 | 
			
		||||
#### 表格
 | 
			
		||||
 | 
			
		||||
| log_id |
 | 
			
		||||
| :----: |
 | 
			
		||||
|   1    |
 | 
			
		||||
|   2    |
 | 
			
		||||
|   3    |
 | 
			
		||||
|   7    |
 | 
			
		||||
|   8    |
 | 
			
		||||
|   10   |
 | 
			
		||||
 | 
			
		||||
#### 脚本
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
CREATE TABLE SQL_10
 | 
			
		||||
(
 | 
			
		||||
     log_id  int
 | 
			
		||||
);
 | 
			
		||||
INSERT INTO SQL_10 (log_id) VALUES (1), (2), (3), (7), (8), (10);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 问题
 | 
			
		||||
 | 
			
		||||
编写SQL 查询得到 Logs 表中的连续区间的开始数字和结束数字。按照 start_id 排序。查询结果格式如下:
 | 
			
		||||
 | 
			
		||||
| start_id | end_id |
 | 
			
		||||
| -------- | ------ |
 | 
			
		||||
| 1        | 3      |
 | 
			
		||||
| 7        | 8      |
 | 
			
		||||
| 10       | 10     |
 | 
			
		||||
 | 
			
		||||
#### 解答
 | 
			
		||||
 | 
			
		||||
```sql
 | 
			
		||||
-- 方法一
 | 
			
		||||
with t1 as (
 | 
			
		||||
    select *, log_id - row_number() over (order by log_id) as gr from SQL_10
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
select min(log_id) as start_id, max(log_id) as end_id from t1 group by gr
 | 
			
		||||
 | 
			
		||||
-- 方法二
 | 
			
		||||
with t1 as (
 | 
			
		||||
    select *, log_id - row_number() over (order by log_id) as gr, log_id - lag(log_id,1) over () as diff from SQL_10
 | 
			
		||||
),
 | 
			
		||||
t2 as (
 | 
			
		||||
    select log_id, gr from t1 where ifnull(diff,-1) <> 1
 | 
			
		||||
),
 | 
			
		||||
t3 as (
 | 
			
		||||
    select *, log_id - row_number() over (order by log_id) as gr, log_id - lead(log_id,1) over () as diff from SQL_10
 | 
			
		||||
),
 | 
			
		||||
t4 as (
 | 
			
		||||
 select log_id, gr from t3 where ifnull(diff, 1) <> -1
 | 
			
		||||
)
 | 
			
		||||
select t2.log_id as start_id, t4.log_id as end_id from t2, t4 where t2.gr = t4.gr;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 技巧
 | 
			
		||||
 | 
			
		||||
如何求连续区间?
 | 
			
		||||
 | 
			
		||||
1)行号过滤法
 | 
			
		||||
 | 
			
		||||
      通过row_number() 生成连续行号,与区间列进行差值运算,得到的临时结果如果相同表示为同一连续区间
 | 
			
		||||
 | 
			
		||||
2) 错位比较法
 | 
			
		||||
 | 
			
		||||
      通过row_number() / row_number()  + 1 分别生成原生的和错位的连续行号列,进行连表操作
 | 
			
		||||
 | 
			
		||||
      也可以通过lag/lead函数直接生成错位列
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# Centos 安装 Docker
 | 
			
		||||
# Linux 安装 Docker
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
一键安装脚本!Linux系统都支持!
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
   curl  -sSL https://get.docker.com/ | sh
 | 
			
		||||
    curl  -sSL https://get.docker.com/ | sh
 | 
			
		||||
    #国内阿里云镜像
 | 
			
		||||
    curl  -fsSL https://get.docker.com | bash -s docker --mirror Aliyun  
 | 
			
		||||
    #Azure源(中国区azure)
 | 
			
		||||
@@ -18,6 +18,39 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## oracle linxu 
 | 
			
		||||
 | 
			
		||||
1. 执行以下命令,以安装Docker依赖项:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
sudo yum -y install yum-utils device-mapper-persistent-data lvm2
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2. 执行以下命令,以添加Docker官方GPG key:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3. 执行以下命令,以安装Docker CE:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
sudo yum install docker-ce
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
4. 启动并设置Docker服务为开机自启:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
sudo systemctl start docker
 | 
			
		||||
sudo systemctl enable docker
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
通过`docker --version`命令,您可以检查Docker是否已经安装。如果命令输出了版本信息,说明已经成功安装Docker CE。
 | 
			
		||||
 | 
			
		||||
希望这次能给您提供一个更简洁的方法。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 启动服务
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
@@ -64,14 +97,17 @@ Docker中国:https://registry.docker-cn.com
 | 
			
		||||
### 写入配置文件 重启服务
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
   sudo mkdir -p /etc/docker
 | 
			
		||||
   
 | 
			
		||||
    sudo tee /etc/docker/daemon.json <<-'EOF'
 | 
			
		||||
    {
 | 
			
		||||
      "registry-mirrors": ["https://mirror.ccs.tencentyun.com"]
 | 
			
		||||
    }
 | 
			
		||||
    EOF
 | 
			
		||||
sudo mkdir -p /etc/docker
 | 
			
		||||
 | 
			
		||||
#注意EOF前不能有空格    
 | 
			
		||||
sudo tee /etc/docker/daemon.json <<-'EOF'
 | 
			
		||||
{
 | 
			
		||||
"registry-mirrors": ["https://mirror.ccs.tencentyun.com"]
 | 
			
		||||
}
 | 
			
		||||
EOF
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
    sudo systemctl daemon-reload
 | 
			
		||||
    sudo systemctl restart docker
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,6 @@ s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可
 | 
			
		||||
可以使用正则  特殊字符\转义 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sed ”s/要被取代的字串/新的字串/g“ fileName
 | 
			
		||||
sed "s/要被取代的字串/新的字串/g" fileName
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -637,7 +637,15 @@ psql -Uicctestedb -dicctestedb -h192.168.53.123 -f ~/
 | 
			
		||||
# -O 不设置表归属,
 | 
			
		||||
# -F c 自定义压缩
 | 
			
		||||
# -v 显示详情
 | 
			
		||||
./pg_dump -Uenterprisedb -diccedb -h192.168.53.118 -O -v -F c -f ~/diccedb_202207_29.data.sql
 | 
			
		||||
./pg_dump -Uenterprisedb -diccedb -h192.168.53.118 -O -v -F c -f ~/alg.da
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 数据导入
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
 | 
			
		||||
#导入数据
 | 
			
		||||
#-c 指定恢复过程中清空目标数据库中的现有表
 | 
			
		||||
@@ -655,10 +663,6 @@ psql -Uicctestedb -dicctestedb -h192.168.53.123 -f ~/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### COPY 导出导出部分数据
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										214
									
								
								redis/redis-3.0.3,哨兵集群.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								redis/redis-3.0.3,哨兵集群.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,214 @@
 | 
			
		||||
## redis-v3.0.3哨兵集群搭建文档
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 准备工作
 | 
			
		||||
 | 
			
		||||
### 主机
 | 
			
		||||
 | 
			
		||||
准备三台全新 centos linux 服务器 
 | 
			
		||||
 | 
			
		||||
固定IP 
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
192.168.1.11 
 | 
			
		||||
192.168.1.12
 | 
			
		||||
192.168.1.13
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**以下命令如无特殊说明 在三台主机中均要执行**
 | 
			
		||||
 | 
			
		||||
准备环境 
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
yum install make gcc wget
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
创建用户 
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
useradd  redis
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
创建相关目录
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
mkdir -p /opt/{app/redis,applog}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
授权
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
chown -R redis:redis /opt/app/redis/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
chown -R redis:redis /opt/applog/redis/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 编译源码
 | 
			
		||||
 | 
			
		||||
切换用户
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
su - redis
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
下载源码
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
cd /opt/app/redis/
 | 
			
		||||
wget https://download.redis.io/releases/redis-3.0.3.tar.gz
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
解压源码
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
tar -zxvf redis-3.0.3.tar.gz
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
编译源码 这里为了和生产一致 使用libc 内存分配器
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
cd redis-3.0.3
 | 
			
		||||
make MALLOC=libc
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
安装
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
make PREFIX=/opt/app/redis/ install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
配置 path
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
echo 'export PATH=$PATH:/opt/app/redis/bin' >> ~/.bashrc
 | 
			
		||||
 | 
			
		||||
source ~/.bashrc
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 配置redis
 | 
			
		||||
 | 
			
		||||
复制配置文件
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
cp /opt/app/redis/redis-3.0.3/src/redis.conf /opt/app/redis/
 | 
			
		||||
 | 
			
		||||
cp /opt/app/redis/redis-3.0.3/src/sentinel.conf /opt/app/redis/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
编辑配置文件 redis.conf 
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
sed -i 's/daemonize no/daemonize yes/g' redis.conf
 | 
			
		||||
 | 
			
		||||
sed -i 's|^logfile ""$|logfile "/opt/applog/redis/redis.log"|g' redis.conf
 | 
			
		||||
 | 
			
		||||
sed -i 's|^dir "./"$|dir "/opt/app/redis/"|g' redis.conf
 | 
			
		||||
 | 
			
		||||
sed -i 's/appendonly no/appendonly yes/g' redis.conf
 | 
			
		||||
 | 
			
		||||
sed -i 's/^# cluster-node-timeout 15000$/cluster-node-timeout 5000/g' redis.conf
 | 
			
		||||
#设置主从复制密码
 | 
			
		||||
sed -i 's/^# masterauth <master-password>$/masterauth dUw~7a)6/g' redis.conf
 | 
			
		||||
#设置 节点密码
 | 
			
		||||
sed -i 's/^# requirepass foobared$/requirepass dUw~7a)6/g' redis.conf
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
配置主从节点
 | 
			
		||||
 | 
			
		||||
在 192.168.1.12,192.168.1.13 两台机子中执行
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
echo "slaveof 10.23.101.3 6379" >> redis.conf
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
编辑配置文件 sentinel.conf 
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
sed -i 's|^\(sentinel monitor mymaster\) 127.0.0.1|\1 181.168.1.11|' sentinel.conf
 | 
			
		||||
 | 
			
		||||
#设置哨兵密码
 | 
			
		||||
sed -i 's/^#sentinel auth-pass <master-name> <password>/sentinel auth-pass mymaster HmpYZ2KB/g' sentinel.conf
 | 
			
		||||
 | 
			
		||||
echo "daemonize yes" >> sentinel.conf
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
编写动脚本
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
echo "bin/redis-server redis.config">/opt/app/redis/start.sh
 | 
			
		||||
chmod 775 start.sh
 | 
			
		||||
 | 
			
		||||
echo "bin/redis-sentinel sentinel.config">/opt/app/redis/start-sentinel.sh
 | 
			
		||||
chmod 775 start.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
指定用户启动
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
su -s /bin/bash -c "$(pwd)/bin/redis-sentinel $(pwd)/sentinel.conf " redis
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 启动
 | 
			
		||||
 | 
			
		||||
先启动三个redis节点
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
bash start.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
再启动 sentinel j节点
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
bash start-sentinel.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 验证
 | 
			
		||||
 | 
			
		||||
登录三个redis节点 分别写入key
 | 
			
		||||
 | 
			
		||||
192.168.1.11  成功写入  并同步至 192.168.1.12,192.168.1.13
 | 
			
		||||
192.168.1.12	写入失败
 | 
			
		||||
192.168.1.13	写入失败
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
登录哨兵节点 查看哨兵信息
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
redis-cli  -p 26379
 | 
			
		||||
 | 
			
		||||
sentinel sentinels mymaster
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
杀死 主节点
 | 
			
		||||
 | 
			
		||||
分别查看 另外两台redis 的info 信息 是否发生切换
 | 
			
		||||
		Reference in New Issue
	
	Block a user