1. 时间戳问题
假设我们 mysql 数据库中有张表叫 student ,它有一个字段是 created(类型是 long 或 int)。mysql 数据库的 student 表在 hive 中对应的表为 dw_student,dw_student 中 created 这个字段对应的类型是 string。这个时候我们在写 hive sql 的时候,如果想比较时间戳,需要把 hive 表中的 created_at 转换为时间戳去比较,否则会查询不到任何数据且不报错。
select * from dw_student where UNIX_TIMESTAMP(created)<=1588262400
created 字段类型为 int ,created 字段值为时间戳(单位:秒),把 created 转换为字符串(格式为:yyyy-MM-dd HH:mm:ss)可以使用下面这个函数:
- from_unixtime(created, ‘yyyy-MM-dd HH:mm:ss’)
presto 和 hive 时间格式转换
以下内容来自转载:https://blog.csdn.net/cdyjy_litao/article/details/80252327
北京时间格式 to unix时间格式
数据格式:
2017-11-17 08:28:13
2017-11-17 08:28:10
2017-11-17 08:27:51.343
2017-11-17 08:27:48.021
presto单个标准时间转化(10位unix):
select to_unixtime(cast ('2017-08-30 10:36:15' as timestamp))
hive单个标准时间转化(10位unix):
select unix_timestamp(cast ('2017-08-30 10:36:15' as timestamp))
presto单个毫秒时间转化(13位unix):
select to_unixtime(cast ('2017-12-01 16:42:08.771' as timestamp))
hive单个毫秒时间转化(自动转为10位unix):
select unix_timestamp(cast ('2017-12-01 16:42:08.771' as timestamp))
presto变量转化(根据时间格式分别转化为10位unix、13位unix):
select to_unixtime(cast (time as timestamp))
hive变量转化(只能自动转化为10位unix):
select unix_timestamp(cast (time as timestamp)) from table1 where d = '2017-12-05'
unix时间格式 to 北京时间格式
数据格式:
1510894478
1510855235
1510855290929
1510873252377
presto单个10位unix(变为标准格式):
select format_datetime(from_unixtime(1510284058),'yyyy-MM-dd HH:mm:ss')
hive单个10位unix(变为标准格式):
select from_unixtime(1323308943123,'yyyy-MM-dd HH:mm:ss')
presto单个13位unix(毫秒格式):
select format_datetime(from_unixtime(1510284058.415),'yyyy-MM-dd HH:mm:ss.mmm')
hive单个13位unix(毫秒格式):手动截取10位
select from_unixtime(cast(substr(1323308943123,1,10) as int),'yyyy-MM-dd HH:mm:ss')
Presto 13、10位unix变量转化(只能截取成10位转化成标准格式):
select format_datetime(from_unixtime(cast(substr(ts,1,10) as double)),'yyyy-MM-dd HH:mm:ss') from table1
hive变量转化(事先截取10位unix):
select from_unixtime(cast(substr(ts,1,10) as int),'yyyy-MM-dd HH:mm:ss') from table1 where d = '2017-12-05'
时间差
select count(*), date_diff('hour',cast(format_datetime(from_unixtime(b.tradetime),'yyyy-MM-dd HH:mm:ss') as timestamp), cast(a.created as timestamp)) as diffhour
from (select gatewaypayid,created from dwd_refundorder where created > '2022-07-24 00:00:00' and created <= '2022-07-24 09:00:00') as a
left join (select tradetime,id from dwd_payorder) as b
on a.payid = b.id
group by date_diff('hour',cast(format_datetime(from_unixtime(b.tradetime),'yyyy-MM-dd HH:mm:ss') as timestamp), cast(a.created as timestamp))
limit 100;
2. 分组中取多个字段
以一个场景来说明这个问题。假设 dw_student_course 表(选课表)里有很多条记录,如下表:
id | user_id | course_id | course_name |
---|---|---|---|
1 | 52100000 | 1 | 英语 |
2 | 52100001 | 1 | 英语 |
3 | 52100002 | 1 | 英语 |
4 | 52100003 | 2 | 数学 |
5 | 52100004 | 3 | 语文 |
6 | 52100005 | 4 | 物理 |
7 | 52100006 | 4 | 物理 |
8 | 52100007 | 4 | 物理 |
9 | 52100008 | 5 | 化学 |
10 | 52100009 | 5 | 化学 |
我们现在想知道某门课程的选课人数大于 2 门的该课程 id 和课程名,那写 hive sql 怎么写呢?
select max(course_id),course_name from
dw_student_course
group by course_name having count(course_name) > 2
PS : 注意 hive sql 中是不允许 select 后跟非 group by key 字段的,所以这里我们通过用一个聚合函数 max 来选一个 course_id 出来,这样就不会报错了。
3. 统计去重
实现方式:
- select count(distinct field_name) * from t_name;
- select count(field_name) * from t_name group by field_name; (推荐)
在 hive 中查询时,我们表的数据量一般都很大,用第一种方式查询的话,性能会比较慢,所以推荐使用第二种方式进行查询。为什么第一种方式会慢呢?因为第一种方式会导致数据倾斜,大数据并不怕数据多,就怕数据倾斜。具体原因请见如下博客:https://blog.csdn.net/xiaoshunzi111/article/details/68484426
4. 查询一张表中满足某种条件下的所有记录的某个字段的值均为同一个
比如:我要根据查询一个账单表(dwd_loan_bill_td)中业务类型(bizType)为 88 且属于同一个月账单下所有账单还款状态均为已还款的月账单 id(monthbillid)。
id | bizType | repaystatus | monthbillid |
---|---|---|---|
1 | 1 | 1 | 1000001 |
2 | 1 | 1 | 1000001 |
3 | 2 | 0 | 1000001 |
4 | 88 | 0 | 1100002 |
5 | 88 | 1 | 1100002 |
6 | 88 | 1 | 1220003 |
7 | 88 | 1 | 1220003 |
8 | 88 | 0 | 1333004 |
9 | 88 | 0 | 1333004 |
10 | 2 | 0 | 1009004 |
hive sql 如下:
select max(monthbillid) as monthbillid from dwd_loan_bill_td
where
bizType=88
group by monthbillid
having avg(repaystatus) = 1
月账单和账单的关系是一对多,每个账单的记录中会记录所对应的月账单 id。
5. 查询一张表中,某2个字段完全相同的记录
假设有表 A(id, name),查询该表中,id 和 name 完全相同的记录:
select a.*
from A a
inner join
(select id, name
from A
group by id, name
having count(*) > 1) b
on a.id = b.id
and a.name = b.name
6. 使用tinyint(1) 用来存储枚举时候的坑
https://my.oschina.net/u/4418709/blog/3510914
7. 查询 group by 后的记录数
select count(*) from (SELECT userId FROM dwd_loan_payorder WHERE to_unixtime(cast(created as timestamp))>=1577808000 group by userId) as payorder;
8. 多表关联查询
left join 多表关联查询时,如果根据驱动表的值在辅助表中没有查询到相应的记录,则查询出来的辅助表中的字段都为 NULL。
left join 原理
W3CSCHOOL上是这样描述 left join 的: LEFT JOIN 关键字会从左表 (table_name1) 那里返回所有的行,即使在右表 (table_name2) 中没有匹配的行。
sql语句:
SELECT * FROM TAB1 LEFT JOIN TAB2 ON (TAB1.TSIZE=TAB2.T_SIZE)
TAB1、TAB2 表以及使用上述 SQL 语句执行后的结果如下图:
查询结果:
结果中 TAB1 表的(2,20)变成了 2 条,它究竟怎么增加的?
首先得了解 sql 语句的执行顺序,是这样的:
- FROM:对 FROM 子句中的前两个表执行笛卡尔积(交叉联接),生成虚拟表 VT1。
- ON:对 VT1 应用 ON 筛选器,只有那些使为真才被插入到 TV2。
对于 MySQL 数据库来说,left jon 的 on 中的筛选条件可以在对 FROM 子句中的前两个表执行笛卡尔积前,利用索引去过滤一些无效的数据,从而减小数据量。且推荐经过索引过滤后数据量最小的表做为驱动表,也就是左表,因为这样可以减少数据的笛卡尔积的次数。
- OUTER (JOIN):如果指定了 OUTER JOIN(相对于 INNER JOIN),保留表中未找到匹配的行将作为外部行添加到 VT2,生成 TV3。如果 FROM 子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤 1 到步骤 3,直到处理完所有的表位置。
- WHERE:对 TV3 应用 WHERE 筛选器,只有使为 true 的行才插入 TV4。
- GROUP BY:按 GROUP BY 子句中的列列表对 TV4 中的行进行分组,生成 TV5。
- 聚合函数:把聚合结果插入 VT5,生成 VT6。
- HAVING:对 VT6 应用 HAVING 筛选器,只有使为true的组插入到 VT7。
- SELECT:处理 SELECT 列表,产生 VT8。
- DISTINCT:将重复的行从 VT8 中删除,产品 VT9。
- ORDER BY:将 VT9 中的行按 ORDER BY 子句中的列列表顺序,生成一个游标(VC10)。
- LIMIT:从 VC10 的开始处选择指定数量或比例的行,生成表 TV11,并返回给调用者。
PS:sql语句的执行顺序来自:http://wang286480403.iteye.com/blog/1899915
根据第 1 条,FROM 中的两个表会执行笛卡尔积,那么 TAB1 和 TAB2 的笛卡尔积就是:
根据第2条,应用 ON 筛选,找到 TSIZE 和 T_SIZE 中相等的记录,结果是:
根据第 3 条,由于指定了 left join(left join属于outer join啊!),需要保留左表中没有找到匹配的记录:
最后按照 sql 语句得到的结果就是这个:
上述内容转载自 https://blog.csdn.net/lamyourangle/article/details/76673850 这位同学的博客,我只是根据这篇博客做了一些删改。
多表左连接查询例子
查询支付单中的待确认金额不等于确认收货总金额加上未确认收货退款的总金额的记录。
支付单中的待确认金额 = 确认收货总金额 + 未确认收货退款的总金额
SELECT payorder.userId, payorder.payrequestid, payorder.toBeConfirmedAmount, payorder.orderamount
, coalesce(totalRefundAmount, 0) AS totalRefundAmount
, coalesce(totalConfirmedAmount, 0) AS totalConfirmedAmount
, payorder.created,payorder.paystatus
FROM dwd_loan_payorder payorder
LEFT JOIN (
SELECT SUM(amount) AS totalRefundAmount, payrequestid
FROM dwd_loan_refundorder
WHERE isconfirmed = 'false'
GROUP BY payrequestid
) refundorder
ON payorder.payrequestid = refundorder.payrequestid
LEFT JOIN (
SELECT SUM(orderAmount) AS totalConfirmedAmount, payrequestid
FROM dwd_loan_confirmorder
GROUP BY payrequestid
) confirmorder
ON payorder.payrequestid = confirmorder.payrequestid
WHERE payorder.paystatus = '4'
AND payorder.orderamount <> (cast(payorder.toBeConfirmedAmount as bigint) + cast(coalesce(totalRefundAmount, 0) as bigint) + cast(coalesce(totalConfirmedAmount, 0) as bigint))
ORDER BY payorder.id DESC
LIMIT 100000;
9. 计算表达式中不能有为 null 的字段
null 不能进行算术运算,所有有 null 参与的运算结果都为 null。
例 1:
hive> select null+2;
OK
NULL
例 2:null=0,null!=0这类结果都为null,做判断条件时等效为false
hive> select case when null=0 then 1 else 0 end;
OK
0
hive> select null!=1;
OK
NULL
解决方案
hive :
实际应用中可用 nvl(字段名,值),如 nvl(a,0) 设置 null 的默认值,避免查询出错。
hive> select nvl(null,0) + 2;
OK
2
presto:
实际应用中可用 nvl(字段名,值),如nvl(a,0) 设置 null 的默认值,避免查询出错。
presto> select coalesce(null,0) + 2;
OK
2
参考链接:Hive 中 null 的处理
10. 解析 json
hive :
select get_json_object(payorder.reserved, '$.confirmAmount') as confirmAmount
from dwd_pay_payorder as payorder
presto:
SELECT b.subOrderType, a.id
FROM dwd_paymentorder a
LEFT JOIN (
SELECT id, CAST(json_extract_scalar(reserve, '$.subOrderType') AS int) AS subOrderType
FROM dwd_paymentorder
) b
ON a.id = b.id
WHERE
b.subOrderType = 1
AND substr(created, 1, 10) > '2019-07-02'
ORDER BY created DESC
LIMIT 100;
PS:payorder.reserved 这个字段的值为 json 串。
参考链接:Hive 解析 json
11. 字段类型转换
hive 和 presto 中都提供了 cast 函数来用于字段类型转换。定义如下:
cast(value AS type) → type
显式转换一个值的类型。 可以将varchar类型的值转为数字类型,反过来转换也可以。
try_cast(value AS type) → type
与cast类似,不过,如果转换失败会返回null,这个只有presto有
另外需要注意的是: hive中的 int 类型是就是 int,而 presto 中是包装类型 Integer;hive中的 string 类型是就是 string,而 presto 中是 varchar(n);如果 cast 的 type 写错也会报错。
varchar(n) 中的 n 可以根据字符串具体的长度自己设置,比如:varchar(100) 或 varchar(200) 或 varchar(500) 等等。
具体实战
payorder.id 字段 bigint 类型 转 字符串类型
hive :
select cast(payorder.id AS string) from dwd_pay_payorder as payorder
presto:
select cast(payorder.id AS varchar(100)) from dwd_pay_payorder as payorder
注意点
有一个需求需要统计某个时间小于100000s的所有记录,这个时间存在一个map<string,string>中,然后自然想到的就是where map[“stat_time”] <100000 ,结果出来的数据特别少,开始还天真的以为是数据的确就特别少。后来产品说不正常。仔细排查以后发现,这些数据都是小于10的。
相信看到这里就已经比较清晰了,这presto种字符串和数字比较,是把数字转化成字符串进行比较,也就是"10000" 和 23比,“10000” 小,由于hive和很多语言以及框架上,这种情况都是把字符串转化成数字,就没做特殊处理。结果遇到了这个坑。后来把查询引擎缓存hive就好了。
参考链接:https://cloud.tencent.com/developer/article/1336156
12. 获取 group by 后 id 最小的记录
dw_useridentity 表记录:
id | useId | identityid | status |
---|---|---|---|
1 | 52100000 | 11111 | 1 |
2 | 52100000 | 11112 | 1 |
3 | 52100002 | 2222 | 1 |
4 | 52100003 | 3333 | 1 |
5 | 52100004 | 4444 | 0 |
我现在想取同 userId 下 id 最小的那条用户实名关联记录。sql 如下:
SELECT t1.identityid, t1.userid
FROM dw_useridentity as t1
INNER JOIN
(
SELECT userid, min(id) AS min_id
FROM dw_useridentity
where status = 1
GROUP BY userid
) as t2
ON t1.userid = t2.userid AND t1.id = t2.min_id
13. hive 差集运算
14. hive 分区表
如果查询的是分区 hive 表,必须在 sql where 条件中加入分区字段。
15. 多表 join 时过滤字段值是 NULL 的记录
例子:
select userid, amount from
(select transid,amount from dwd_recon_outbill
where partnerName in('alipay')) as a
left join (select userid,transid from dwd_payorder) as b
on a.transid = b.transid
问题:过滤 userid是NULL的记录。
解决方案:
- left join 改为 join
- 在外面嵌套一层,where userid is not null