04hive查询
详细文档查看:https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Select
一. 基本查询(SELECT …FROM)
1. 全表和特定列查询
select * from emp;
select empno, ename from emp;
注意:
(1)SQL 语言大小写不敏感。
(2)SQL 可以写在一行或者多行
(3)关键字不能被缩写也不能分行
(4)各子句一般要分行写。
(5)使用缩进提高语句的可读性
列别名:重命名一个列 ;便于计算 ;紧跟列名,也可以在列名和别名之间加入关键字‘AS’。
select ename AS name, deptno dn from emp;
2. 算术运算符
查询出所有员工的薪水后加 1 显示。 select sal +1 from emp;
3. 常用函数
1) 求总行数(count) hive (default)> select count(*) cnt from emp;
2) 求工资的最大值(max) hive (default)> select max(sal) max_sal from emp;
3) 求工资的最小值(min) hive (default)> select min(sal) min_sal from emp;
4) 求工资的总和(sum) hive (default)> select sum(sal) sum_sal from emp;
5) 求工资的平均值(avg) hive (default)> select avg(sal) avg_sal from emp;
4. LIMIT 子句
典型的查询会返回多行数据。LIMIT 子句用于限制返回的行数。
hive (default)> select * from emp limit 5;
二. Where 语句
- 使用 WHERE 子句,将不满足条件的行过滤掉
- WHERE 子句紧随 FROM 子句
- 案例实操 查询出薪水大于 1000 的所有员工 : hive (default)> select * from emp where sal >1000;
1. 比较运算符(Between/In/ Is Null)
1) 谓词操作符,这些操作符同样可以用于 JOIN…ON 和 HAVING 语句中。
2. Like 和 RLike
1)使用 LIKE 运算选择类似的值
2)选择条件可以包含字符或数字:
% 代表零个或多个字符(任意个字符)。
_ 代表一个字符。
3)RLIKE 子句是 Hive 中这个功能的一个扩展,其可以通过 Java 的正则表达式这个更
强大的语言来指定匹配条件(好像是满足其中一个条件就可以显示)
3. 逻辑运算符(And/Or/Not)
三. 分组
1. Group by语句
GROUP BY 语句通常会和聚合函数一起使用,按照一个或者多个列队结果进行分组,然后对每个组执行聚合操作。
计算 emp 表每个部门的平均工资 select t.deptno, avg(t.sal) avg_sal from emp t group by t.deptno;
2.having语句
having 与 where 不同点
l where 针对表中的列发挥作用,查询数据;having 针对查询结果中的列发挥作用,
筛选数据。
l where 后面不能写聚合函数,而 having 后面可以使用聚合函数。
l having 只用于 group by 分组统计语句。
求每个部门的平均薪水大于 2000 的部门
select deptno, avg(sal) avg_sal from emp group by deptno having avg_sal > 2000;
四. Join 语句
1. 等值 Join
Hive 支持通常的 SQL JOIN 语句,但是只支持等值连接,不支持非等值连接。
2. 内连接
内连接:只有进行连接的两个表中都存在与连接条件相匹配的数据才会被保留下来。
hive (default)> select e.empno, e.ename, d.deptno from emp e join dept d on e.deptno = d.deptno;
3. 左外连接
左外连接:JOIN 操作符左边表中符合 WHERE 子句的所有记录将会被返回。
hive (default)> select e.empno, e.ename, d.deptno from emp e left join dept d on e.deptno = d.deptno;
4. 右外连接
右外连接:JOIN 操作符右边表中符合 WHERE 子句的所有记录将会被返回。
hive (default)> select e.empno, e.ename, d.deptno from emp e right join dept d on e.deptno = d.deptno;
5. 满外连接
满外连接:将会返回所有表中符合 WHERE 语句条件的所有记录。如果任一表的指定 字段没有符合条件的值的话,那么就使用 NULL 值替代。
select e.empno, e.ename, d.deptno from emp e full join dept d on e.deptno = d.deptno;
6. 多表连接
注意:连接 n 个表,至少需要 n-1 个连接条件。
SELECT e.ename, d.deptno, l.loc_name FROM emp e JOIN dept d ON d.deptno = e.deptno JOIN location l ON d.loc = l.loc;
五. 排序
1. 全局排序 ORDER BY
l Order By:全局排序,一个 Reducer
l 使用 ORDER BY 子句排序
ASC(ascend): 升序(默认)
DESC(descend): 降序
l ORDER BY 子句在 SELECT 语句的结尾
2.每个 MapReduce 内部排序(Sort By)
Sort By:每个 Reducer 内部进行排序,对全局结果集来说不是排序
使用:
l 设置 reduce 个数
hive (default)> set mapreduce.job.reduces=3;
l 查看设置 reduce 个数
hive (default)> set mapreduce.job.reduces;
l 根据部门编号降序查看员工信息
hive (default)> select * from emp sort by empno desc;
l 将查询结果导入到文件中(按照部门编号降序排序)
hive (default)> insert overwrite local directory '/opt/module/datas/sortby-result' select * from emp sort by deptno desc
3.分区排序(Distribute By)
Distribute By:类似 MR 中 partition,进行分区,结合 sort by 使用。
注意,Hive 要求 DISTRIBUTE BY 语句要写在 SORT BY 语句之前。
对于 distribute by 进行测试,一定要分配多 reduce 进行处理,否则无法看到 distribute by
的效果。
先按照部门编号分区,再按照员工编号降序排序。
hive (default)> set mapreduce.job.reduces=3;
hive (default)> insert overwrite local directory '/opt/module/datas/distribute-result' select * from emp distribute by deptno sort by empno desc;
4.Cluster By
当 distribute by 和 sorts by 字段相同时,可以使用 cluster by 方式。
cluster by 除了具有 distribute by 的功能外还兼具 sort by 的功能。但是排序只能是升序排序,不能指定排序规则为 ASC 或者 DESC。
六. 分桶抽样
1)分桶表数据存储
分区针对的是数据的存储路径;分桶针对的是数据文件。
2) 创建分桶表
要先设置属性!
hive (default)> set hive.enforce.bucketing=true;
hive (default)> set mapreduce.job.reduces=-1;
hive (default)> insert into table stu_buck select id, name from stu;
3)分桶抽样查询:对于非常大的数据集,有时用户需要使用的是一个具有代表性的查询结果而不是全部结果。Hive 可以通过对表进行抽样来满足这个需求。
语法:TABLESAMPLE(BUCKET x OUT OF y) 。
y 必须是 table 总 bucket 数的倍数或者因子。hive 根据 y 的大小,决定抽样的比例。
例如,table 总共分了 4 份,当 y=2 时,抽取(4/2=)2 个 bucket 的数据,当 y=8 时,抽取(4/8=)1/2个 bucket 的数据。
x 表示从哪个 bucket 开始抽取,如果需要取多个分区,以后的分区号为当前分区号加上y。
注意:x 的值必须小于等于 y 的值。
七. 一些常用函数
1.空字段赋值
格式是 NVL( string1, replace_with)。
它的功能是如果string1 为 NULL,则 NVL 函数返回 replace_with 的值,否则返回 string1 的值,如果两个参数都为 NULL ,则返回 NULL。
2.时间类
以上的函数都只能针对yyyy-MM-dd这种格式的日期进行操作,如果不是该格式,则要先使用regexp_replace(‘yyyy/mm/dd’,’/’,’-’)得到正确格式再使用函数操作。
3.case & if
求出不同部门男女各多少人。
select dept_id,
sum(case sex when '男' then 1 else 0 end) male_count,
sum(case sex when '女' then 1 else 0 end) female_count
from emp_sex group by dept_id;
也可以用if
4.行转列
l CONCAT(string A/col, string B/col…):返回输入字符串连接后的结果,支持任意个输入字符串;
l CONCAT_WS(separator, str1, str2,...):它是一个特殊形式的 CONCAT()。
第一个参数剩余参数间的分隔符。分隔符可以是与剩余参数一样的字符串。如果分隔符是 NULL,返回值也将为 NULL。这个函数会跳过分隔符参数后的任何 NULL 和空字符串。分隔符将被加到被连接的字符串之间;
l COLLECT_SET(col):函数只接受基本数据类型,它的主要作用是将某字段的值进行去重汇总,产生array 类型字段。
练习:
select t1.base,concat_ws('|', collect_set(t1.name)) name from
(select name, concat(constellation, ",", blood_type) base from
person_info) t1 group by t1.base;
5.列转行
l EXPLODE(col):将 hive 一列中复杂的 array 或者 map 结构拆分成多行。
l LATERAL VIEW
用法:LATERAL VIEW udtf(expression) tableAlias AS columnAlias
解释:用于和 split, explode 等 UDTF 一起使用,它能够将一列数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。
练习:
select movie, category_name
from movie_info lateral view explode(category) table_tmp as category_name;
6.窗口函数
l OVER():指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变化而变化;
- CURRENT ROW:当前行;
- n PRECEDING:往前 n 行数据;
假如要求当前行和前面两行叠加之后的结果,则可以
- n FOLLOWING:往后 n 行数据;
区间一定要符合规律,如下是可以的,但如果是rows between current row and 2 preceding就是不成立的。over里边也可以添加partition by 这些。
- UNBOUNDED:起点,UNBOUNDED PRECEDING 表示从前面的起点, UNBOUNDED
- FOLLOWING 表示到后面的终点;
以下三个函数后边都要带over()函数,否则报错
l LAG(col,n):往前第 n 行数据;
l LEAD(col,n):往后第 n 行数据;
l NTILE(n):把有序分区中的行分发到指定数据的组中,各个组有编号,编号从 1 开始,对于每一行,NTILE 返回此行所属的组的编号。注意:n 必须为 int 类型。(感觉就是把数据集n等分的意思)
over函数里边有两组可以使用的固定搭配:
练习:
- 数据集:
jack,2017-01-01,10 tony,2017-01-02,15 jack,2017-02-03,23 tony,2017-01-04,29 jack,2017-01-05,46 jack,2017-04-06,42 tony,2017-01-07,50 jack,2017-01-08,55 mart,2017-04-08,62 mart,2017-04-09,68 neil,2017-05-10,12 mart,2017-04-11,75 neil,2017-06-12,80 mart,2017-04-13,94
- 需求:
(1)查询在 2017 年 4 月份购买过的顾客及总人数
(2)查询顾客的购买明细及购买总额
(3)上述的场景,要将 cost 按照日期进行累加
(4)查询顾客上次的购买时间
(5)查询前 20%时间的订单信息
- 执行步骤:
1)建表导数据
create table business( name string, orderdate string, cost int ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ','; load data local inpath "/opt/module/datas/business.txt" into table business;
2)按需求查数据
7.Rank函数
实际也是窗口函数,后边需要带over(),否则会报错。
l RANK() 排序相同时会重复,总数不会变
l DENSE_RANK() 排序相同时会重复,总数会减少
l ROW_NUMBER() 会根据顺序计算
练习:① 建表、导数据、查看数据
② 使用三个函数分别对成绩进行排序
结果如下: (主要看英语学科三个函数的排序区别)
八. 练习
1.练习1
i. 我们有如下的用户访问数据
userId |
visitDate |
visitCount |
u01 |
2017/1/21 |
5 |
u02 |
2017/1/23 |
6 |
u03 |
2017/1/22 |
8 |
u04 |
2017/1/20 |
3 |
u01 |
2017/1/23 |
6 |
u01 |
2017/2/21 |
8 |
u02 |
2017/1/23 |
6 |
u01 |
2017/2/22 |
4 |
ii. 要求使用SQL统计出每个用户的累积访问次数,如下表所示:
用户id |
月份 |
小计 |
累积 |
u01 |
2017-01 |
11 |
11 |
u01 |
2017-02 |
12 |
23 |
u02 |
2017-01 |
12 |
12 |
u03 |
2017-01 |
8 |
8 |
u04 |
2017-01 |
3 |
3 |
① 建表、导数据、查看数据
② 先对Visitdate字段进行变换
③ 对同个用户同个年月的数据进行汇总
④ 进行同用户的消费总额叠加,按照时间顺序
2.练习2
有50W个京东店铺,每个顾客访问任何一个店铺的任何一个商品时都会产生一条访问日志,访问日志存储的表名为visit,访客的用户id为user_id,被访问的店铺名称为shop,请统计:
1)每个店铺的UV(访客数)
2)每个店铺访问次数top3的访客信息。输出店铺名称、访客id、访问次数
第一题做法:
用distinct可以完成,但是不利于优化,建议用以下方法。
① 去重
select shop,userid from visit group by shop,userid;
② 计数
select shop,count(*) uv from
(select shop,user_id from visit group by shop,userid) t1
group by shop;
第二题做法:
先对店铺,用户做一个访客数量计数,再用row_number进行排序,最后筛选出前三名。
3.蚂蚁森林
题目
1.蚂蚁森林植物申领统计
问题:假设2017年1月1日开始记录低碳数据(user_low_carbon),假设2017年10月1日之前满足申领条件的用户都申领了一颗p004-胡杨,剩余的能量全部用来领取“p002-沙柳” 。
统计在10月1日累计申领“p002-沙柳” 排名前10的用户信息;以及他比后一名多领了几颗沙柳。
得到的统计结果如下表样式:
user_id plant_count less_count(比后一名多领了几颗沙柳)
u_101 1000 100
u_088 900 400
u_103 500 …
2、蚂蚁森林低碳用户排名分析
问题:查询user_low_carbon表中每日流水记录,条件为:用户在2017年,连续三天(或以上)的天数里,每天减少碳排放(low_carbon)都超过100g的用户低碳流水。需要查询返回满足以上条件的user_low_carbon表中的记录流水。
例如用户u_002符合条件的记录如下,因为2017/1/2~2017/1/5连续四天的碳排放量之和都大于等于100g:
seq(key) user_id data_dt low_carbon
xxxxx10 u_002 2017/1/2 150
xxxxx11 u_002 2017/1/2 70
xxxxx12 u_002 2017/1/3 30
xxxxx13 u_002 2017/1/3 80
xxxxx14 u_002 2017/1/4 150
xxxxx14 u_002 2017/1/5 101
备注:统计方法不限于sql、procedure、python,java等
第一小题解答:
1.统计每个用户截止到2017/10/1日期总低碳量
select user_id, sum(low_carbon) sum_low_carbon from user_low_carbon where date_format(regexp_replace(data_dt,'/','-'),'yyyy-MM')<'2017-10' group by user_id order by sum_low_carbon desc limit 11;t1
提前进行数据过滤,提高效率。
2.取出胡杨的能量
select low_carbon from plant_carbon where plant_id='p004';t2
3.取出沙柳的能量
select low_carbon from plant_carbon where plant_id='p002';t3
4.计算每个人申领沙柳的棵数
select user_id, floor((sum_low_carbon-t2.low_carbon)/t3.low_carbon) plant_count from t1,t2,t3;t4
5.按照申领沙柳棵数排序,并将下一行数据中的plant_count放置当前行
select user_id, plant_count, lead(plant_count,1,'9999-99-99') over(order by plant_count desc) lead_plant_count from t4 limit 10;t5
(我自己做的时候没有给lead传默认值,故有Null值)
6.求相差的沙柳棵数
select user_id, plant_count, (plant_count-lead_plant_count) plant_count_diff from t5; select user_id, plant_count, lead(plant_count,1,'9999-99-99') over(order by plant_count desc) lead_plant_count from (select user_id, floor((sum_low_carbon-t2.low_carbon)/t3.low_carbon) plant_count from (select user_id, sum(low_carbon) sum_low_carbon from user_low_carbon where date_format(regexp_replace(data_dt,'/','-'),'yyyy-MM')<'2017-10' group by user_id)t1, (select low_carbon from plant_carbon where plant_id='p004')t2, (select low_carbon from plant_carbon where plant_id='p002')t3)t4 limit 10;t5
lead里边第三个参数是为了给最后一条数据添加一个默认值,使其不会成为Null值。
select user_id, plant_count, (plant_count-lead_plant_count) plant_count_diff from (select user_id, plant_count, lead(plant_count,1,'9999-99-99') over(order by plant_count desc) lead_plant_count from (select user_id, floor((sum_low_carbon-t2.low_carbon)/t3.low_carbon) plant_count from (select user_id, sum(low_carbon) sum_low_carbon from user_low_carbon where date_format(regexp_replace(data_dt,'/','-'),'yyyy-MM')<'2017-10' group by user_id)t1, (select low_carbon from plant_carbon where plant_id='p004')t2, (select low_carbon from plant_carbon where plant_id='p002')t3)t4 order by plant_count desc limit 10)t5;
第二小题解答:
解法1的思路:把数据中的前两行以及后两行数据都并列在一起之后,计算与当前行差值,符合条件的行即为所求。
1.过滤出2017年且单日低碳量超过100g
select user_id, date_format(regexp_replace(data_dt,'/','-'),'yyyy-MM-dd') data_dt from user_low_carbon where substring(data_dt,1,4)='2017' group by user_id,data_dt having sum(low_carbon)>=100;t1
2.将前两行数据以及后两行数据的日期放置当前行
select user_id, data_dt, lag(data_dt,2,'1970-01-01') over(partition by user_id order by data_dt) lag2, lag(data_dt,1,'1970-01-01') over(partition by user_id order by data_dt) lag1, lead(data_dt,1,'1970-01-01') over(partition by user_id order by data_dt) lead1, lead(data_dt,2,'1970-01-01') over(partition by user_id order by data_dt) lead2 from t1;
3.计算当前日期跟前后两行时间的差值
select user_id, data_dt, datediff(data_dt,lag2) lag2_diff, datediff(data_dt,lag1) lag1_diff, datediff(data_dt,lead1) lead1_diff, datediff(data_dt,lead2) lead2_diff from t2;t3
4.过滤出连续3天超过100g的用户
select user_id, data_dt from t3 where (lag2_diff=2 and lag1_diff=1) or (lag1_diff=1 and lead1_diff=-1) or (lead1_diff=-1 and lead2_diff=-2);t4
5.关联原表
select user.user_id, user.data_dt, user.low_carbon from t4 join user_low_carbon user on t4.user_id = user.user_id and t4.data_dt = date_format(regexp_replace(user.data_dt,'/','-'),'yyyy-MM-dd');
解法2的思路:对每个用户分组之后按日期排序并添加一列递增的数,用当前日期减去添加的数,判断是否相等的列有多少,如果大于等于3则保留数据。
1.过滤出2017年且单日低碳量超过100g
select user_id, date_format(regexp_replace(data_dt,'/','-'),'yyyy-MM-dd') data_dt from user_low_carbon where substring(data_dt,1,4)='2017' group by user_id,data_dt having sum(low_carbon)>=100;t1
2.按照日期进行排序,并给每一条数据一个标记
select user_id, data_dt, rank() over(partition by user_id order by data_dt) rk from (select user_id, date_format(regexp_replace(data_dt,'/','-'),'yyyy-MM-dd') data_dt from user_low_carbon where substring(data_dt,1,4)='2017' group by user_id,data_dt having sum(low_carbon)>=100)t1;t2
3.将日期减去当前的rank值
select user_id, data_dt, date_sub(data_dt,rk) data_sub_rk from (select user_id, data_dt, rank() over(partition by user_id order by data_dt) rk from (select user_id, date_format(regexp_replace(data_dt,'/','-'),'yyyy-MM-dd') data_dt from user_low_carbon where substring(data_dt,1,4)='2017' group by user_id,data_dt having sum(low_carbon)>=100)t1)t2;t3
4.过滤出连续3天超过100g的用户
select user_id from (select user_id, data_dt, date_sub(data_dt,rk) data_sub_rk from (select user_id, data_dt, rank() over(partition by user_id order by data_dt) rk from (select user_id, date_format(regexp_replace(data_dt,'/','-'),'yyyy-MM-dd') data_dt from user_low_carbon where substring(data_dt,1,4)='2017' group by user_id,data_dt having sum(low_carbon)>=100)t1)t2)t3 group by user_id,data_sub_rk having count(*)>=3;