当前位置:   article > 正文

Hive SQL 五大经典面试题_hivesql面试题

hivesql面试题

目录

第 1 题 连续问题

分析:

解法:

第 2 题 分组问题

分析:

解法:

第 3 题 间隔连续问题

分析:

解法:

第 4 题 打折日期交叉问题

分析:

解法:

第 5 题 同时在线问题

分析:

解法:


第 1 题 连续问题

如下数据为蚂蚁森林中用户领取的减少碳排放量

  1. id dt lowcarbon
  2. 1001 2021-12-12 123
  3. 1002 2021-12-12 45
  4. 1001 2021-12-13 43
  5. 1001 2021-12-13 45
  6. 1001 2021-12-13 23
  7. 1002 2021-12-14 45
  8. 1001 2021-12-14 230
  9. 1002 2021-12-15 45
  10. 1001 2021-12-15 23
  11. .......

找出连续3天及以上减少碳排放量在100以上的用户

分析:

遇到这类问题,我们可以用等差数列法来求解,何为等差数列法?
等差数列法:两个等差数列如果等差相同,则相同位置的数据相减到的结果相同

  1. 比如有一个等差数列:2 3 4 5 6 7 8 对他们排序后的顺序为 1 2 3 4 5 6 7,转成列展示为:
  2. num rank 相同位置相减得(flag)
  3. 2 1 1
  4. 3 2 1
  5. 4 3 1
  6. 5 4 1
  7. 6 5 1
  8. 7 6 1
  9. 8 7 1
  10. 此时按照flag分组求和,就得到连续的条数
  11. 再如:
  12. num rank 相同位置相减得(flag)
  13. 2 1 1
  14. 3 2 1
  15. 4 3 1
  16. 7 4 3
  17. 8 5 3
  18. 9 6 3
  19. 10 7 3
  20. 14 8 6
  21. 15 9 6
  22. 按照flag分组求和,就得到连续的条数有三组:234(3),789 10(4),14 15(2)
  23. 基于这样的结果再做一次筛选就能得到想要的答案。

解法:

  1. -- 1) 按照用户ID及时间字段分组,计算每个用户单日减少的碳排放量
  2. select id,
  3. dt,
  4. sum(lowcarbon) lowcarbon
  5. from test1
  6. group by
  7. id,dt
  8. having lowcarbon>100; 记为 t1
  9. 得到:
  10. 1001 2021-12-12 123
  11. 1001 2021-12-13 111
  12. 1001 2021-12-14 230
  13. -- 2) 按照用户分组,同时按照时间排序,计算每条数据的Rank值
  14. select id,
  15. dt,
  16. lowcarbon,
  17. rank() over(partition by id order by dt) rk
  18. from t1; 记为 t2
  19. 得到:
  20. 1001 2021-12-12 123 1
  21. 1001 2021-12-13 111 2
  22. 1001 2021-12-14 230 3
  23. -- 3) 将每行数据中的日期减去Rank值
  24. select id,
  25. dt,
  26. lowcarbon,
  27. date_sub(dt,rk) flag
  28. from t2; 记为 t3
  29. 得到:
  30. 1001 2021-12-12 123 2021-12-11
  31. 1001 2021-12-13 111 2021-12-11
  32. 1001 2021-12-14 230 2021-12-11
  33. -- 4) 按照用户及Flag分组,求每个组有多少条数据,并找出大于等于3条的数据
  34. select id,
  35. flag,
  36. count(*) ct
  37. from t3
  38. group by
  39. id,flag
  40. having ct >= 3;
  41. 得到:
  42. 1001 2021-12-11 3
  43. -- 5) 最终将SQL拼接在一起
  44. select id,
  45. flag,
  46. count(*) ct
  47. from
  48. ( select id,
  49. dt,
  50. lowcarbon,
  51. date_sub(dt,rk) flag
  52. from
  53. ( select id,
  54. dt,
  55. lowcarbon,
  56. rank() over(partition by id order by dt) rk
  57. from
  58. ( select id,
  59. dt,
  60. sum(lowcarbon) lowcarbon
  61. from test1
  62. group by
  63. id,dt
  64. having lowcarbon > 100
  65. )t1
  66. )t2
  67. )t3
  68. group by
  69. id,flag
  70. having ct >= 3;

第 2 题 分组问题

这里的分组不是简单的分组,而是会话的分组。

比如说,进入一个网站以后,可以连续的点击很多个页面,后台会记录用户的行为日志;如果T日上午连续点击几个页面后退出了网站,直到第二天的下午才再次进入网站,单单从时间线上来看,昨天退出的那条日志跟今天进入的那条日志是连在一起的,但这两条数据实际上并不是一个会话产生的,如果需要对这样的数据进行分组,将其分在两个不同的会话当中,应该怎么做呢?组与组之间的时间间隔应该是多少呢?

这个就得看具体的业务逻辑了,比如接下来的例子:

如下为电商公司用户访问时间数据

  1. id ts
  2. 1001 17523641234
  3. 1001 17523641256
  4. 1002 17523641278
  5. 1001 17523641334
  6. 1002 17523641434
  7. 1001 17523641534
  8. 1001 17523641544
  9. 1002 17523641634
  10. 1001 17523641638
  11. 1001 17523641654
  12. 时间间隔小于60秒,则分为同一个组
  13. 1001 17523641234 1
  14. 1001 17523641256 1
  15. 1001 17523641334 2
  16. 1001 17523641534 3
  17. 1001 17523641544 3
  18. 1001 17523641638 4
  19. 1001 17523641654 4
  20. 1002 17523641278 1
  21. 1002 17523641434 2
  22. 1002 17523641634 3

分析:

这个问题可以看做:判断连续的两条数据是否属于同一个组(时间有序),这就涉及到当前行数据及前一行数据或者后一行数据的时间差是否在60秒以内,如果是就属于同一组,反之就不是同一组。

我们应该想到有两个窗口函数,用来获取当前行数据的前N行或者后N行数据:

  • 返回位于当前行的前n行的expr的值:LAG(expr,n,defval)
  • 返回位于当前行的后n行的expr的值:LEAD(expr,n,defval)

用法:

  1. lag(exp_str,offset,defval) over(partion by ..order by …)
  2. lead(exp_str,offset,defval) over(partion by ..order by …)

详细的用法感兴趣可以移步我的另一篇博客,干货满满:MySQL/Hive 常用窗口函数详解及相关面试题

解法:

  1. -- 1) 按照id分组,将上一行时间数据下移,即将当前行的上一行时间移到当前行,
  2. -- 如果前面没有数据,取默认值0
  3. select id,
  4. ts,
  5. lag(ts,1,0) over(partition by id order by ts) lagts
  6. from test2; 记为 t1
  7. 得到:
  8. 1001 17523641234 0
  9. 1001 17523641256 17523641234
  10. 1001 17523641334 17523641256
  11. 1001 17523641534 17523641334
  12. 1001 17523641544 17523641534
  13. 1001 17523641638 17523641544
  14. 1001 17523641654 17523641638
  15. 1002 17523641278 0
  16. 1002 17523641434 17523641278
  17. 1002 17523641634 17523641434
  18. -- 2) 将当前行时间数据减去上一行时间数据,得到两行数据的时间差
  19. select id,
  20. ts,
  21. ts-lagts tsdiff
  22. from t1; 记为 t2
  23. 得到:
  24. 1001 17523641234 17523641234
  25. 1001 17523641256 22
  26. 1001 17523641334 78
  27. 1001 17523641534 200
  28. 1001 17523641544 10
  29. 1001 17523641638 94
  30. 1001 17523641654 16
  31. 1002 17523641278 17523641278
  32. 1002 17523641434 156
  33. 1002 17523641634 200
  34. -- 3) 计算每个用户范围内从第一行到当前行tsdiff大于等于60的总个数(分组号)
  35. select id,
  36. ts,
  37. sum(if(tsdiff >= 60,1,0)) over(partition by id order by ts) groupid -- 这一行将得到从第一行到当前行的 sum(if(tsdiff >= 60,1,0)) 值
  38. from t2;
  39. 得到:
  40. 1001 17523641234 1
  41. 1001 17523641256 1
  42. 1001 17523641334 2
  43. 1001 17523641534 3
  44. 1001 17523641544 3
  45. 1001 17523641638 4
  46. 1001 17523641654 4
  47. 1002 17523641278 1
  48. 1002 17523641434 2
  49. 1002 17523641634 3
  50. -- 4) 最终将SQL拼接在一起
  51. select id,
  52. ts,
  53. sum(if(tsdiff>=60,1,0)) over(partition by id order by ts) groupid
  54. from
  55. ( select id,
  56. ts,
  57. ts-lagts tsdiff
  58. from
  59. ( select id,
  60. ts,
  61. lag(ts,1,0) over(partition by id order by ts) lagts
  62. from test2
  63. )t1
  64. )t2;

第 3 题 间隔连续问题

某游戏公司记录的用户每日登录数据

  1. 1001 2021-12-12
  2. 1002 2021-12-12
  3. 1001 2021-12-13
  4. 1001 2021-12-14
  5. 1001 2021-12-16
  6. 1002 2021-12-16
  7. 1001 2021-12-19
  8. 1002 2021-12-17
  9. 1001 2021-12-20

计算每个用户最大的连续登录天数,可以间隔一天。解释:如果一个用户在 1,3,5,6 登录游戏,则视为连续 6 天登录。

分析:

先将数据按照id,dt排序后得到:

  1. id dt
  2. 1001 2021-12-12
  3. 1001 2021-12-13
  4. 1001 2021-12-14
  5. 1001 2021-12-16
  6. 1001 2021-12-19
  7. 1001 2021-12-20
  8. 1002 2021-12-12
  9. 1002 2021-12-16
  10. 1002 2021-12-17

依题意分析得到:

  1. 1001用户12、13、14、16号为连续登录,连续天数为5
  2. 1001用户19、20号为连续登录,连续天数为2
  3. 1002用户12号为连续登录,连续天数为1
  4. 1002用户16、17号为连续登录,连续天数为2

如果我们将以上四种情况分为四组,那四组的连续天数计算方式分别为:max(dt)-min(dt)+1,即

  1. 16-12+1=5
  2. 20-19+1=2
  3. 12-12+1=1
  4. 17-16+1=2

由此可见,该类问题就可以转换为,先将数据进行分组,再由组内最大日期减最小日期+1得到。分组问题也就是第二题。

以1001用户的数据为例,流程应该是:

  1. 时间下移 diff sum(if(diff>2,1,0)) over(...) as group_flag
  2. 1001 2021-12-12 1970-01-01 564564 1
  3. 1001 2021-12-13 2021-12-12 1 1
  4. 1001 2021-12-14 2021-12-13 1 1
  5. 1001 2021-12-16 2021-12-14 2 1
  6. 1001 2021-12-19 2021-12-16 3 2
  7. 1001 2021-12-20 2021-12-19 1 2
  8. 然后按照group_flag分组取组内的最大日期和最小日期求diff+1,就是最大连续登录天数

解法:

  1. -- 1) 将上一行时间数据下移
  2. select id,
  3. dt,
  4. lag(dt,1,'1970-01-01') over(partition by id order by dt) lagdt
  5. from test3;记为 t1
  6. 得到:
  7. 1001 2021-12-12 1970-01-01
  8. 1001 2021-12-13 2021-12-12
  9. 1001 2021-12-14 2021-12-13
  10. 1001 2021-12-16 2021-12-14
  11. 1001 2021-12-19 2021-12-16
  12. 1001 2021-12-20 2021-12-19
  13. -- 2) 将当前行时间减去上一行时间数据(datediff(dt1,dt2))
  14. select id,
  15. dt,
  16. datediff(dt,lagdt) flag
  17. from t1; 记为 t2
  18. 得到:
  19. 1001 2021-12-12 564564
  20. 1001 2021-12-13 1
  21. 1001 2021-12-14 1
  22. 1001 2021-12-16 2
  23. 1001 2021-12-19 3
  24. 1001 2021-12-20 1
  25. -- 3) 按照用户分组,同时按照时间排序,计算从第一行到当前行大于2的数据的总条数(sum(if(flag>2,1,0)))
  26. select id,
  27. dt,
  28. sum(if(flag>2,1,0)) over(partition by id order by dt) flag
  29. from t2;记为 t3
  30. 得到:
  31. 1001 2021-12-12 1
  32. 1001 2021-12-13 1
  33. 1001 2021-12-14 1
  34. 1001 2021-12-16 1
  35. 1001 2021-12-19 2
  36. 1001 2021-12-20 2
  37. -- 4) 按照用户和flag分组,求最大时间减去最小时间并加上1
  38. select id,
  39. flag,
  40. datediff(max(dt),min(dt)) days
  41. from t3
  42. group by
  43. id,flag; 记为 t4
  44. 得到:
  45. 1001 1 4
  46. 1001 2 1
  47. -- 5)取连续登录天数的最大值
  48. select id,
  49. max(days)+1
  50. from t4
  51. group by
  52. id;
  53. 得到:
  54. 1001 5
  55. 1002 21002用户明细略)
  56. -- 6) 将SQL拼接起来
  57. select id,
  58. max(days)+1
  59. from
  60. ( select id,
  61. flag,
  62. datediff(max(dt),min(dt)) days
  63. from
  64. ( select id,
  65. dt,
  66. sum(if(flag>2,1,0)) over(partition by id order by dt) flag
  67. from
  68. ( select id,
  69. dt,
  70. datediff(dt,lagdt) flag
  71. from
  72. ( select id,
  73. dt,
  74. lag(dt,1,'1970-01-01') over(partition by id order by dt) lagdt
  75. from test3
  76. )t1
  77. )t2
  78. )t3
  79. group by
  80. id,flag
  81. )t4
  82. group by
  83. id

第 4 题 打折日期交叉问题

如下为平台商品促销数据:字段为品牌,打折开始日期,打折结束日期

  1. id stt edt
  2. oppo 2021-06-05 2021-06-09
  3. oppo 2021-06-11 2021-06-21
  4. vivo 2021-06-05 2021-06-15
  5. vivo 2021-06-09 2021-06-21
  6. redmi 2021-06-05 2021-06-21
  7. redmi 2021-06-09 2021-06-15
  8. redmi 2021-06-17 2021-06-26
  9. huawei 2021-06-05 2021-06-26
  10. huawei 2021-06-09 2021-06-15
  11. huawei 2021-06-17 2021-06-21

计算每个品牌总的打折销售天数,注意其中的交叉日期,比如 vivo 品牌,第一次活动时间为 2021-06-05 到 2021-06-15,第二次活动时间为 2021-06-09 到 2021-06-21 其中 9 号到 15 号为重复天数,只统计一次,即 vivo 总打折天数为 2021-06-05 到 2021-06-21 共计 17 天。

分析:

题意要求求某品牌总的打折天数,有两种情景需要考虑:

情景一:两次活动的日期没有交叉。如OPPO的两次活动:

  1. id stt edt
  2. oppo 2021-06-05 2021-06-09
  3. oppo 2021-06-11 2021-06-21

对于这种情况,我们直接分别求diff再做sum即可,即(edt-stt+1):

  1. id stt edt diff
  2. oppo 2021-06-05 2021-06-09 5
  3. oppo 2021-06-11 2021-06-21 11
  4. 再按照 id 分组求sum(diff)=16

情景二:两次活动的日期有交叉。如vivo的两次活动:

  1. id stt edt
  2. vivo 2021-06-05 2021-06-15
  3. vivo 2021-06-09 2021-06-21

第一次活动时间为[2021-06-05 , 2021-06-15],第二次活动时间为 [2021-06-09, 2021-06-21] 其中 9 号到 15 号为重复天数,只统计一次,即 vivo 总打折天数为 [2021-06-05,2021-06-21] 共计 17 天。

如果不考虑交叉,仍然按照情景一的方式计算,两段活动日期的diff分别为:11、13,加起来是24天,如何将中间重复的天数只计算一次呢?

注意观察,出现日期交叉的原因是因为第二次活动的开始时间小于第一次活动的结束时间,换句话说,第一次活动还没结束第二次活动就开始了。

换个角度思考,如果第二次活动在第一次活动结束后再开始,就不会出现日期交叉了,我们试试将第二次活动的开始时间改为第一次活动结束+1看看会是什么样:

  1. -- 2021-06-09 改成 2021-06-16
  2. id stt edt
  3. vivo 2021-06-05 2021-06-15
  4. vivo 2021-06-16 2021-06-21

我们再用情景一的计算方式计算出来,活动天数为:11+6=17,符合题意。

通过这样的转换,就能将交叉重复的日期只计算一次,所以到此可以总结为:在计算时,先将本次活动的起始时间改为上次活动的结束时间+1,再分别做diff再求和即可。这样我们就可以用lag()或者lead()将edt字段下移做计算。但这样真的可以吗?会存在一个问题,看个redmi例子:

  1. -- 原数据(人工计算出来的天数应该是22天):
  2. id stt edt
  3. redmi 2021-06-05 2021-06-21
  4. redmi 2021-06-09 2021-06-15
  5. redmi 2021-06-17 2021-06-26
  6. -- 按照分析,将"本次"活动的开始时间改为"上次"活动的结束时间+1:
  7. id stt edt edt下移 diff(edt-stt+1)
  8. redmi 2021-06-05 2021-06-21 1970-01-01 --不变 21-5+1=17
  9. redmi 2021-06-09 2021-06-15 2021-06-21 --2021-06-09改为2021-06-22 15-22+1= -6
  10. redmi 2021-06-17 2021-06-26 2021-06-15 --2021-06-17改为2021-06-16 26-16+1= 11

会发现,有负数出现,但这个无关紧要,在做sum时会过滤掉 <0 的天数
非负数求和加起来是28天,跟真实的22天不符。
问题出在第三行,2021-06-17改为2021-06-16,其实第三行的开始时间应该改成第三行前面活动的最大结束时间+1,即改成2021-06-22即可。

所以,前面的总结需要修改一下:在计算时,先将本次活动的起始时间改为前几次活动的最大结束时间+1,再分别做diff再求和即可

实现过程中会用到开窗取前N行数据的最大值的知识点,具体用法感兴趣可以移步我的另一篇博客,干货满满:MySQL/Hive 常用窗口函数详解及相关面试题

解法:

  1. -- 以 Redmi 数据为例
  2. -- 1) 将当前行以前的数据中最大的edt放置当前行
  3. select id,
  4. stt,
  5. edt,
  6. max(edt) over(partition by id order by stt rows between UNBOUNDED PRECEDING and 1 PRECEDING) maxEdt
  7. from test4; 记为 t1
  8. 得到:
  9. redmi 2021-06-05 2021-06-21 null
  10. redmi 2021-06-09 2021-06-15 2021-06-21
  11. redmi 2021-06-17 2021-06-26 2021-06-21
  12. -- 2) 比较开始时间与移动下来的数据,如果开始时间大,则不需要操作,
  13. -- 反之则需要将移动下来的数据加一替换当前行的开始时间
  14. -- 如果是第一行数据,maxEDT为null,则不需要操作
  15. select id,
  16. if(maxEdt is null,stt,if(stt>maxEdt,stt,date_add(maxEdt,1))) stt,
  17. edt
  18. from t1; 记为 t2
  19. 得到:
  20. redmi 2021-06-05 2021-06-21
  21. redmi 2021-06-22 2021-06-15
  22. redmi 2021-06-22 2021-06-26
  23. -- 3) 将每行数据中的结束日期减去开始日期
  24. select id,
  25. datediff(edt,stt) days
  26. from t2; 记为 t3
  27. 得到:
  28. redmi 16
  29. redmi -4
  30. redmi 4
  31. -- 4) 按照品牌分组,计算每条数据加一的总和
  32. select id,
  33. sum(if(days>=0,days+1,0)) days
  34. from t3
  35. group by
  36. id;
  37. 得到:
  38. redmi 22
  39. -- 5) 最终SQL
  40. select id,
  41. sum(if(days>=0,days+1,0)) days
  42. from
  43. ( select id,
  44. datediff(edt,stt) days
  45. from
  46. (
  47. select id,
  48. if(maxEdt is null,stt,if(stt>maxEdt,stt,date_add(maxEdt,1))) stt,
  49. edt
  50. from
  51. ( select id,
  52. stt,
  53. edt,
  54. max(edt) over(partition by id order by stt rows between UNBOUNDED PRECEDING and 1 PRECEDING) maxEdt
  55. from test4
  56. )t1
  57. )t2
  58. )t3
  59. group by
  60. id

第 5 题 同时在线问题

如下为某直播平台主播开播及关播时间,根据该数据计算出平台最高峰同时在线的主播人数。

  1. id stt edt
  2. 1001 2021-06-14 12:12:12 2021-06-14 18:12:12
  3. 1003 2021-06-14 13:12:12 2021-06-14 16:12:12
  4. 1004 2021-06-14 13:15:12 2021-06-14 20:12:12
  5. 1002 2021-06-14 15:12:12 2021-06-14 16:12:12
  6. 1005 2021-06-14 15:18:12 2021-06-14 20:12:12
  7. 1001 2021-06-14 20:12:12 2021-06-14 23:12:12
  8. 1006 2021-06-14 21:12:12 2021-06-14 23:15:12
  9. 1007 2021-06-14 22:12:12 2021-06-14 23:10:12

分析:

采用流式数据的思想,将一条数据拆分成两条(id,dt,p),并且对数据进行标记:开播为1,关播为-1,1表示有主播开播在线,-1表示有主播关播离线,其中dt为开播时间或者关播时间:

  1. id dt p
  2. 1001 2021-06-14 12:12:12 1
  3. 1001 2021-06-14 18:12:12 -1
  4. 1001 2021-06-14 20:12:12 1
  5. 1001 2021-06-14 23:12:12 -1
  6. 1002 2021-06-14 15:12:12 1
  7. 1002 2021-06-14 16:12:12 -1
  8. 1003 2021-06-14 13:12:12 1
  9. 1003 2021-06-14 16:12:12 -1
  10. 1004 2021-06-14 13:15:12 1
  11. 1004 2021-06-14 20:12:12 -1
  12. 1005 2021-06-14 15:18:12 1
  13. 1005 2021-06-14 20:12:12 -1
  14. 1006 2021-06-14 21:12:12 1
  15. 1006 2021-06-14 23:15:12 -1
  16. 1007 2021-06-14 22:12:12 1
  17. 1007 2021-06-14 23:10:12 -1

然后按照dt排序,求某一时刻的主播在线人数,直接对那时刻之前的p求和即可。

那要求一天中最大的同时在线人数,就需要先分别求出每个时刻的同时在线人数,再取最大值即可。需要用到开窗函数:sum() over(...)

解法:

  1. -- 1) 对数据分类,在开始数据后添加正1,表示有主播上线,同时在关播数据后添加-1,表示有主播下线
  2. select id,stt as dt, 1 as p from test5
  3. union
  4. select id,edt as dt,-1 as p from test5 记为 t1
  5. 得到:
  6. 1001 2021-06-14 12:12:12 1
  7. 1001 2021-06-14 18:12:12 -1
  8. 1001 2021-06-14 20:12:12 1
  9. 1001 2021-06-14 23:12:12 -1
  10. 1002 2021-06-14 15:12:12 1
  11. 1002 2021-06-14 16:12:12 -1
  12. 1003 2021-06-14 13:12:12 1
  13. 1003 2021-06-14 16:12:12 -1
  14. 1004 2021-06-14 13:15:12 1
  15. 1004 2021-06-14 20:12:12 -1
  16. 1005 2021-06-14 15:18:12 1
  17. 1005 2021-06-14 20:12:12 -1
  18. 1006 2021-06-14 21:12:12 1
  19. 1006 2021-06-14 23:15:12 -1
  20. 1007 2021-06-14 22:12:12 1
  21. 1007 2021-06-14 23:10:12 -1
  22. -- 2) 按照时间排序,计算累加人数
  23. select id,
  24. dt,
  25. sum(p) over(order by dt) sum_p -- 重点
  26. from t1; 记为 t2
  27. -- 3) 找出同时在线人数最大值
  28. select max(sum_p)
  29. from
  30. ( select id,
  31. dt,
  32. sum(p) over(order by dt) sum_p
  33. from
  34. ( select id,stt as dt, 1 as p from test5
  35. union
  36. select id,edt as dt,-1 as p from test5
  37. )t1
  38. )t2
本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号