行转列,列转行,窗口函数详细分析

  • 一、行转列
  • 二、列转行
  • 三、窗口函数
  • 3.1 窗口函数分析
  • 3.2 案例实践一
  • 3.3 案例实践二


一、行转列

1)函数说明(可以是一行转一列,多行转一列)

CONCAT(string A, string B...):返回输入字符串连接后的结果,支持任意个输入字符串;

CONCAT_WS(separator, str1, str2,...):它是一个特殊形式的 concat()。第一个参数是其余参数,的分隔符。分隔符可以是与剩余参数一样的字符串。如果分隔符是 NULL,返回值也将为 NULL。

  • 注意:CONCAT_WS must be “string or array<string>
  • COLLECT_SET(col):只接受基本数据类型,主要作用是将某字段的值进行去重汇总,产生array类型字段。注:一般需要配合group by一起使用,聚合非分组字段
  • COLLECT_LIST(col):只接受基本数据类型,主要作用是将某字段的值进行不去重汇总,产生array类型字段。

2)数据准备

name

constellation

blood_type

关胜

白羊座

A

林冲

射手座

A

宋江

白羊座

B

卢俊义

白羊座

A

公孙胜

射手座

A

柴进

白羊座

B

3)需求

把星座和血型一样的人归类到一起。结果如下:

射手座,A            林冲|公孙胜
白羊座,A            关胜|卢俊义
白羊座,B            宋江|柴进

4)创建本地constellation.txt,导入数据

[qinjl@hadoop102 datas]$ vi constellation.txt
关胜	白羊座	A
林冲	射手座	A
宋江	白羊座	B
卢俊义	白羊座	A
公孙胜	射手座	A
柴进	白羊座	B

5)创建hive表并导入数据

create table person_info(
name string, 
constellation string, 
blood_type string
) 
row format delimited fields terminated by "\t";

load data local inpath "/opt/module/hive/datas/constellation.txt" into table person_info;

6)按需求查询数据

SELECT t1.c_b , CONCAT_WS("|",collect_set(t1.name))
FROM (
    SELECT `name`,CONCAT_WS(',',constellation,blood_type) c_b
    FROM person_info
) t1 
GROUP BY t1.c_b

二、列转行

1)函数说明

SPLIT(str, separator):将字符串按照后面的分隔符切割,转换成字符array。

EXPLODE(col):将hive一列中复杂的array或者map结构拆分成多行。

LATERAL VIEW:(一进多出

  • 用法:LATERAL VIEW udtf(expression) tableAlias AS columnAlias

解释:lateral view用于和split, explode等UDTF一起使用,它能够将一行数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。

lateral view首先为原始表的每行调用UDTF,UDTF会把一行拆分成一或者多行,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, myCol2 FROM baseTable
LATERAL VIEW explode(col1) myTable1 AS myCol1
LATERAL VIEW explode(col2) myTable2 AS myCol2;

结果:
myCol1		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"

2)数据准备

movie

category

《疑犯追踪》

悬疑,动作,科幻,剧情

《Lie to me》

悬疑,警匪,动作,心理,剧情

《战狼2》

战争,动作,灾难

3)需求

将电影分类中的数组数据展开。结果如下:

《疑犯追踪》      悬疑
《疑犯追踪》      动作
《疑犯追踪》      科幻
《疑犯追踪》      剧情
《Lie to me》   悬疑
《Lie to me》   警匪
《Lie to me》   动作
《Lie to me》   心理
《Lie to me》   剧情
《战狼2》        战争
《战狼2》        动作
《战狼2》        灾难

4)创建本地movie.txt,导入数据

[qinjl@hadoop102 datas]$ vi movie_info.txt
《疑犯追踪》	悬疑,动作,科幻,剧情
《Lie to me》	悬疑,警匪,动作,心理,剧情
《战狼2》	战争,动作,灾难

5)创建hive表并导入数据

create table movie_info(
    movie string, 
    category string) 
row format delimited fields terminated by "\t";

load data local inpath "/opt/module/hive/datas/movie_info.txt" into table movie_info;

6)按需求查询数据

SELECT movie,category_name 
FROM movie_info 
lateral VIEW explode(split(category,",")) movie_info_tmp  AS category_name;

三、窗口函数

LEAD(col,n, default_val):表示往后第n行数据,col是字段,n表示第几行,default_val 如果没有往后第n行则用默认值代替

LAG(col,n,default_val):表示往前第n行数据,col是字段,n表示第几行,default_val 如果没有往前第n行则用默认值代替

FIRST_VALUE (col,true/false):表示当前窗口下的第一个值,第二个参数为true,跳过空值

LAST_VALUE (col,true/false):表示当前窗口下的最后一个值,第二个参数为true,跳过空值

标准的聚合函数
COUN()TSUM()MIN()MAX()AVG()

排名和分析函数
RANK()ROW_NUMBER()DENSE_RANK()NTILE()

3.1 窗口函数分析

窗口函数=函数+窗口( 窗口:可以限定函数计算的范围)

语法: 窗口函数() over([partition by 字段...] [order by 字段..] [窗口子句])

窗口函数本身的执行顺序:

  1. over()最大的窗口范围
  2. partition by 表示对 over 划分窗口再次划分
  3. order by 对 over/partition 的窗口数据按照字段做排序 ascdesc
  4. 窗口子句 对于over/partition 内的数据,给定函数的运算范围
  5. 窗口函数() 对每一行数据做 窗口范围内的运算

over():是表示最大的窗口范围

partition by 字段:按照字段是否相同来划分更细的窗口,将字段相同的数据分到同一个细窗口里面

窗口子句:

  • 当有只有 over 的时候,窗口子句的范围是 over 指定的范围
  • 当有 partition 的时候,窗口子句的范围是 partition by 过后所有细窗口范围

并不是所有行数都需要窗口子句,以下窗口函数没有窗口字句
Rank(),NTile(),DenseRank(),ROW_NUMBER(),Lead(),Lag ()

当有order by 但没有窗口子句的时候,默认范围 (上无边界 到 当前行)

  • the WINDOW specification defaults to rows BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.

当order by 和 窗口子句都没有的时候,默认范围 (上无边界 到 下无边界)

  • the WINDOW specification defaults to ROW BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.

窗口子句格式:

(ROWS | RANGE) BETWEEN (UNBOUNDED | [num]) PRECEDING AND ([num] PRECEDING | CURRENT ROW | (UNBOUNDED | [num]) FOLLOWING)

(ROWS | RANGE) BETWEEN CURRENT ROW AND (CURRENT ROW | (UNBOUNDED | [num]) FOLLOWING)

(ROWS | RANGE) BETWEEN [num] FOLLOWING AND (UNBOUNDED | [num]) FOLLOWING
  • current row:当前行
  • n preceding:往前n行数据
  • n following:往后n行数据
  • unbounded:无边界
  • unbounded preceding 前无边界,表示从前面的起点
  • unbounded following 后无边界,表示到后面的终点

3.2 案例实践一

1)数据准备:name,orderdate,cost

jack,2020-01-01,10
tony,2020-01-02,15
jack,2020-02-03,23
tony,2020-01-04,29
jack,2020-01-05,46
jack,2020-04-06,42
tony,2020-01-07,50
jack,2020-01-08,55
mart,2020-04-08,62
mart,2020-04-09,68
neil,2020-05-10,12
mart,2020-04-11,75
neil,2020-06-12,80
mart,2020-04-13,94

2)需求

(1)查询在2020年4月份购买过的顾客及总人数

(2)查询顾客的购买明细及月购买总额

(3)上述的场景, 将每个顾客的cost按照日期进行累加

(4)查询顾客购买明细以及上次的购买时间和下次购买时间

(5)查询顾客每个月第一次的购买时间 和 每个月的最后一次购买时间

(6)查询前20%时间的订单信息

3)创建本地business.txt,导入数据

[qinjl@hadoop102 datas]$ vi business.txt

4)创建hive表并导入数据

create table business(
name string, 
orderdate string,
cost int
) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

load data local inpath "/opt/module/hive/datas/business.txt" into table business;

5)按需求查询数据

(1)查询在2020年4月份购买过的顾客及总人数

select name,count(*) over() 
from business 
where substring(orderdate,1,7) = '2020-04' 
-- where month(orderdate)=4 
-- where date_format(orderdate,'yyyy-MM')='2020-04' 
group by name;
+-------+-----------------+
| name  | count_window_0  |
+-------+-----------------+
| mart  | 2               |
| jack  | 2               |
+-------+-----------------+

(2)查询顾客的购买明细及月购买总额

select 
name, 
orderdate, 
cost, 
sum(cost) over(partition by name,month(orderdate)) name_month_cost
from business;
+-------+-------------+-------+------------------+
| name  |  orderdate  | cost  | name_month_cost  |
+-------+-------------+-------+------------------+
| jack  | 2020-01-05  | 46    | 111              |
| jack  | 2020-01-08  | 55    | 111              |
| jack  | 2020-01-01  | 10    | 111              |
| jack  | 2020-02-03  | 23    | 23               |
| jack  | 2020-04-06  | 42    | 42               |
| mart  | 2020-04-13  | 94    | 299              |
| mart  | 2020-04-11  | 75    | 299              |
| mart  | 2020-04-09  | 68    | 299              |
| mart  | 2020-04-08  | 62    | 299              |
| neil  | 2020-05-10  | 12    | 12               |
| neil  | 2020-06-12  | 80    | 80               |
| tony  | 2020-01-04  | 29    | 94               |
| tony  | 2020-01-02  | 15    | 94               |
| tony  | 2020-01-07  | 50    | 94               |
+-------+-------------+-------+------------------+

(3)将每个顾客的cost按照日期进行累加

select name,orderdate,cost, 
sum(cost) over() as sample1,--所有行相加 
sum(cost) over(partition by name) as sample2,--按name分组,组内数据相加 
sum(cost) over(partition by name order by orderdate) as sample3,--按name分组,组内数据累加 
sum(cost) over(partition by name order by orderdate rows between unbounded preceding and current row ) as sample4 ,--和sample3一样,由起点到当前行的聚合 
sum(cost) over(partition by name order by orderdate rows between 1 preceding and current row) as sample5, --当前行和前面一行做聚合 
sum(cost) over(partition by name order by orderdate rows between 1 preceding AND 1 following ) as sample6,--当前行和前边一行及后面一行 
sum(cost) over(partition by name order by orderdate rows between current row and unbounded following ) as sample7 --当前行及后面所有行 
from business;
  • rows必须跟在Order by 子句之后,对排序的结果进行限制,使用固定的行数来限制分区中的数据行数量

(4)查询顾客购买明细以及上次的购买时间和下次购买时间

select 
name,orderdate,cost, 
lag(orderdate,1,'1970-01-01') over(partition by name order by orderdate) prev_time, 
lead(orderdate,1,'1970-01-01') over(partition by name order by orderdate) next_time
from business;
+-------+-------------+-------+-------------+-------------+
| name  |  orderdate  | cost  |  prev_time  |  next_time  |
+-------+-------------+-------+-------------+-------------+
| jack  | 2020-01-01  | 10    | 1970-01-01  | 2020-01-05  |
| jack  | 2020-01-05  | 46    | 2020-01-01  | 2020-01-08  |
| jack  | 2020-01-08  | 55    | 2020-01-05  | 2020-02-03  |
| jack  | 2020-02-03  | 23    | 2020-01-08  | 2020-04-06  |
| jack  | 2020-04-06  | 42    | 2020-02-03  | 1970-01-01  |
| mart  | 2020-04-08  | 62    | 1970-01-01  | 2020-04-09  |
| mart  | 2020-04-09  | 68    | 2020-04-08  | 2020-04-11  |
| mart  | 2020-04-11  | 75    | 2020-04-09  | 2020-04-13  |
| mart  | 2020-04-13  | 94    | 2020-04-11  | 1970-01-01  |
| neil  | 2020-05-10  | 12    | 1970-01-01  | 2020-06-12  |
| neil  | 2020-06-12  | 80    | 2020-05-10  | 1970-01-01  |
| tony  | 2020-01-02  | 15    | 1970-01-01  | 2020-01-04  |
| tony  | 2020-01-04  | 29    | 2020-01-02  | 2020-01-07  |
| tony  | 2020-01-07  | 50    | 2020-01-04  | 1970-01-01  |
+-------+-------------+-------+-------------+-------------+

(5)查询顾客每个月第一次的购买时间 和 每个月的最后一次购买时间

select
name, orderdate, cost, 
FIRST_VALUE(orderdate) over(partition by name,month(orderdate) order by orderdate 
rows between unbounded preceding and unbounded following) first_time,
LAST_VALUE(orderdate) over(partition by name,month(orderdate) order by orderdate 
rows between unbounded preceding and unbounded following) last_time
from business;
+-------+-------------+-------+-------------+-------------+
| name  |  orderdate  | cost  | first_time  |  last_time  |
+-------+-------------+-------+-------------+-------------+
| jack  | 2020-01-01  | 10    | 2020-01-01  | 2020-01-08  |
| jack  | 2020-01-05  | 46    | 2020-01-01  | 2020-01-08  |
| jack  | 2020-01-08  | 55    | 2020-01-01  | 2020-01-08  |
| jack  | 2020-02-03  | 23    | 2020-02-03  | 2020-02-03  |
| jack  | 2020-04-06  | 42    | 2020-04-06  | 2020-04-06  |
| mart  | 2020-04-08  | 62    | 2020-04-08  | 2020-04-13  |
| mart  | 2020-04-09  | 68    | 2020-04-08  | 2020-04-13  |
| mart  | 2020-04-11  | 75    | 2020-04-08  | 2020-04-13  |
| mart  | 2020-04-13  | 94    | 2020-04-08  | 2020-04-13  |
| neil  | 2020-05-10  | 12    | 2020-05-10  | 2020-05-10  |
| neil  | 2020-06-12  | 80    | 2020-06-12  | 2020-06-12  |
| tony  | 2020-01-02  | 15    | 2020-01-02  | 2020-01-07  |
| tony  | 2020-01-04  | 29    | 2020-01-02  | 2020-01-07  |
| tony  | 2020-01-07  | 50    | 2020-01-02  | 2020-01-07  |
+-------+-------------+-------+-------------+-------------+

(6)查询前20%时间的订单信息

select * from (
    select name,orderdate,cost, ntile(5) over(order by orderdate) sorted
    from business
) t
where t.sorted = 1;
+---------+--------------+---------+-----------+
| t.name  | t.orderdate  | t.cost  | t.sorted  |
+---------+--------------+---------+-----------+
| jack    | 2020-01-01   | 10      | 1         |
| tony    | 2020-01-02   | 15      | 1         |
| tony    | 2020-01-04   | 29      | 1         |
+---------+--------------+---------+-----------+

3.3 案例实践二

RANK():排序相同时会重复,总数不会变

DENSE_RANK():排序相同时会重复,总数会减少

ROW_NUMBER():会根据顺序计算

1)数据准备

name	subject	score
燕青	语文	87
燕青	数学	95
燕青	英语	68
宋江	语文	94
宋江	数学	56
宋江	英语	84
林冲	语文	64
林冲	数学	86
林冲	英语	84
朱仝	语文	65
朱仝	数学	85
朱仝	英语	78

2)需求

计算每门学科成绩排名。

3)创建本地score.txt,导入数据

[qinjl@hadoop102 datas]$ vi score.txt

4)创建hive表并导入数据

create table score(
name string,
subject string, 
score int) 
row format delimited fields terminated by "\t";
load data local inpath '/opt/module/hive/datas/score.txt' into table score;

5)按需求查询数据

select name,
subject,
score,
rank() over(partition by subject order by score desc) rp,
dense_rank() over(partition by subject order by score desc) drp,
row_number() over(partition by subject order by score desc) rmp
from score;
+-------+----------+--------+-----+------+------+
| name  | subject  | score  | rp  | drp  | rmp  |
+-------+----------+--------+-----+------+------+
| 燕青   | 数学     | 95     | 1   | 1    | 1    |
| 林冲   | 数学     | 86     | 2   | 2    | 2    |
| 朱仝   | 数学     | 85     | 3   | 3    | 3    |
| 宋江   | 数学     | 56     | 4   | 4    | 4    |
| 林冲   | 英语     | 84     | 1   | 1    | 1    |
| 宋江   | 英语     | 84     | 1   | 1    | 2    |
| 朱仝   | 英语     | 78     | 3   | 2    | 3    |
| 燕青   | 英语     | 68     | 4   | 3    | 4    |
| 宋江   | 语文     | 94     | 1   | 1    | 1    |
| 燕青   | 语文     | 87     | 2   | 2    | 2    |
| 朱仝   | 语文     | 65     | 3   | 3    | 3    |
| 林冲   | 语文     | 64     | 4   | 4    | 4    |
+-------+----------+--------+-----+------+------+