常用的sql知识点总结

MYSql分组topN

Employee 表包含所有员工信息,每个员工有其对应的工号 Id,姓名 Name,工资 Salary 和部门编号 DepartmentId 。
​
+----+-------+--------+--------------+
| Id | Name  | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1  | Joe   | 85000  | 1            |
| 2  | Henry | 80000  | 2            |
| 3  | Sam   | 60000  | 2            |
| 4  | Max   | 90000  | 1            |
| 5  | Janet | 69000  | 1            |
| 6  | Randy | 85000  | 1            |
| 7  | Will  | 70000  | 1            |
+----+-------+--------+--------------+
Department 表包含公司所有部门的信息。
​
+----+----------+
| Id | Name     |
+----+----------+
| 1  | IT       |
| 2  | Sales    |
+----+----------+
编写一个 SQL 查询,找出每个部门获得前三高工资的所有员工。例如,根据上述给定的表,查询结果应返回:
​
+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT         | Max      | 90000  |
| IT         | Randy    | 85000  |
| IT         | Joe      | 85000  |
| IT         | Will     | 70000  |
| Sales      | Henry    | 80000  |
| Sales      | Sam      | 60000  |
+------------+----------+--------+
解释:
​
IT 部门中,Max 获得了最高的工资,Randy 和 Joe 都拿到了第二高的工资,Will 的工资排第三。销售部门(Sales)只有两名员工,Henry 的工资最高,Sam 的工资排第二。
​
​
​
解析:
SELECT D.Name, E1.Name, E1.Salary
FROM Employee AS E1
JOIN Department AS 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`
)
ORDER BY D.`Name`,E1.`Salary` DESC;
​
不妨假设e1=e2=[6,5,4,3],则子查询的过程如下:
1、e1.Salary=3;则e2.Salary可以取4、5、6;COUNT(DISTINCT e2.Salary)=3
2、e1.Salary=4;则e2.Salary可以取5、6;COUNT(DISTINCT e2.Salary)=2
3、e1.Salary=5;则e2.Salary可以取6;COUNT(DISTINCT e2.Salary)=1
4、e1.Salary=6;则e2.Salary无法取值;COUNT(DISTINCT e2.Salary)=0
则要令COUNT(DISTINCT e2.Salary) < 3 的情况有上述的4、3、2.
也即是说,这等价于取e1.Salary最大的三个值。
​
​

三种方式处理sql的TOPN问题

1、自连接
SELECT * FROM aaa a1 
WHERE 3>
(SELECT COUNT(*) FROM aaa a2 
WHERE a2.`num`>a1.num 
AND a1.`sex`=a2.`sex`
)
ORDER BY a1.sex, a1.num DESC
​
//解析 表内连接,在字段相同的情况下(AND a1.`sex`=a2.`sex`) 比较 数据处于表中的位置,然后(WHERE 3>)来确定要取值的N名
​
2 in
select * from stu where score in
(select max(score) ms from stu  group by subject)
​
//解析 先求出字段最大值的值,然后 字段 in ()
//注意 方法只能查最大值 若有最大值重复,可以同时求出
​
3 union all
(select * from stu where subject ='math' order by score desc limit 1)
union all
(select * from stu where subject ='english' order by score desc limit 1)")
​
​
//解析 分别在每个字段中求出最大值然后union all
//注意 该方法只能如果有重复值则结果出错建议用第一种
​

自连接排序

编写一个 SQL 查询来实现分数排名。
​
如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
​
+----+-------+
| Id | Score |
+----+-------+
| 1  | 3.50  |
| 2  | 3.65  |
| 3  | 4.00  |
| 4  | 3.85  |
| 5  | 4.00  |
| 6  | 3.65  |
+----+-------+
例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):
​
+-------+------+
| Score | Rank |
+-------+------+
| 4.00  | 1    |
| 4.00  | 1    |
| 3.85  | 2    |
| 3.65  | 3    |
| 3.65  | 3    |
| 3.50  | 4    |
+-------+------+
重要提示:对于 MySQL 解决方案,如果要转义用作列名的保留字,可以在关键字之前和之后使用撇号。例如 `Rank`
​

select s1.Score,count(distinct(s2.score)) Rank
from
Scores s1,Scores s2
where
s1.score<=s2.score
group by s1.Id
order by Rank
​
例如数据有 1,2,3,3,4,5
s1.score<=s2.score s1=1时候 s2.score= 1,2,3,3,4,5 rank=6 去重=5
s1.score<=s2.score s1=2时候 s2.score= 2,3,3,4,5 rank=6 去重=4
s1.score<=s2.score s1=3时候 s2.score= 3,3,4,5 rank=6 去重=3
s1.score<=s2.score s1=4时候 s2.score= 4,5 rank=6 去重=2
s1.score<=s2.score s1=5时候 s2.score= 5 rank=6 去重=1
​
-- 19、按各科成绩进行排序,并显示排名
SELECT s1.c_id,s1.s_id,s1.s_score, COUNT(DISTINCT(s2.s_score)) Rank
FROM
Score s1,Score s2
WHERE
s1.c_id=s2.c_id AND
s1.s_score<s2.s_score
GROUP BY s1.c_id,s1.s_id,s1.s_score 
ORDER BY s1.c_id,Rank

窗口函数

-- sum()
select
    uid,
    month,
    amount,
    sum(amount) over(
        partition by uid
        order by
            month rows between unbounded preceding
            and current row
    ) accumulate--表示从当前行加到最前面一行(
from(
        select
            uid,
            month,
            sum(amount) amount
        from
            t_accumulate
        group by
            uid,
            month
    ) tmp

--ntile  将数据分为n份
select a.user_id
       ,a.sum_pay_amount
      ,a.level
from(
     select user_id
            ,sum(pay_amount) as sum_pay_amount
            ,ntile(5) over(order by sum(pay_amount) desc) as level
     from user_trade
     group by user_id
) a
where a.level = 1
​
--提拉函数LAG()LEAD()
编写一个 SQL 查询,查找所有至少连续出现三次的数字。
+----+-----+
| Id | Num |
+----+-----+
| 1  |  1  |
| 2  |  1  |
| 3  |  1  |
| 4  |  2  |
| 5  |  1  |
| 6  |  2  |
| 7  |  2  |
+----+-----+
------------------------------------------
select 
distinct Num as count 
from 
    (
    select Id,Num,
    LAG(Num,1)OVER(order by Id) last_1,
    LAG(Num,2)OVER(order by Id) last_2
    from logs) as tmp
where 
tmp.num=tmp.last_1 
and 
tmp.last_1=tmp.last_2

行转列

explode()函数,行转列函数
假如有以下数据
1,zhangsan,化学:物理:数学:语文
2,lisi,化学:数学:生物:生理:卫生
3,wangwu,化学:语文:英语:体育:生物

create table t_stu_subject(id int,name string,subjects array<string>)
row format delimited fields terminated by ','
collection items terminated by ":";

load data local inpath '/root/stu_sub.dat' into table t_stu_subject;

select explode(subjects) from t_stu_subject;
+------+--+
| col  |
+------+--+
| 化学   |
| 物理   |
| 数学   |
| 语文   |
| 化学   |
| 数学   |
| 生物   |
| 生理   |
| 卫生   |
| 化学   |
| 语文   |
| 英语   |
| 体育   |
| 生物   |
+------+--+

select distinct tmp.subs from (select explode(subjects) subs from t_stu_subject) tmp;

+-----------+--+
| tmp.subs  |
+-----------+--+
| 体育        |
| 化学        |
| 卫生        |
| 数学        |
| 物理        |
| 生物        |
| 生理        |
| 英语        |
| 语文        |
+-----------+--+

------------------------------------------------------------------------------------------------------------
1,zhangsan,化学:物理:数学:语文
2,lisi,化学:数学:生物:生理:卫生
3,wangwu,化学:语文:英语:体育:生物

lateral view函数  ##横向连接
select id,name,sub from t_stu_subject lateral view explode(subjects) tmp as sub
+-----+-----------+----------+--+
| id  |   name    | tmp.sub  |
+-----+-----------+----------+--+
| 1   | zhangsan  | 化学       |
| 1   | zhangsan  | 物理       |
| 1   | zhangsan  | 数学       |
| 1   | zhangsan  | 语文       |
| 2   | lisi      | 化学       |
| 2   | lisi      | 数学       |
| 2   | lisi      | 生物       |
| 2   | lisi      | 生理       |
| 2   | lisi      | 卫生       |
| 3   | wangwu    | 化学       |
| 3   | wangwu    | 语文       |
| 3   | wangwu    | 英语       |
| 3   | wangwu    | 体育       |
| 3   | wangwu    | 生物       |
+-----+-----------+----------+--+

炸map
select
id,name,key,value
from t_user lateral view explode(family) tmp as key,value


 =========================================================
 +-----+-----------+----------+--------+
| name       |   math    |  english    |
+------------+-----------+----------+--+
| zhansgan   |     32    |    88       |
| lisi       |     88    |    93       |
+------------+-----------+----------+--+
转为
zhangsan math 32
zhangsan english 88
lisi math 88
lisi english 93

sql
select
    test_stuinfo.name,
    a.item,
    a.score
from
    test_stuinfo lateral view explode( 
    str_to_map(concat('math=', math, '&english=', english), '&', '=') --{"math:21","english:90"}
    ) a as item, score;
   

select
    movie,
    category_name
from
    movie_info lateral view explode(category) table_tmp as category_name;

列转行

--case when

select t.sname,
max(case when t.cname="语文" then t.score end ) `语文`,
max(case when t.cname="数学" then t.score end ) `数学`,
max(case when t.cname="英语" then t.score end ) `英语`
from
(select s.s_name sname,sc.s_score score,c.c_name cname from 
ods_score sc,ods_student s,ods_course c
where 
s.s_id=sc.s_id
and
sc.c_id = c.c_id) t
group by t.sname

使用max和group by 

-- 
col_lie.user_id    col_lie.order_id
104399    1715131
104399    2105395
104399    1758844
104399    981085
104399    2444143
104399    1458638
104399    968412
104400    1609001
104400    2986088
104400    1795054
-- 把相同user_id的order_id按照逗号转为一行
select user_id,
concat_ws(',',collect_list(order_id)) as order_value
from col_lie
group by user_id
limit 10

NVL函数

nvl(1,2) 1 第一个函数不为null 则取1 否则2

get_json_object

get_json_object(line,'$.mid') mid,

日期处理函数

date_format("2018-06-14",'yyyy-MM')转换格式

date_format("2018-06-14",'%Y-%m')转换格式

date_add('2018-10-29','-1)前一天

date_add('2018-10-29',1)后一天

next_day('2018-10-29','MO')下一个周一

date_add(next_day('2018-10-29','MO'),-7)本周一

last_day('2018-10-29')当月最后一天

date_format(regexp_replace('2020/12/11','/','-'),'yyyy-MM' ---->2020-12

datediff(date1,date2) 日期相减,date1

如果想把 20180123 转换成 2018-01-23,可以使用:

select from_unixtime(unix_timestamp('${p_date}','yyyymmdd'),'yyyy-mm-dd') from dual lim

时间戳转成日期

select distinct from_unixtime(1441565203,‘yyyy/MM/dd HH:mm:ss’) from test_date;

日期转成时间戳

select distinct unix_timestamp(‘20111207 13:01:03’) from test_date; // 默认格式为“yyyy-MM-dd HH:mm:ss“

select distinct unix_timestamp(‘20111207 13:01:03’,‘yyyyMMdd HH:mm:ss’) from test_date;

collect函数

Hive笔记之collect_list/collect_set/concat_ws/concat函数(列转行)
 
create table t_visit_video (
    username string,
    video_name string
) partitioned by (day string)
row format delimited fields terminated by ',';
 
vi  /home/hadoop/t_visit_video.txt 
 
张三,大唐双龙传
李四,天下无贼
张三,神探狄仁杰
李四,霸王别姬
李四,霸王别姬
王五,机器人总动员
王五,放牛班的春天
王五,盗梦空间
 
load data local inpath '/home/hadoop/t_visit_video.txt' into table t_visit_video partition (day='20200508');
 
hive (felix)> select * from t_visit_video;
OK
张三    大唐双龙传      20200508
李四    天下无贼        20200508
张三    神探狄仁杰      20200508
李四    霸王别姬        20200508
李四    霸王别姬        20200508
王五    机器人总动员    20200508
王五    放牛班的春天    20200508
王五    盗梦空间        20200508
Time taken: 1.445 seconds, Fetched: 8 row(s)
 
按用户分组,取出每个用户每天看过的所有视频的名字:
hive (felix)> select username, collect_list(video_name) from t_visit_video group by username;
张三    ["大唐双龙传","神探狄仁杰"]
李四    ["天下无贼","霸王别姬","霸王别姬"]
王五    ["机器人总动员","放牛班的春天","盗梦空间"]
Time taken: 26.414 seconds, Fetched: 3 row(s)
 
但是上面的查询结果有点问题,因为霸王别姬实在太好看了,所以李四这家伙看了两遍,这直接就导致得到的观看过视频列表有重复的,所以应该增加去重,使用collect_set,其与collect_list的区别就是会去重:
hive (felix)> select username, collect_set(video_name) from t_visit_video group by username;
OK
张三    ["神探狄仁杰","大唐双龙传"]
李四    ["霸王别姬","天下无贼"]
王五    ["盗梦空间","放牛班的春天","机器人总动员"]
Time taken: 24.167 seconds, Fetched: 3 row(s)
 
李四的观看记录中霸王别姬只出现了一次,实现了去重效果。
 
--实现了linux的rownum功能
 
hive (felix)> select username,video_name,day,row_number() over ()  rn from t_visit_video; 
王五    盗梦空间        20200508        1
王五    放牛班的春天    20200508        2
王五    机器人总动员    20200508        3
李四    霸王别姬        20200508        4
李四    霸王别姬        20200508        5
张三    神探狄仁杰      20200508        6
李四    天下无贼        20200508        7
张三    大唐双龙传      20200508        8
 
 
突破group by限制
还可以利用collect来突破group by的限制,Hive中在group by查询的时候要求出现在select后面的列都必须是出现在group by后面的,即select列必须是作为分组依据的列,但是有的时候我们想根据A进行分组然后随便取出每个分组中的一个B,代入到这个实验中就是按照用户进行分组,然后随便拿出一个他看过的视频名称即可:
 
hive (felix)> select username, collect_list(video_name)[0] from t_visit_video group by username;
张三    神探狄仁杰
李四    霸王别姬
王五    盗梦空间
Time taken: 23.027 seconds, Fetched: 3 row(s)
 
video_name不是分组列,依然能够取出这列中的数据。
 
 
concat、concat_ws 函数
1)concat只是将各个字符进行拼接
2)concat_ws是将各个字符串 通过一个指定的拼接符进行拼接,相当于Oracle的wm_concat和listagg 
 
从数据库里取N个字段,然后组合到一起用“,”分割显示,起初想到用CONCAT()来处理,好是麻烦,没想到在手册里居然有提到CONCAT_WS(),非常好用。
它是一个特殊形式的 CONCAT(),第一个参数剩余参数间的分隔符,分隔符可以是与剩余参数一样的字符串,如果分隔符是 NULL,返回值也将为 NULL,这个函数会跳过分隔符参数后的任何 NULL 和空字符串,分隔符将被加到被连接的字符串之间。
hive (felix)> SELECT CONCAT_WS(",","First name","Second name","Last Name");
OK
First name,Second name,Last Name
Time taken: 0.055 seconds, Fetched: 1 row(s)
 
hive (felix)> SELECT CONCAT(",","First name","Second name","Last Name");
OK
,First nameSecond nameLast Name
Time taken: 0.044 seconds, Fetched: 1 row(s)
 
hive (felix)> SELECT CONCAT_WS(",","First name",NULL,"Last Name");
OK
First name,Last Name
Time taken: 0.051 seconds, Fetched: 1 row(s)
 

collect_set 实例

-- 现在要生产订单号要关联出库单号,一个生产订单号可能有多个出库单号,如果join就会造成数据膨胀的问题
select * from 
(select * from 生产订单)a
left join
(select
 		scddh,
 		collect_set(out_general_code) out_general_code 
 from 
 		出库单表
 group by 
 		scddh -- 只用group一个就可以了
 )b
 on a.scddh=b.scddh

str_to_map

使用两个分隔符转为键值对 第一个分隔符是键值对之间的分隔符,第二个分隔符是KV之间的分隔符

例:
select str_to_map('name:wangwenjie&sex:male','&',':')
结果: {"name":"wangwenjie","sex":"male"}

也可以通过K获取V
select str_to_map('name:wangwenjie&sex:male','&',':')['name']
结果:wangwenjie

reflect函数

-- hive新特性
--reflect函数可以支持在sql中调用java中的自带函数,秒杀一切udf函数。
例如一张表数据 
1,2
2,3
3,4
4,5
5,6
sql:select reflect("java.lang.Math","max",column1,column2) from table
--就是调用java的math中的max方法
结果:2,3,4,5,6

--使用apache commons中的函数,commons下的jar已经包含在hadoop的classpath中,所以可以直接使用。
select reflect("org.apache.commons.lang.math.NumberUtils","isNumber","123") from dual;
结果:true

regexp_replace

-- 把INITIAL_STRING中与PATTERN相匹配的子串替换为REPLACEMENT
regexp_replace(string INITIAL_STRING, string PATTERN, string REPLACEMENT)

ex:select regexp_replace('\n123\n','\n','456')
result:456123456

ex:select regexp_replace('\n123\n','\n|2','abc')
result:abc1abc3abc

用\\s替换空格,(最好) 用\\n替换\n,
hive> select regexp_replace('\n12  3   \n','\n|2|\s','abc') ;
abc1abc  3   abc

hive> select regexp_replace('\n12  3   \n','\n|2|\\s','abc') ;
abc1abcabcabc3abcabcabcabc

hive> select regexp_replace('\n12  3   \n','\\n|2|\\s','abc') ;
abc1abcabcabc3abcabcabcabc

explode和lateral view

-- 行转列
数据 
100,200,300
400,500,600
sql:
select explode(split(num,',')) from table
result:
100,
200,
300,
400,
500,
600

-- 联合lateral view
数据:
pageAds表
string pageid	Array<int> adid_list
"front_page"	[1, 2, 3]
"contact_page"	[3, 4, 5]

--要统计所有广告ID在所有页面中出现的次数。
首先分拆广告ID:
select pageid,adid from pageAds lateral view explode(adid_list) adtable as adid

结果
string pageid	int adid
"front_page"	1
"front_page"	2
"front_page"	3
"contact_page"	3
"contact_page"	4
"contact_page"	5

SELECT adid, count(1) 
	FROM pageAds LATERAL VIEW explode(adid_list) adTable AS adid
GROUP BY adid;
 
执行结果如下:
 
  
int adid	count(1)
1	1
2	1
3	2
4	1
5	1

多个lateral view语句:
一个FROM语句后可以跟多个lateral view语句,后面的lateral view语句能够引用它前面的所有表和列名。
以下面的表为例: 
Array<int> col1	    Array<string> col2
[1, 2]	[a", "b", "c"]
[3, 4]	[d", "e", "f"]
SELECT myCol1, col2 FROM baseTable
	LATERAL VIEW explode(col1) myTable1 AS myCol1;
的执行结果为:

 
int
 mycol1	Array<string> col2
1	[a", "b", "c"]
2	[a", "b", "c"]
3	[d", "e", "f"]
4	[d", "e", "f"]

加上一个lateral view:

SELECT myCol1, myCol2 FROM baseTable
	LATERAL VIEW explode(col1) myTable1 AS myCol1
	LATERAL VIEW explode(col2) myTable2 AS myCol2;

它的执行结果为:

 
int
 myCol1	string myCol2
1	"a"
1	"b"
1	"c"
2	"a"
2	"b"
2	"c"
3	"d"
3	"e"
3	"f"
4	"d"
4	"e"
4	"f"

注意上面语句中,两个lateral view按照出现的次序被执行。

笛卡尔积应用

id course 
1,a 
1,b 
1,c 
1,e 
2,a 
2,c 
2,d 
2,f 
3,a 
3,b 
3,c 
3,e
编写Hive的HQL语句来实现以下结果:
表中的1表示选修,表中的0表示未选修

id    a    b    c    d    e    f
1    1    1    1    0    1    0
2    1    0    1    1    0    1
3    1    1    1    0    1    0
--------------------------------------------------------------
首先获取所有的课程
select collect_set(trim(course)) id_course from stuinfo group by id;--["a","b","c","d","e","f"]
--------------------------------------------------------------
求每一个学员的课程
select
id,collect_set(trim(course)) id_course    
from interview04
group by id;
--------------------------------------------------------------
将所有课程和所选课程join形成笛卡尔积
select
b.id id,b.id_course id_course,a.courses courses
from
(select collect_set(trim(course))courses from stuinfo
) a join
(select
id,collect_set(trim(course)) id_course    
from interview04
group by id
) b;
id      id_course               courses
1       ["a","b","c","e"]       ["a","b","c","e","d","f"]
2       ["a","c","d","f"]       ["a","b","c","e","d","f"]
3       ["a","b","c","e"]       ["a","b","c","e","d","f"]

-------------------------------------------------------------
最终判断coureses是否包含学生所选课程,包含为1不包含为0
select
id,
if(array_contains(id_course,courses[0]),1,0) a, --如果id_course(学生所选课程)包含courses[0](全部课程[0])
if(array_contains(id_course,courses[1]),1,0) b,
if(array_contains(id_course,courses[2]),1,0) c,
if(array_contains(id_course,courses[3]),1,0) e,
if(array_contains(id_course,courses[4]),1,0) d,
if(array_contains(id_course,courses[5]),1,0) f
from
(select
b.id id,b.id_course id_course,a.courses from stuinfo
from
(select collect_set(trim(course))courses from stuinfo ) a --所有的课程
join
(select collect_set(trim(course))id_course from stuinfo group by id)b )c--学生所选课程

连续登录

select
    '2020-06-22',
    concat(date_add('2020-06-22', -6), '|2020-06-22'),
    count(*)
from
    (
        select
            mid_id
        from
            (-- 查询差日期为同一天的结果大于等于3
                select
                    mid_id,
                    count(*) cnts
                from
                    ( --日期减去排序,连续登陆则差相同
                        select
                            mid_id,
                            date_sub(dt, rk) date_diff 
                        from
                            (-- 先根据ID给对应的日期排序
                                select
                                    mid_id,
                                    dt,
                                    row_number() over(partition by mid_id order by dt) rk
                                from
                                    dws_uv_detail_daycount
                                where
                                    dt >= date_add('2020-06-22', -6)
                                    and dt <= '2020-06-22'
                            ) t1
                    ) t2
                group by
                    mid_id,
                    date_diff
                having
                    cnts >= 3
            ) t3
        group by
            mid_id
    ) t4

树状结构两种情况

-- 层级相同 有最大层级 获取所有的层级
WITH t AS ( SELECT
parent_id
,id
,category_name,level
FROM
s_wxapp_swp_product_physic_category where ds=20210512 ) 

SELECT a.id lv1
,a.category_name lv1_name
,b.id lv2
,b.category_name lv2_name
,c.id lv3
,c.category_name lv3_name
,d.id lv4
,d.category_name lv4_name
FROM (
SELECT
parent_id
,id
,category_name
FROM
t 
WHERE level=4
) a
LEFT JOIN t b
ON a.parent_id = b.id LEFT
JOIN t c
ON b.parent_id = c.id
LEFT JOIN t d
ON c.parent_id = d.id
;



-- 每个层级不固定,最大层级不确定只能最大循环,且只取最大最小
SELECT
    IF(
        g.pid IS NULL,
        IF(
            f.pid IS NULL,
            IF(
                e.pid IS NULL,
                IF(
                    d.pid IS NULL,
                    IF(c.pid IS NULL, b.pid, c.pid),
                    d.pid
                ),
                e.pid
            ),
            f.pid
        ),
        g.pid
    ) product,
    a.bid
FROM
    (
        SELECT
            bid
        FROM
            digui
        WHERE
            bid NOT IN (
                SELECT
                    pid
                FROM
                    digui
            )
    ) a
    LEFT JOIN digui b ON a.bid = b.bid
    LEFT JOIN digui c ON b.pid = c.bid
    LEFT JOIN digui d ON c.pid = d.bid
    LEFT JOIN digui e ON d.pid = e.bid
    LEFT JOIN digui f ON e.pid = f.bid
    LEFT JOIN digui g ON g.pid = f.bid
GROUP BY
    product,
    bid

数据倾斜

文章链接

https://blog.csdn.net/u010670689/article/details/42920917?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162495266916780262577659%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162495266916780262577659&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-1-42920917.pc_search_result_control_group&utm_term=hive+%E6%95%B0%E6%8D%AE%E5%80%BE%E6%96%9C%E6%80%8E%E4%B9%88%E5%A4%84%E7%90%86&spm=1018.2226.3001.4187



https://blog.csdn.net/strongyoung88/article/details/100594082?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162495266816780357210641%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162495266816780357210641&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-3-100594082.pc_search_result_control_group&utm_term=hive+%E6%95%B0%E6%8D%AE%E5%80%BE%E6%96%9C%E6%80%8E%E4%B9%88%E5%A4%84%E7%90%86&spm=1018.2226.3001.4187

3.1空值产生的数据倾斜
场景:如日志中,常会有信息丢失的问题,比如日志中的 user_id,如果取其中的 user_id 和 用户表中的user_id 关联,会碰到数据倾斜的问题。

解决方法1: user_id为空的不参与关联(红色字体为修改后)

select * from log a

  join users b

  on a.user_id is not null

  and a.user_id = b.user_id

union all

select * from log a

  where a.user_id is null;

 

解决方法2 :赋与空值分新的key值

select *

  from log a

  left outer join users b

  on case when a.user_id is null then concat(‘hive’,rand() ) else a.user_id end = b.user_id;

 

结论:方法2比方法1效率更好,不但io少了,而且作业数也少了。解决方法1中 log读取两次,jobs是2。解决方法2 job数是1 。这个优化适合无效 id (比如 -99 , ’’, null 等) 产生的倾斜问题。把空值的 key 变成一个字符串加上随机数,就能把倾斜的数据分到不同的reduce上 ,解决数据倾斜问题。

3.2不同数据类型关联产生数据倾斜
场景:用户表中user_id字段为int,log表中user_id字段既有string类型也有int类型。当按照user_id进行两个表的Join操作时,默认的Hash操作会按int型的id来进行分配,这样会导致所有string类型id的记录都分配到一个Reducer中。

解决方法:把数字类型转换成字符串类型

select * from users a

  left outer join logs b

  on a.usr_id = cast(b.user_id as string)

 

3.3小表不小不大,怎么用 map join 解决倾斜问题
使用 map join 解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用的频率非常高,但如果小表很大,大到map join会出现bug或异常,这时就需要特别的处理。 以下例子:

select * from log a

  left outer join users b

  on a.user_id = b.user_id;

 

users 表有 600w+ 的记录,把 users 分发到所有的 map 上也是个不小的开销,而且 map join 不支持这么大的小表。如果用普通的 join,又会碰到数据倾斜的问题。

解决方法:

select /*+mapjoin(x)*/* from log a

  left outer join (

    select  /*+mapjoin(c)*/d.*

      from ( select distinct user_id from log ) c

      join users d

      on c.user_id = d.user_id

    ) x

  on a.user_id = b.user_id;

 

假如,log里user_id有上百万个,这就又回到原来map join问题。所幸,每日的会员uv不会太多,有交易的会员不会太多,有点击的会员不会太多,有佣金的会员不会太多等等。所以这个方法能解决很多场景下的数据倾斜问题。

4总结
使map的输出数据更均匀的分布到reduce中去,是我们的最终目标。由于Hash算法的局限性,按key Hash会或多或少的造成数据倾斜。大量经验表明数据倾斜的原因是人为的建表疏忽或业务逻辑可以规避的。在此给出较为通用的步骤:

1、采样log表,哪些user_id比较倾斜,得到一个结果表tmp1。由于对计算框架来说,所有的数据过来,他都是不知道数据分布情况的,所以采样是并不可少的。

2、数据的分布符合社会学统计规则,贫富不均。倾斜的key不会太多,就像一个社会的富人不多,奇特的人不多一样。所以tmp1记录数会很少。把tmp1和users做map join生成tmp2,把tmp2读到distribute file cache。这是一个map过程。

3、map读入users和log,假如记录来自log,则检查user_id是否在tmp2里,如果是,输出到本地文件a,否则生成<user_id,value>的key,value对,假如记录来自member,生成<user_id,value>的key,value对,进入reduce阶段。

4、最终把a文件,把Stage3 reduce阶段输出的文件合并起写到hdfs。

 

如果确认业务需要这样倾斜的逻辑,考虑以下的优化方案:

1、对于join,在判断小表不大于1G的情况下,使用map join

2、对于group by或distinct,设定 hive.groupby.skewindata=true

3、尽量使用上述的SQL语句调节进行优化
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值