Hive基础入门总结(基本语法、数据导入导出、基本查询、分区分桶的概念、函数、开窗函数)

目录

一、基本介绍

1.1 Hive基本概念与架构

1.Hive是基于Hadoop的 一个数据仓库工具 可以将结构化的 数据文件映射为一张表 并提供类SQL查询功能

2.Hive本质:将HQL转化成MapReduce程序 自动生成的MR 通常情况下不够智能化

3.Hive不是数据库 他与数据库除了拥有类似的查询语言 再无类似之处

4.Hive是一个数据仓库分析工具 数仓读多写少 Hive是不建议对数据进行添加/修改操作的 所有的数据都是在加载的时候确定好的

5.Hive没有索引 需要扫描整个表 且底层执行的就是MR 因此会有较高的延迟(数据库的延迟一般很低的)

6.Hive基于大数据集群 支持大规模数据处理(数据规模较小的时候 建议使用数据库比如MySQL)

7.迭代式算法无法表达


1.客户端Client:CLI(command-line interface)、JDBC/ODBC(jdbc访问hive)、WEBUI(浏览器访问hive)

2.元数据存储Metastore:表名、表所属的数据库、表的拥有者、列/分区字段、表的类型(是否是外部表)、表的数据所在目录等(默认存储在自带的derby数据库中 只支持一个客户端连接Hive 推荐使用MySQL存储Metastore )

3.使用HDFS进行存储 使用MapReduce交给Yarn进行计算

4.Driver:

(1)解析器(SQL Parser) 将SQL字符串转换成抽象语法树AST 这一步一般都用第三方工具库完成 对AST进行语法分析 比如表是否存在、字段是否存在、SQL语义是否有误

(2)编译器(Physical Plan) 将AST编译生成逻辑执行计划 比如大表join小表

(3)优化器(Query Optimizer) 对逻辑执行计划进行优化 比如→小表join大表(优化不允许影响结果)

(4)执行器(Execution)把逻辑执行计划 → 可以运行的物理计划 对于Hive来说 就是MR/Spark/Tez

1.2 Hive日志和历史记录的配置

mv 把模版后缀去掉 就可以指定log文件的位置了


这里可以查看执行的历史命令


1.默认配置文件hive-default.xml 用户自定义配置文件hive-site.xml 用户自定义配置会覆盖默认配置

注意 Hive是作为Hadoop的客户端启动的 Hive的配置会覆盖Hadoop的配置

配置文件的设定对本机启动的所有Hive进程都有效

2.命令行参数方式 启动Hive时 可以在命令行添加-hiveconf param=value来设定参数

例如hive -hiveconf mapred.reduce.tasks=10 仅对本次hive启动有效

查看参数设置 set mapred.reduce.tasks

3.参数声明方式 可以在HQL中使用SET关键字设定参数

例如 set mapred.reduce.tasks=100 仅对本次hive启动有效

查看参数设置 set mapred.reduce.tasks

上述三种设定方式的优先级依次递增 配置文件<命令行参数<参数声明

1.3 Hive数据类型与集合案例

基本数据类型

Hive的String类型 相当于数据库的varchar类型 该类型是一个可变的字符串

不过它不能 声明最多能存储多少个字符 理论上它可以存储2GB的字符数


集合数据类型


集合的案列:

1.假设某表有如下一行 用JSON格式来表示其数据结构

2.基于上述数据结构 在Hive里创建对应的表 并导入数据

创建本地测试文件person.txt 集合里的元素间用同一个字符表示 这里用"_"

songsong,bingbing_lili,xiao song:18_xiaoxiao song:19,hui long guan_beijing

yangyang,caicai_susu,xiao yang:18_xiaoxiao yang:19,chao yang_beijing

3.Hive上创建测试表test

4.导入文本数据到测试表

load data local inpath '/home/tmp/person.txt' into table test666;

5.访问三种集合列里的数据 以下分别是ARRAY通过下标 MAP通过K找V STRUCT通过.

select friends1,children'xiao song',address.city from test666 where name='songsong';

1.4 类型转化

Hive的原子数据类型是可以进行隐式转换的 TINYINT会自动转换为INT类型

但是Hive不会进行反向转化 INT不会自动转换为TINYINT类型 除非使用CAST操作

1.5 DDL

drop database mydb1; 空库可以直接删

drop database mydb cascade; 有表的库 需要级联


所以这个目录是:

1.default库默认对应的库目录

2.不指定location 默认创建库目录的位置


建库 其实就相当于在MySQL的DBS更新了一条元数据

建表语句

建表的时候 如果没有指定location 当前use哪个库 就在该库目录下创建表目录

表的元数据 在MySQL的TBLS表和SDS可以查到

desc formatted 表;


表不一定就非要在库目录下面

不指定表的location 默认是在库目录下

指定了location 就跟location的目录映射


修改表

修改列的的时候(第二个alter) 要确保列的新类型 ≥ 原来的类型

假设原始数据有5列 建表只建了3个字段 那就从左往右匹配三列(再alter增加一列 就再往右匹配一列)

如果数据只有3列 建表建了5个字段 匹配不上的就是NULL

删除表:drop table xxx

1.6 数据与Hive表映射的三种方式

方式1:把Linux本地数据load到Hive的表(相当于把数据文件上传到Hive对应的表目录下了 )


方式2:直接把对应的数据put到HDFS对应的hive表目录下 就相当于建立了映射关系了


方式3:数据已经在HDFS上了 那么在创建表的同时 直接把表关联到数据所在目录即可

一个表→一个HDFS上的表目录 把数据放到这个表目录下 就相当于对应起来了

总而言之 只要能对应上(无论先有数据还是先有表) 数据就会按建表的规则映射给Hive

1.7 内部表 外部表

alter table xxx set tblproperties('EXTERNAL'='FALSE'); TRUE就是改为内部表

desc formatted xxx 可以查看

drop table test5 删除管理表/内部表 HDFS上对应的目录和下面的数据 全都会被删除

drop table test4 删除外部表 只会把MySQL的元数据删掉(查不到此表了) HDFS上的目录和数据都保留(用的多)

因为一份数据在HDFS上 可能会被hive分析 也可能会被其他分析 hive分析完如果删表 肯定是只删元数据 不删数据本身的


Truncate只能清空管理表/内部表的数据

不能删除外部表中数据(理解:外部表没有删除数据的权限)

删除之后 表还在 只是记录被清空了

1.8 数据导入

load

load data [local] inpath '数据的path' [overwrite] into table student [partition (partcol1=val1,...)];

(1)load data:表示加载数据

(2)local:表示从本地加载数据到hive表(拷贝上传 本地还在);否则从HDFS加载数据到hive表(移动或剪贴)

(3)inpath:表示加载数据的路径

(4)overwrite:表示覆盖表中已有数据 否则表示追加

(5)into table:表示加载到哪张表

(6)student:表示具体的表

(7)partition:表示上传到指定分区

insert

不常用 会跑MR

insert不支持插入部分字段 假如要插入数据到表test test有两个字段id name 则插入的数据必须是1 'aaa'这种的

insert into:以追加数据的方式插入到表或分区 原有数据不会删除

insert overwrite:会覆盖表中已存在的数据

insert into student values(1017,'ss17'),(1018,'ss18'),(1019,'ss19');

insert into student2 select id, name from student ; 把后面的查询结果插入到前面的表(在HDFS上的体现 其实是表目录下多了一个文件)

as select

根据查询结果创建表 查询的结果会添加到新创建的表中

create table student3 as select id, name from student ; 建表的具体字段/内容 根据后面的查询结果

location

建表的时候指定location(HDFS上) 而该location下面已经有数据了 直接建立映射关系

create table student4(id string, name string)

row format delimited fields terminated by '\t'

location '/student4' ;

import

注意:先用export导出后 再将数据导入

import table emptest2 from '/emptest';

1.9 数据导出

insert

insert overwrite local directory '/insert-result1'

row format delimited fields terminated by ':'

select * from emptest ;

如果表中的列的值为null 导出到文件中以后通过\N来表示

hadoop命令

在hive中可以直接用 把HDFS的文件→本地文件系统

hive (default)> dfs -get /user/hive/warehouse/student/student.txt /opt/module/datas/export/student3.txt;

这就相当于是在Linux本地执行hdfs dfs -get XXX

hive -e并重定向

hive -e 'select * from tem11.test1' > /home/tmp/0.txt;

这里的SQL查到什么 就是什么 不会NULL解析成\N

export

元数据和数据都会被导出 但是只能导出到HDFS

导出表到HDFSexport table test999 to '/dd/test999.bak';

再导入这张表到某个库下面 import table newTable from '/dd/test999.bak';

二、查询

2.1 查询语法和数据准备

1.HQL语言大小写不敏感

2.SQL可以写在一行或者多行

3.关键字不能被缩写也不能分行

4.各子句一般要分行写 使用缩进提高语句的可读性


dept:

10 ACCOUNTING 1700

20 RESEARCH 1800

30 SALES 1900

40 OPERATIONS 1700

create table if not exists dept(

deptno int,

dname string,

loc int

)

row format delimited fields terminated by ' ';

load data local inpath '/opt/module/datas/dept.txt' into table dept;
emp:

7369 SMITH CLERK 7902 1980-12-17 800.00 20

7499 ALLEN SALESMAN 7698 1981-2-20 1600.00 300.00 30

7521 WARD SALESMAN 7698 1981-2-22 1250.00 500.00 30

7566 JONES MANAGER 7839 1981-4-2 2975.00 20

7654 MARTIN SALESMAN 7698 1981-9-28 1250.00 1400.00 30

7698 BLAKE MANAGER 7839 1981-5-1 2850.00 30

7782 CLARK MANAGER 7839 1981-6-9 2450.00 10

7788 SCOTT ANALYST 7566 1987-4-19 3000.00 20

7839 KING PRESIDENT 1981-11-17 5000.00 10

7844 TURNER SALESMAN 7698 1981-9-8 1500.00 0.00 30

7876 ADAMS CLERK 7788 1987-5-23 1100.00 20

7900 JAMES CLERK 7698 1981-12-3 950.00 30

7902 FORD ANALYST 7566 1981-12-3 3000.00 20

7934 MILLER CLERK 7782 1982-1-23 1300.00 10

create table if not exists emp(

empno int,

ename string,

job string,

mgr int,

hiredate string,

sal double,

comm double,

deptno int)

row format delimited fields terminated by ' ';

load data local inpath '/opt/module/datas/emp.txt' into table emp;

2.2 比较运算符和逻辑运算符

可以用于JOINON/HAVING/WHERE语句中


查询薪水大于1000 部门是30

hive (default)> select * from emp where sal>1000 and deptno=30;

查询薪水大于1000 或者部门是30

hive (default)> select * from emp where sal>1000 or deptno=30;

查询除了20部门和30部门以外 的员工信息

hive (default)> select * from emp where deptno not IN(30, 20);

2.3 Like和RLike

like:%代表零个或多个字符 _代表一个字符

RLIKE子句是Hive中这个功能的一个扩展 支持正则表达式

案例:

(1)查找名字以A开头的员工信息

hive (default)> select * from emp where ename LIKE 'A%';

(2)查找名字中第二个字母为A的员工信息

hive (default)> select * from emp where ename LIKE '_A%';

(3)查找名字中带有A的员工信息

hive (default)> select * from emp where ename RLIKE 'A';

+:A出现1-n次

*:0-n次

?:0or1次

2.4 分组查询案例

GROUP BY语句通常会和聚合函数一起使用 按照一个或者多个列队结果进行分组 然后对每个组执行聚合操作

也就是分组之后 select后面只能跟组标识(分组字段) 和 聚合函数(分组函数)

计算emp表每个部门的平均工资

hive (default)>select deptno , avg(sal) from emp group by deptno;

计算emp每个部门中每个岗位的最高薪水

hive (default)>select deptno,job,max(sal) from emp group by deptno,job;

计算emp中每个部门最高薪水的那个人

S1.先查下每个部门最高薪水是多少

select deptno , max(sal) max_sal from emp group by deptno;

S2.写法一

隐式内连接 用where过滤(不再建议这么写)

select e.ename,e.sal,e.deptno from emp e , (select deptno , max(sal) max_sal from emp group by deptno) m where e.deptno=m.deptno and e.sal = m.max_sal;

S2.写法二

在连接后过滤 先按deptno连接 得到各部门每个员工的工资情况 再过滤出sal=max_sal的记录

select e.ename,e.sal,e.deptno from emp e join (select deptno , max(sal) max_sal from emp group by deptno) m on e.deptno=m.deptno where e.sal = m.max_sal;

S3.写法三最好

在连接时匹配 emp表的员工 只有工资等于该部门最高工资时才算连接成功

先匹配deptno 再匹配sal=max_sal 都匹配成功才产生结果

写法二三执行计划通常相同

但是e.sal = m.max_sal本质是 e表与m表之间的关联关系 不是单独对某张表的筛选 因此写成on and更可读

select e.ename,e.sal,e.deptno from emp e join (select deptno , max(sal) max_sal from emp group by deptno) m on e.deptno=m.deptno and e.sal = m.max_sal;


where后面不能写分组函数 而having后面可以使用分组函数

having只用于group by分组统计语句

求每个部门的平均薪水大于2000的部门

S1.求每个部门的平均工资

select deptno, avg(sal) from emp group by deptno;

S2.求每个部门的平均薪水大于2000的部门

select deptno, avg(sal) avg_sal from emp group by deptno having avg_sal > 2000;

计算emp中除了CLERK岗位的员工之外 剩下的员工的所在部门的平均工资>1000的部门和平均工资

where在分组前过滤 having在分组后过滤

select deptno,avg(sal) avg_sal from emp where job != 'CLERK' group by deptno having avg_sal >1000;

2.5 join

总结与测试数据

内连接

A inner join B on ... 内连接的结果集取交集

外连接 主表(驱动表) 和 从表(匹配表)

外连接的结果集 = 主表的所有数据 + 从表中与主表匹配的数据

左外连接 A 主 B 从

A left outer join B on ...

B right outer Join A on...

右外连接 A 从 B 主

A right outer join B on ...

B left outer join A on ...

下图是原始数据

内连接 取交集

1.emp 和 dept共有的数据(内连接)

select e.ename, d.deptno from emp e inner join dept d on e.deptno = d.deptno;

A(emp)表的所有数据

2.emp所有的数据 和 dept中与emp匹配的数据(理解成emp特有的+emp和dept的交集更合适)

下图前面部分是匹配的数据 最后的ZS是emp的 必须要有 匹配不上就给NULL

select e.ename, d.deptno from emp e left outer join dept d on e.deptno = d.deptno;

select e.ename, d.deptno from dept d right outer join emp e on e.deptno = d.deptno;

B(dept)表的所有数据

3.dept中所有的数据 和 emp中与dept匹配的数据

select e.ename, d.deptno from dept d left outer join emp e on d.deptno = e.deptno;

select e.ename, d.deptno from emp e right outer join dept d on d.deptno = e.deptno;

emp特有数据

4.emp表独有的数据

先emp左连接 然后去掉交集(交集就是AB都有 通过只要B is null就能去掉交集) 剩下的就是emp所特有的

select e.ename, d.deptno from emp e left outer join dept d on e.deptno = d.deptno where d.deptno is null;

dept特有数据

5.dept表独有的数据

先dept左连接 然后去掉交集 剩下的就是dept所特有的

注意过滤的时候一定要用on的关联条件(如果用e.ename is null过滤 有可能某条关联的记录的ename 恰好是null)

select e.ename, d.deptno from dept d left outer join emp e on d.deptno = e.deptno where e.deptno is null;

满外连接

6.emp和dept 所有的数据(Hive直接支持全外连接/满外连接 但是MySQL是不支持的 需要用union)

union all将结果集拼接到一起 不去重

union 将结果集拼接到一起 去重

S1:把A的所有 和 B的所有拼接起来 union all 中间的交集的14条记录 会重复出现2次

select e.ename, d.deptno from emp e left outer join dept d on e.deptno = d.deptno

union all

select e.ename, d.deptno from dept d left outer join emp e on d.deptno = e.deptno;

S2:union 去重 就完成了

select e.ename, d.deptno from emp e left outer join dept d on e.deptno = d.deptno

union

select e.ename, d.deptno from dept d left outer join emp e on d.deptno = e.deptno;

S3:或者直接用Hive支持的满外连接

select e.ename, d.deptno from emp e full outer join dept d on e.deptno = d.deptno;

A和B特有的数据

7.emp和dept独有的数据

S1:把A独有的 和 B独有的 直接union拼起来

S2:或者 先full 再去掉中间的交集部分(用is null去掉交集 关联条件是null的 那就是特有的)

select e.ename, d.deptno from emp e full outer join dept d on e.deptno = d.deptno

where e.deptno is null or d.deptno is null;

多表join

数据准备

查询 员工名 部门名 位置名

2.6 排序

全局排序oder by (只有一个分区 哪怕设置了三个分区 也只用一个)

select ename, sal*2 twosal from emp order by twosal;

select ename, deptno, sal from emp order by deptno, sal ;
区内排序Sort By 如果不先分区 结果是随机的

set mapreduce.job.reduces=3;

insert overwrite local directory '目录' select * from emp sort by deptno desc;


distribute by类似MR中partition(自定义分区) 结合sort by使用

1.distribute by根据分区字段的hash码 与 reduce的个数进行模除后 余数相同的分到一个区

2.Hive要求DISTRIBUTE BY语句要写在SORT BY语句之前

set mapreduce.job.reduces=3;

insert overwrite local directory '目录' select * from emp distribute by deptno sort by empno desc;
当distribute by和sort by字段相同时 可以使用cluster by方式

但是排序只能是升序排序 不能指定排序规则为ASC或者DESC

select * from emp cluster by deptno;

等价于

select * from emp distribute by deptno sort by deptno;

三、分区表和分桶表

3.1 分区表/分桶表大致概念

1.分区表:分的是目录(细分表目录) 分区其实就是在HDFS上分目录

2.分桶表:分的是一整个数据文件(一整个数据文件 → 拆分成几个文件 也就是几个桶)

3.2 分区表基本操作

创建分区表

注意 分区字段不能 是表中已经存在的数据 可以将分区字段看作表的伪列

create table dept_partition(deptno int, dname string, loc string)

partitioned by (day string)

row format delimited fields terminated by ' ';
数据准备


加载数据到分区表中

load data local inpath '/home/tmp/logs/dept_20200401.log' into table dept_partition partition(day='20200401');

load data local inpath '/home/tmp/logs/dept_20200402.log' into table dept_partition partition(day='20200402');

load data local inpath '/home/tmp/logs/dept_20200403.log' into table dept_partition partition(day='20200403');


多分区联合查询

select * from dept_partition where day='20200401'

union

select * from dept_partition where day='20200402'

union

select * from dept_partition where day='20200403';

or

select * from dept_partition where day='20200401' or day='20200402' or day='20200403' ;
查看分区表有多少分区

show partitions dept_partition;
加分区 HDFS对应会新增几个目录

alter table dept_partition add partition(day='20200404');

alter table dept_partition add partition(day='20200405') partition(day='20200406') ;

删分区 如果是一个内部分区表 HDFS的数据都会被删 否则HDFS的数据不会被删

alter table dept_partition drop partition(day='20200404');

alter table dept_partition drop partition(day='20200405') , partition(day='20200406');

3.3 二级(多级)分区

create table dept_partition2(deptno int, dname string, loc string)

partitioned by (day string ,hour string )

row format delimited fields terminated by '\t' ;

show partitions dept_partition2;

3.4 先有数据 和分区表如何关联

手动创建分区目录 手动上传数据到分区目录 再手动修复

按照下图的已知分区规则 去创建对应的目录

hadoop fs -mkdir /user/hive/warehouse/mydb.db/dept_partition/day=20200404

hadoop fs -put /opt/module/hive-3.1.2/datas/dept_20200403.log /user/hive/warehouse/mydb.db/dept_partition/day=20200404

手动修复分区

msck repair table dept_partition;

手动创建分区目录 手动上传数据到分区目录 然后添加一个分区和它对应

创建目录 上传文件

hadoop fs -mkdir /user/hive/warehouse/mydb.db/dept_partition/day=20200405

hadoop fs -put /opt/module/hive-3.1.2/datas/dept_20200403.log /user/hive/warehouse/mydb.db/dept_partition/day=20200405

添加一个对应的分区(主要是要让元数据认识这个分区 就能select到)

alter table dept_partition add partition(day='20200405');

show partitions dept_partition;

手动创建分区目录 load数据到分区中

创建目录 即使和分区目录的规则一模一样 show partitions看元数据的时候 也是没有识别到这个分区的

hadoop fs -mkdir /user/hive/warehouse/mydb.db/dept_partition/day=20200406

load 就可以加载元数据 就能查到元数据 也能select到了

load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200403.log' into table dept_partition partition(day='20200406')

3.5 动态分区

开启动态分区参数设置

关系型数据库中 对分区表Insert数据时候 数据库自动会根据分区字段的值 将数据插入到相应的分区中

Hive中也提供了类似的机制 即动态分区

1.开启动态分区参数设置

hive.exec.dynamic.partition=true

2.动态分区的模式默认strict 必须指定至少一个分区为静态分区

hive.exec.dynamic.partition.mode=nonstrict

3.在所有执行MR的节点上 最大一共可以创建多少个动态分区

hive.exec.max.dynamic.partitions=1000

4.在每个执行MR的节点上 最大可以创建多少个动态分区

比如:源数据中包含了一年的数据 即day字段有365个值 那么该参数就需要设置成大于365

hive.exec.max.dynamic.partitions.pernode=100

5.整个MR Job中 最大可以创建多少个HDFS文件

hive.exec.max.created.files=100000

6.当有空分区生成时 是否抛出异常 一般不需要设置 默认false

hive.error.on.empty.partition=false

load数据到动态分区

其实就是根据分区字段来匹配 判断到哪个分区

匹配上了 就插入该分区 否则就新建一个分区 再插入

create table dept_partition_dy(deptno int, dname string, loc string)

partitioned by (day string)

row format delimited fields terminated by ' ';

下图是数据样本

1.load本地数据到动态分区表 这里的load 会根据分区字段计算到哪个分区 所以会跑一个MR

分区字段是伪列 不写入HDFS数据文件 只存在于HDFS目录名 + Hive元数据中

load data local inpath '/home/tmp/dept.txt' into table dept_partition_dy666;

2.也可以直接把DHFS上的数据load到动态分区

hdfs dfs -put ./dept.txt /

load data inpath '/dept.txt' into table dept_partition_dyjiqun;

insert数据到动态分区

现在的load功能更强了 会根据不同的表判断是直接上传数据 还是要跑一个MR

老版本的load 功能非常单一 单纯就是上传数据 不会跑MR去动态分区 如何解决?

创建一个含有分区字段的中间表(普通表) → 把数据load到普通表 → 把普通表的数据insert到分区表 跑MR 数据去对应分区

创建普通表 但是包含了day这个字段(也就是分区字段 但这里仅作为一个普普通通的字段 )

create table dept_dy(deptno int, dname string, loc string,day string) row format delimited fields terminated by '\t';

load数据到普通表

load data local inpath 'XXX.txt' into table dept_dy ;

创建一个干净的分区表 把普通表的数据insert进来

dept_partition_dy 分区表 建表时指定了分区字段

dept_dy 普通无分区表 表内包含分区字段

insert into 分区表 select * from 普通表 执行时 Hive会自动根据分区字段的值创建对应的分区目录、写入分区元数据

所以插入前空分区 插入后show partitions就能看到分区

3.6 分桶表

数据准备


分桶的字段必须是表中的某个字段 而不能单独再去指定

create table student_bucket(id int, name string )

clustered by (id)

into 4 buckets

row format delimited fields terminated by ' ' ;

load data inpath '/student.txt' into table student_bucket;

3.7 抽样查询

对于非常大的数据集 有时用户需要使用的是一个具有代表性的查询结果 而不是全部结果

Hive可以通过对表进行抽样来满足这个需求 TABLESAMPLE(BUCKET x OUT OF y)

按照empno分成四个桶 抽样其中的一个(这样抽出来的数据就是原数据里到处抽一点的感觉 很均匀 不失真)

select * from emp tablesample (bucket 1 out of 4 on empno);

=x的值必须小于等于y的值 否则

FAILED: SemanticException Error 10061: Numerator should not be bigger than denominator in sample clause for table stu_buck

四、函数

4.1 系统内置函数

查看系统自带的函数 show functions;

显示自带的函数的用法 desc function upper;

详细显示自带的函数的用法 desc function extended upper;

4.2 常用函数

常用日期函数

  1. unix_timestamp:返回当前或指定时间的时间戳

    select unix_timestamp();

    select unix_timestamp("2020-10-28",'yyyy-MM-dd');

  2. from_unixtime:将时间戳转为日期格式

    select from_unixtime(1603843200);

  3. current_date:当前日期

    select current_date;

  4. current_timestamp:当前的日期加时间

    select current_timestamp;

  5. to_date:抽取日期部分(2020-10-28)

    select to_date('2020-10-28 12:12:12');

  6. year:获取年

    select year('2020-10-28 12:12:12');

  7. month:获取月

    select month('2020-10-28 12:12:12');

  8. day:获取日

    select day('2020-10-28 12:12:12');

  9. hour:获取时

    select hour('2020-10-28 12:13:14');

  10. minute:获取分

    select minute('2020-10-28 12:13:14');

  11. second:获取秒

    select second('2020-10-28 12:13:14');

  12. weekofyear:当前时间是一年中的第几周

    select weekofyear('2020-10-28 12:12:12');

  13. dayofmonth:当前时间是一个月中的第几天

    select dayofmonth('2020-10-28 12:12:12');

  14. months_between: 两个日期间的月份

    select months_between('2020-04-01','2020-10-28');

  15. add_months:日期加减月

    select add_months('2020-10-28',-3);

  16. datediff:两个日期相差的天数

    select datediff('2020-11-04','2020-10-28');

  17. date_add:日期加天数

    select date_add('2020-10-28',4);

  18. date_sub:日期减天数

    select date_sub('2020-10-28',4);

  19. last_day:日期的当月的最后一天

    select last_day('2020-02-30');

  20. date_format(): 格式化日期

    select date_format('2020-10-28 12:12:12','yyyy/MM/dd HH:mm:ss');

常用取整函数

  1. round: 四舍五入

    select round(3.14);

    select round(3.54);

  2. ceil: 向上取整

    select ceil(3.14);

    select ceil(3.54);

  3. floor: 向下取整

    select floor(3.14);

    select floor(3.54);

常用字符串操作函数

  1. upper: 转大写

    select upper('low');

  2. lower: 转小写

    select lower('low');

  3. length: 长度

    select length("zcy");

  4. trim: 前后去空格

    select trim(" zcy ");

  5. lpad: 向左补齐,到指定长度

    select lpad('zcy',9,'g');

  6. rpad: 向右补齐,到指定长度

    select rpad('zcy',9,'g');

  7. regexp_replace:使用正则表达式匹配目标字符串 匹配成功后替换

    SELECT regexp_replace('2020/10/25', '/', '-');

集合操作

  1. size: 集合中元素的个数

    select size(friends) from test3;

  2. map_keys: 返回map中的key

    select map_keys(children) from test3;

  3. map_values: 返回map中的value

    select map_values(children) from test3;

  4. array_contains: 判断array中是否包含某个元素

    select array_contains(friends,'bingbing') from test3;

  5. sort_array: 将array中的元素排序

    select sort_array(friends) from test3;

多维分析

grouping sets:多维分析


需求: 统计每个部门各多少人, 男女各多少人, 每个部门中男女各多少人

select deptno, sex ,count(id) num from testgrouping group by deptno,sex

grouping sets( (deptno,sex), sex , deptno )

4.3 空字段赋值

NVL

给值为NULL的数据赋值 它的格式是NVL( value,default_value)

如果value为NULL 则NVL函数返回default_value的值 否则返回value的值

如果两个参数都为NULL 则返回NULL

4.4 CASE和IF

数据

需求 求出不同部门男女各多少人


先按照部门分组是确定的

分组之后 再按照需求 在每个组内用sum

根据当前值 给一个对应的结果

select

dept_Id,

sum(case sex when '男' then 1 else 0 end ) man,

sum(case sex when '女' then 1 else 0 end ) female

from emp_sex

group by dept_Id ;


只有两个分之 可以直接用if

select

dept_Id,

sum(if(sex='男',1,0)) man,

sum(if(sex='女',1,0)) female

from emp_sex

group by dept_Id;

4.5 行转列

相关函数

concat(): 字符串拼接

concat_ws(): 字符串拼接 但必须是String类型的字段或者数组

collect_set(): 去重汇总某一列

collect_list(): 汇总 但是不会去重

行转列案例

看到这个需求 首先想到的就是先把星座和血型拼在一起

select name,concat_ws(',',constellation,blood_type) c_b from person_info;

然后自然会想到要把c_b作为一个分组字段 先分好组 这个时候上面那个就要作为一个临时表了(子查询)

对于同一个c_b组的(也就是同一个星座,血型的) 再把他们的名字汇总起来即可 → 对名字collect_set

select tmp.c_b , collect_set(tmp.name) as names

from

(select name,concat_ws(',', constellation,blood_type) c_b from person_info) as tmp

group by tmp.c_b;

进一步地 需求想要的分隔符是|

也就是把name汇总成一行的时候 间隔符要用| → 想到用concat_ws('|',汇总的name行)

select tmp.c_b , concat_ws('|' , collect_set(tmp.name) )as names

from

(select name,concat_ws(',', constellation,blood_type) c_b from person_info) as tmp

group by tmp.c_b;

4.6 列转行

explode() 将数组或者map 拆分成多行

lateral view 侧写表(虚拟表) 它能记录拆分前的对应关系


这个需求 首先是能想到先把category这一列拆开的 但是explode只支持数组 集合 → 先用split

select explode(split(category, ',')) from movie_info;

如果直接查询movie 可能出现 一行记录 就要有一个电影对多个拆分的category的情况 错误

需要让侧写表记住 某个电影对应的category 被拆分成了哪几个

select movie,category_name

from movie_info

lateral view explode(split(category, ',')) tmp_table as category_name;

4.7 窗口函数/开窗函数

0.原始数据和相关函数

load data local inpath '/home/tmp/business.txt' into table business;

1.查询在2017年4月份购买过的顾客及总人数

① 查出2017年4月份购买过的顾客

select name, orderdate, cost from business where month(orderdate) = '4'

or

select name, orderdate, cost from business where substring(orderdate,0,7) = '2017-04'

② 按照name分组,求count 还是不对 这是对每个组内的聚合

select t1.name,count(t1.name)

from (select name, orderdate, cost from business where month(orderdate) = '4') t1

group by t1.name;

第一种方式:继续顺着一开始的思路往下写

select t1.name, count(t1.name) over()

from

(select name, orderdate, cost from business where substring(orderdate,0,7) = '2017-04') t1

group by t1.name;

第二种方式:分析函数count 不一定非要作用在组内 可以作用于窗口

select distinct(t1.name)

from (select name, orderdate, cost from business where substring(orderdate,0,7) = '2017-04') t1

开窗之前 得到的就是上图这个数据集结果

则每条数据的默认窗口大小 就是当前数据集的大小

所以每个数据基于开窗函数求count的时候 就都的到2

select t2.name, count(t2.name) over() from

(

select distinct(t1.name) from

(select name, orderdate, cost from business where substring(orderdate,0,7) = '2017-04') t1

) t2

2.查询顾客的购买明细 及 所有顾客的月购买总额

在窗口里按月份分区

select

name, orderdate, cost , sum(cost) over(partition by substring(orderdate,0,7)) month_cost

from business

3.查询顾客的购买明细 及 每个顾客的月购买总额

按照人 然后按照月份 进行分区

select

name, orderdate, cost , sum(cost) over(partition by name , substring(orderdate,0,7)) everyone_month_cost

from business

4.将每个顾客的cost按照日期进行累加

按照顾客name分区 再排序

每条数据的窗口大小指定为 当前行所属分区的开始~当前行

select

name,

orderdate,

cost,

sum(cost) over(partition by name order by orderdate rows between UNBOUNDED PRECEDING and CURRENT ROW) leijia

from business;

or 省略指定的rows 有了order by默认就是当前分区的开始到当前行

select

name,

orderdate,

cost,

sum(cost) over(partition by name order by orderdate) leijia

from business;

5.将所有顾客的cost按照日期进行累加

所有顾客了 那就不需要按照name分区 直接按日期排序 然后累加cost即可

这里因为有order by 所以可以省略指定rows

每条数据的窗口大小 默认从当前数据集的开始 到 当前行

select

name,

orderdate,

cost,

sum(cost) over(order by orderdate) leijia

from business;


补充:

select

name,

orderdate,

cost,

sum(cost) over(order by orderdate rows between UNBOUNDED PRECEDING and CURRENT ROW) f_c,

sum(cost) over(order by orderdate rows between 1 PRECEDING and CURRENT ROW ) p_c,

sum(cost) over(order by orderdate rows between 1 PRECEDING and 1 FOLLOWING ) p_n,

sum(cost) over(order by orderdate rows between CURRENT ROW and 1 FOLLOWING ) c_n,

sum(cost) over(order by orderdate rows between CURRENT ROW and UNBOUNDED FOLLOWING ) c_l

from business

6.查询每个顾客上次的购买时间 及 下一次的购买时间

lag lead就是把某个字段的前第几行或者后第几行的数据 提取到当前行

select

name,

cost,

orderdate current_orderdate,

lag(orderdate ,1 ,'1970-01-01') over(partition by name order by orderdate) p_orderdate,

lead(orderdate ,1 ,'9999-01-01') over(partition by name order by orderdate) f_orderdate

from business;

7.查询前20%时间的订单信息

NTILE(n) 把有序窗口的行分发到指定数据的组中 各个组有编号 编号从1开始

对于每一行 NTILE返回此行所属的组的编号

先排好序 然后分成五组

select

name,

orderdate,

cost,

ntile(5) over(order by orderdate ) gid

from business;

把上图的表作为一个子查询 查询where gid = 1

select t1.name, t1.orderdate,t1.cost ,t1.gid

from

(select name, orderdate, cost, ntile(5) over(order by orderdate ) gid from business) as t1

where t1.gid = 1 ;

小结

1.over(): 会为每条数据都开启一个窗口 默认的窗口大小就是当前数据集的大小 →需求1

2.over(partition by ...) : 会按照指定的字段进行分区 将分区字段相同的数据划分到同一个区

每个区中的每条数据 都会开启一个窗口 每条数据的窗口大小默认为当前分区数据集的大小 →需求23

3.over(order by ...) : 会在窗口中按照指定的字段对数据进行排序 会为每条数据都开启一个窗口 默认的窗口大小为 从数据集开始 到 当前行 → 需求45

4.over(partition by ... order by ...) :先按照指定的字段进行分区 再在每个区中会按照指定的字段进行排序

最后为每条数据都开启一个窗口 默认的窗口大小为 当前分区从数据集开始到当前行 → 需求45

5.over(partition by ... order by ... rows between ... and ...) : 自己指定每条数据的窗口大小 → 需求45

关键字小结

order by 全局排序 or 窗口中排序

distribute by 分区

sort by 区内排序

cluster by 分区排序(distribute by 和 sort by 是同一个字段 )

partition by 窗口中分区

partitioned by 建表指定分区字段

clustered by 建表指定分桶字段

4.8 排名函数

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

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

ROW_NUMBER() 会根据顺序计算

需求: 按照学科进行排名


select

name,

subject,

score,

rank() over(partition by subject order by score desc ) rk1,

dense_rank() over(partition by subject order by score desc ) rk2,

row_number() over(partition by subject order by score desc ) rk3

from score;