作者:邱晨
一、常用函数
-
数值相关
-
字符串转浮点数、整数
select cast (bj_score as bigint), bj_score from dcs_input_tab_bj_score where etl_date = '2019-09-10' limit 50; --字符串转整型 select cast (bj_score as double), bj_score from dcs_input_tab_bj_score where etl_date = '2019-09-10' limit 50; --字符串转浮点型 --如需精确指定小数位数,数值精度可以按以下方式来做转换: select cast (bj_score as decimal(18, 2)), bj_score from dcs_input_tab_bj_score where etl_date = '2019-09-10' limit 50;
说明:对于数据分析人员来说,对数值类型字段的处理非常重要,尤其是数值保存的精度将决定最后的统计结果,而hive中同步数据时,基本都是以String类型来做保存每个字段。因此,当你在SQL中做数值统计,大小比较操作时,这几个函数尤其重要。
对于为什么这么处理如果你有猎奇心想知道具体原因,有以下三个途径:- 上百度查资料,搞懂关于大数据的一些基本常识。
- 过来当面请教我。
- 你完全可以就当做这是个默认的游戏规则吧,数据进Hive(贴源同步),任何类型通常都是以string类型来保存,不要去思考为什么,按这个规则做相应的数据转换即可。
当然,企业级数据仓库(EDW)的建设工作任重而道远,严格来说,贴源同步过来的数据(ods层)一般是不会给业务人员直接使用的,往往需要经过ETL开发工程师经过数据清洗、转换,并定义好公共维度,以主题域的形式组织好数据(dwd明细层、dws轻度汇总层)。最后,BI工程师会以dwd层、dws层及少量ods层数据为基础,按业务需求加工成宽表,这个时候数据进入DM层,也就是我们通常说的数据集市层。DM层的数据会按业务组织通常来划分,比如供风控组为主使用的风险集市,供营销组使用的客户集市。所以,数据挖掘工程师,业务分析师以及报表开发团队真正在使用的往往是数据集市层的数据,这些数据都是经过清洗转换并重新组织的干净数据,使用者往往无需担心脏数据这类问题。
-
数值类运算函数举例
- round(x, d) 保留d位小数,对后面的做四舍五入。
select round(976.56374123, 5); --对第5位后的做四舍五入
- is_nan(x) 判断浮点数是为为空,返回true或者false。
select is_nan(976.56374123);
- rand() 随机生成一个随机浮点数。
select rand();
还有很多科学计算类(包括统计相关)的函数,这里不一一列举了,如需使用可以查看相关文档:
+ 官方文档连接
+ 阿里云文档连接
当然,有个前提非常重要:你必须确保的字段是数值类型或者已经被转换为数值类型。最好是对于需要做数值聚合的字段,诸如sum、avg、max等也先转为数值类型比较好,以免垃圾数据影响最终结果。
-
-
时间相关
- 字符串转时间
说明:必须格式为yyyy-MM-dd的字符串才可以被转换为date类型,必须格式为yyyy-MM-dd HH:mm:ss的字符串才可以被转换为timestamp类型,date_parse转换后的字段类型都是timestamp类型,如果上面的示例时间单位标识符不满足你的需求,你可以再上这里查阅你所需要的。-- 固定格式转换 select cast('2019-09-05' as date); select cast('2019-09-05 09:50:55' as timestamp); select date('2019-09-05'); select date(cast('2019-09-05 09:50:55' as timestamp)); -- 自定义格式转换 select date_parse('20190905', '%Y%m%d'); select date_parse('2019-09-05', '%Y-%m-%d'); select date_parse('06/30/2019', '%m/%d/%Y'); select date_parse('2019-09-05 09:50:55', '%Y-%m-%d %H:%i:%s'); select date_parse('2019-09-05 09:50', '%Y-%m-%d %H:%i'); select date_parse('2019-09-05 09', '%Y-%m-%d %H');
值得一提的是,date(x)这个函数不仅可以将符合格式的字符串转为date类型,还能将timestamp类型类型也转变为date类型。 - 时间转字符串
说明:必须是date或者timestamp类型才能使用format_datetime函数进行转换。select format_datetime(cast('2019-09-05 09:50:55' as timestamp),'yyyy-MM-dd HH:mm:ss'); select format_datetime(cast('2019-09-05 09:50:55' as timestamp),'yyyy-MM-dd 00:00:00'); select format_datetime(cast('2019-09-05 09:50:55' as timestamp),'yyyy-MM-dd'); select format_datetime(date('2019-09-05'),'yyyy-MM-dd 00:00:00');
重点要记住:通常这种场景一般是hive里面保存的时间字符串不满足我们的格式需求,我们在用presto查询hive数据的时候,需要先将改端转成timestamp或者date类型,然后再使用format_datetime函数转换成需要的格式,如果先转成date类型,需要考虑后面的小时分钟数值会丢失的问题,比如你不需要后面的小时数、分钟数么? - 时间比较
date_diff函数第一个参数为时间单位,如:'year'、'month'、'week'、'day'、'hour'、'minute'、'second'。后面两个为date或者timestamp类型字段,以计算出两个时间字段的该单位的差。date类型仅支持day以及day以上的单位,无法支持hour级及以下的时间比较。timestamp类型则全部支持。--date类型字段之间进行比较 select date_diff('year',cast('2018-11-05' as date),cast('2019-12-07' as date)); select date_diff('month',cast('2019-11-05' as date),cast('2019-12-07' as date)); select date_diff('week',cast('2019-11-05' as date),cast('2019-12-07' as date)); select date_diff('day',cast('2019-11-05' as date),cast('2019-11-07' as date)); --timestamp类型字段之间进行比较 select date_diff('month',cast('2019-09-05 09:50:55' as timestamp),cast('2019-09-07 09:50:55' as timestamp)); select date_diff('day',cast('2019-09-05 09:50:55' as timestamp),cast('2019-09-07 09:50:55' as timestamp)); select date_diff('hour',cast('2019-09-05 09:50:55' as timestamp),cast('2019-09-07 09:50:55' as timestamp)); select date_diff('minute',cast('2019-09-05 09:50:55' as timestamp),cast('2019-09-07 09:50:55' as timestamp)); select date_diff('second',cast('2019-09-05 09:50:55' as timestamp),cast('2019-09-07 09:50:55' as timestamp)); select date_diff('hour',cast('2019-09-05' as date),cast('2019-09-07 09:50:55' as timestamp)); select date_diff('minute',cast('2019-09-05' as date),cast('2019-09-07 09:50:55' as timestamp)); --date类型与timestamp类型字段进行比较 select date_diff('day',date('2019-09-05'),cast('2019-09-07 09:50:55' as timestamp)); select date_diff('hour',date('2019-09-05'),cast('2019-09-07 09:50:55' as timestamp)); select date_diff('minute',date('2019-09-05'),cast('2019-09-07 09:50:55' as timestamp));
但是,要记住:date类型和timestamp类型可以相互进行比较计算,且不仅支持day级以上单位,hour级及以下的均支持。 - 时间加减
timestamp类型可以支持增加或减少数天、数小时、数分钟或者数月、数年,但是date类型仅支持增加或减少数天、数月、数年。select cast('2019-09-05 09:50:55' as timestamp) + interval '2' day; select cast('2019-09-05 09:50:55' as timestamp) - interval '2' day; select cast('2019-09-05 09:50:55' as timestamp) + interval '2' hour; select cast('2019-09-05 09:50:55' as timestamp) + interval '2' minute; select cast('2019-09-05 09:50:55' as timestamp) + interval '2' year; select date('2019-09-05') + interval '2' day; select date('2019-09-05') - interval '2' day; select date('2019-09-05') + interval '2' month; select date('2019-09-05') + interval '2' year;
重点来了:它们做了时间加减后,字段类型仍然是之前的类型,比如date类型字段增加一天后,仍然是date类型字段。 - where条件时间字段对比
timestamp类型可以和date类型做大小比较判断的。select .... from .... where cast('2019-09-05 09:50:55' as timestamp) > date('2019-09-05') limit 10; select .... from .... where cast('2019-09-05 09:50:55' as timestamp) >= cast('2019-09-05 09:50:55' as timestamp) select .... from .... where cast('2019-09-05 00:00:00' as timestamp) = date('2019-09-05') limit 10; select cast('2019-09-05 00:00:00' as timestamp) = date('2019-09-05');
重点中的重点来了:如果需要做时间类型字段比较,请转换成timestamp或者date类型再进行比较,因为时间格式的字符串类型字段相互比较不会报错,但是转成时间类型字段再比较我觉得会更严谨吧!重要的事情说三遍,presto对字段类型是非常敏感的,无论是数值还是时间类型都需要注意这个问题。当然,个人的想法是,这种强类型管理是好事,对于数据分析来说,它更能保证我们做统计分析最终结果的准确性。
顺带说一下,cast('2019-09-05 00:00:00' as timestamp)与date('2019-09-05')是相等的,不信你运行上面SQL的最后一句试试。 - 其他重要时间函数及用法
select year(date('2019-10-20')); --获得该字段的年份 select day(cast('2019-09-05 00:00:00' as timestamp)); --获得该字段的日期(day of month) select day_of_week(date('2020-01-16')); --获得该日期是本周第几天 select quarter(cast('2019-09-05 00:00:00' as timestamp)); --获得季度 select week_of_year(date('2020-01-16')); --以上函数对于date和timestamp类型字段都适用。 select current_date; --获得今天的日期 select current_date - interval '1' day; --昨天 select format_datetime(current_date,'yyyy-MM-01'); -- 本月一号 select format_datetime(current_date - interval '1' month,'yyyy-MM-01'); -- 上月一号 select current_timestamp; --获得当前时间 select format_datetime(current_timestamp,'yyyy-MM-dd HH:mm:ss'); --当前时间格式化字符串 select format_datetime(current_timestamp,'yyyy-MM-dd 00:00:00'); select format_datetime(current_timestamp,'yyyy-MM-dd HH:00:00'); --当前小时格式化 select format_datetime(current_timestamp - interval '1' hour,'yyyy-MM-dd HH:00:00'); --上一小时格式化
- 字符串转时间
-
JSON处理
-
JSON字符串处理
-- 我们假设有个表t1,里面有个字段a1,其中一行数据为'{"name":"王二","sex":"男","age":"25"}' with t1 as ( select '{"name":"王二","sex":"男","age":"25"}' as a1 ) select json_extract(a1, '$.age') from t1; -- 深度提取 with t1 as ( select '{"name":"王二","location": {"province": "广东", "city": "深圳"},"sex":"男","age":"25"}' as a1 ) select json_extract(a1, '$.location.city') from t1;
-
JSON数组处理
-- 提取数组中的第一个元素的值 with t1 as ( select '[{"name":"王二","sex":"男","age":"25"},{"name":"李四","sex":"男","age":"47"}]' as a1 ) select json_array_get(a1, 0) from t1; -- 提取数组中的第一个元素(JSON)中的key为age的值 with t1 as ( select '[{"name":"王二","sex":"男","age":"25"},{"name":"李四","sex":"男","age":"47"}]' as a1 ) select json_extract(json_array_get(a1, 0), '$.age') from t1;
-
-
其他重要函数
- 转换相关
-- try_cast 如果你想把某个列转换成你需要的类型,如果遇到不符合要求的数据你会这么处理呢,try_cast会帮你将不符合要求的转换成null: with t1 as ( select 'ss' as a1 union all select '22' as a1 ) select try_cast(a1 as bigint) as a1 from t1; -- typeof 判断列的类型 with t1 as ( select 'ss' as a1 union all select '22' as a1 ) select typeof(a1) as a1 from t1;
- 条件表达式
-- if(condition, v1) 满足条件则为v1,否则为null with t1 as ( select 45 as a1 union all select 33 as a1 union all select 22 as a1 ) select if(a1 > 30, a1) as a1 from t1; -- if(condition, v1, v2) 满足条件则为v1,否则为v2 with t1 as ( select 45 as a1 union all select 33 as a1 union all select 22 as a1 ) select if(a1 > 30, a1, 0) as a1 from t1; -- coalesce(value1, value2[, ...]) 返回第一个非空的数值 with t1 as ( select 45 as a1, 490 as a2, 17 as a3 union all select null as a1, 220 as a2, 2322 as a3 union all select null as a1, null as a2, 144 as a3 ) select coalesce(a1, a2, a3) as a from t1; -- nullif(v1, v2) 当v1=v2时返回null,否则返回v1 select nullif('ss', 'tt'); -- 得到ss select nullif(12, 12); -- 得到null
- 转换相关
二、Presto与Hive使用参照对比
总体来说,Presto SQL是属于标准SQL,和平时我们使用业界关系型数据库的写法差不多,而Hive SQL则是标准SQL的一个变种,属于Like-SQL的一种,我们通常给它命名为:HiveQL或者HQL。下面主要介绍最关键的几点差异,由于时间有限,后续会不断总结并补充进来更多的差异点进来。
-
INSERT用法区别
-
Presto如何实现Hive中的insert overwrite
Hive可以支持insert into(增量插入)和insert overwrite(覆盖全表或者全分区写入), 但是Presto仅支持insert into,如果需要做覆盖写入操作,只能先truncate,再使用insert into-- Hive语句 insert overwrite 分区表 insert overwrite table t1 partition (etl_date='2019-10-20') select id, name, age from t2; -- Hive语句 insert overwrite 全表 insert overwrite table t1 select id, name, age from t2; -- Presto实现上述功能 overwrite 分区表 delete table t1 where etl_date='2019-10-20'; insert into t1 (id, name, age, etl_date) select id, name, age, '2019-10-20' as etl_date from t2; --没错,这就是Presto插入到某个分区的方式,分区字段在这 没 当作是一个字段了 -- Presto实现上述功能 overwrite 全量表 delete table t1; insert into t1 (id, name, age, etl_date) select id, name, age, '2019-10-20' as etl_date from t2;
-
Presto其他支持的地方
-- Presto支持部分字段写入 insert into t1 (id, name) select id, name from t2; --presto支持打乱列的顺序,不必像hive那样严格按书序写入,不允许自定义顺序 insert into t1 (name, id, etl_date) select name, id, '2019-10-20' as etl_date from t2;
-
-
CREATE语句差异
-
HIVE建表方式
-- 方式一 指定列、分区及存储格式元信息 CREATE TABLE `dw.utas_tags_change_log`( `cust_no` string COMMENT '客户号', `product_code` string COMMENT '产品号', `contract_no` string COMMENT '合同号', `tag_value_old` string COMMENT '标签值old', `tag_value_new` string COMMENT '标签值new', `change_date` string COMMENT '变更日期') COMMENT '用户标签历史变更表' PARTITIONED BY (`data_date` string COMMENT '数据日期', `tag_name` string COMMENT '标签名称') stored as ORC; -- 方式二 从其他表克隆 CREATE TABLE tmp_qiu1 like tmp_qiu; -- 方式三 CTAS CREATE TABLE tmp_qiu1 AS select id, name, age from tmp_qiu where etl_date = '2019-10-20';
-
Presto建表方式
-- 如果需要利用Presto按上述HIVE中的 CTAS方式建表,需要这样操作: create table IF NOT EXISTS tmp_qiu1 with (format = 'PARQUET') as select id, name, age from tmp_qiu where etl_date = '2019-10-20'; -- 还可以这样 create table tmp_qiu1 with (format = 'ORC') as select id, name, age from tmp_qiu where etl_date = '2019-10-20'; -- 查看建表DDL语句 show create table tmp_qiu1; -- 所以,如果你想创建能被hive访问的表,你可以先查看人家的建表语句,这是个小技巧,请收藏
-
-
其他写法差异(未完成,待后续补充)
-
JSON处理方面
--这是presto的: with t1 as ( select '{"name":"王二","sex":"男","age":"25"}' as a1 ) select json_extract(a1, '$.age') from t1; --这是Hive的: with t1 as ( select '{"name":"王二","sex":"男","age":"25"}' as a1 ) select get_json_object(a1, '$.age') from t1; --JSON数组方面差异 --这是presto的: with t1 as ( select '[{"name":"王二","sex":"男","age":"25"},{"name":"李四","sex":"男","age":"47"}]' as a1 ) select json_array_get(a1, 0) from t1; --这是Hive的: with t1 as ( select '[{"name":"王二","sex":"男","age":"25"},{"name":"李四","sex":"男","age":"47"}]' as a1 ) select get_json_object(a1, '$.[0].age') from t1; -- 提取数组中的第一个元素(JSON)中的key为age的值 --这是presto的: with t1 as ( select '[{"name":"王二","sex":"男","age":"25"},{"name":"李四","sex":"男","age":"47"}]' as a1 ) select json_extract(json_array_get(a1, 0), '$.age') from t1; --这是Hive的: with t1 as ( select '[{"name":"王二","sex":"男","age":"25"},{"name":"李四","sex":"男","age":"47"}]' as a1 ) select get_json_object(a1, '$.[0].age') from t1;
-