拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Hive SQL从基础到实战:建表、查询、函数与常见问题解决

1. 为什么要学Hive SQL以及它到底能解决什么问题我入行大数据那会儿Hive还远没有现在这么普及。那时候MapReduce写起来是真痛苦一个简单的单词统计写Java代码还要处理各种输入输出格式、Combiner、Partitioner光调试就能耗掉半天。后来Hive一出现整个圈子的风气就变了——不再需要人人写MR只要会写SQL就能在Hadoop集群上跑数据计算。Hive本质上就是把SQL翻译成MapReduce作业的工具但它的价值远不止“翻译”这么简单。Hive SQL的核心场景是“大规模数据的离线批处理”。你手上有一批日志文件每天几百GB甚至TB级别存在HDFS上你想从中提取用户行为、统计PV/UV、计算转化漏斗、做用户画像标签。传统关系型数据库比如MySQL在这种数据量面前基本扛不住要么慢到怀疑人生要么直接OOM。而Hive基于Hadoop的分布式存储和计算天然支持海量数据。你只要建好表结构写好SQL它就能自动分发任务到集群节点并行计算几分钟或者几十分钟后出结果。很多人纠结“Hive SQL和标准SQL有什么区别”。直观上Hive SQL支持大部分标准的SQL语法SELECT、JOIN、GROUP BY、WHERE、HAVING、子查询等但也有一些重要的限制和扩展。比如Hive不支持事务ACID是后来才加入的早期版本完全没有不支持行级更新和删除不支持索引虽然有分区和分桶但索引不是传统意义上的B-Tree索引。另外Hive在SQL语法上做了很多针对大数据场景的扩展比如支持UDF用户自定义函数、支持LATERAL VIEW与explode处理复杂类型数组、Map、Struct、支持窗口函数OVER、PARTITION BY、ORDER BY等。这些扩展是Hive处理复杂数据结构的利器。学Hive SQL本质上是在学习一种“面向批处理”的思维模式。写SQL时不能假设数据是实时更新的不能假设查询是交互式的不能假设内存无限大。你需要考虑数据倾斜、分区裁剪、执行计划优化。这些是写传统SQL比较少遇到的事情。这篇文章是为那些刚开始接触Hive、或者已经会用一点但想系统梳理基础的同学准备的。我会从Hive SQL的DDL表定义、DML数据装载、基本查询、常用函数、复杂类型处理、以及遇到的一些坑和优化技巧从头到尾讲一遍。每一部分都结合我实际工作中踩过的坑和积累的经验力求让你在看完后能直接上手操作并且少犯错误。2. Hive SQL基础表、分区、分桶与数据存储2.1 创建表外部分区表是首选Hive建表有两种主要类型内部表Managed Table和外部表External Table。内部表的数据文件由Hive管理当你DROP表时数据文件也会被删除。外部表的数据文件存放在外部路径如HDFS上的指定目录Hive只记录元数据删除表不会删除数据文件。在实际生产环境中我强烈推荐使用外部表尤其是数据来自上游系统比如Flume采集的日志、Flink写入的HDFS文件。原因很简单数据是宝贵的资产万一某天Hive表结构出问题你删表重建数据还在不会因为误操作导致数据丢失。建表的基本语法CREATE EXTERNAL TABLE IF NOT EXISTS dwd.user_log ( user_id STRING COMMENT 用户ID, event_time BIGINT COMMENT 事件时间戳, event_type STRING COMMENT 事件类型, page_url STRING COMMENT 页面URL, stay_seconds INT COMMENT 停留时长秒, extra_info MAPSTRING, STRING COMMENT 扩展信息键值对 ) PARTITIONED BY (dt STRING COMMENT 日期分区格式yyyyMMdd) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMINATED BY : STORED AS TEXTFILE LOCATION /data/dw/dwd/user_log;这里有几个关键点需要说明PARTITIONED BY (dt STRING)分区是Hive的核心特性之一。分区字段在物理存储上表现为目录比如/data/dw/dwd/user_log/dt20250301/。查询时如果指定分区过滤Hive可以只扫描对应目录极大提升效率。分区字段dt在表中是伪列实际数据文件中并不包含它而是通过目录名推断。所以建表时不要在主字段列表中重复定义分区字段。ROW FORMAT DELIMITED这是Hive默认的文本文件解析方式。FIELDS TERMINATED BY \t表示字段之间用制表符分隔。COLLECTION ITEMS TERMINATED BY ,和MAP KEYS TERMINATED BY :是用于复杂类型数组和Map的内部分隔符。比如extra_info字段的值可能像key1:value1,key2:value2Hive会按照逗号分割出每个元素再用冒号分割键值。STORED AS TEXTFILE文本文件格式可读性好但压缩率低、查询效率一般。生产环境中常用STORED AS PARQUET列式存储压缩高查询快或STORED AS ORCHive推荐格式有索引和压缩。如果你刚开始学习先用TEXTFILE方便调试正式环境建议改用ORC。LOCATION指定HDFS上的路径。外部表必须指定Hive会管理这个目录下的所有文件。创建表之后可以使用DESCRIBE FORMATTED table_name查看表结构详细信息包括分区信息、存储格式、SerDe等。这个命令在排查问题时非常有用。2.2 分区表的管理添加、删除、修复分区表需要手动添加分区或者使用动态分区插入。添加分区的语法ALTER TABLE dwd.user_log ADD PARTITION (dt20250301) LOCATION /data/dw/dwd/user_log/dt20250301;如果数据文件已经在HDFS上但Hive元数据中没有记录该分区可以使用MSCK REPAIR TABLE table_name命令自动检测并添加分区。这个命令适用于分区目录命名规范与分区字段一致的情况。注意MSCK REPAIR在分区数量极大时比如几千个可能会比较慢建议用ALTER TABLE ADD PARTITION逐个添加或者使用Hive Metastore的Hive Metastore Check工具。删除分区语法ALTER TABLE dwd.user_log DROP PARTITION (dt20250301);这只会删除元数据如果外部表数据文件不会删除内部表会删除数据。日常维护中我们经常需要删除过期分区释放空间可以写一个脚本循环调用ALTER TABLE DROP PARTITION。2.3 分桶表更细粒度的数据组织分桶Bucket是Hive的另一种数据组织方式它在分区之下进一步将数据分散到固定数量的文件中。比如按user_id哈希分桶每个桶可以对应一个物理文件。分桶的主要用途是提升抽样查询和Map-Side Join的效率。因为相同桶编号的数据会落在同一个文件中当两个表按照相同的分桶字段和桶数分桶时Join可以在Map端直接完成不需要Reduce。建表时指定分桶CREATE EXTERNAL TABLE dwd.user_log_bucketed ( user_id STRING, event_time BIGINT, event_type STRING ) CLUSTERED BY (user_id) INTO 10 BUCKETS ROW FORMAT DELIMITED FIELDS TERMINATED BY \t STORED AS TEXTFILE LOCATION /data/dw/dwd/user_log_bucketed;分桶字段必须是表中已有的字段不能是分区字段。分桶数通常设置成质数或者根据数据量估算让每个桶文件大小在128MB到256MB之间比较合适。分桶表和分区表可以同时使用先分区再分桶。比如按照dt分区每个分区内再按user_id分桶。2.4 数据装载LOAD DATA和INSERT INTO/OVERWRITEHive加载数据有两种方式LOAD DATA LOCAL INPATH 本地路径 [OVERWRITE] INTO TABLE table_name [PARTITION (dt...)]从本地文件系统上传文件到HDFS并移动到对应的表目录。如果使用LOCAL指的是本地客户端所在机器文件系统如果不加LOCAL则从HDFS路径移动。注意LOAD DATA会移动文件相当于HDFS的mv而不是复制。如果文件还在其他地方有用建议先复制再加载。INSERT INTO table_name [PARTITION (dt...)] SELECT ... FROM source_table这是最常用的方式用于从其他表或查询结果插入数据。INSERT INTO追加数据INSERT OVERWRITE覆盖已有数据。生产环境中我们通常用INSERT OVERWRITE TABLE target_table PARTITION (dt${date}) SELECT ...来每天全量刷新一个分区保证数据一致。使用动态分区插入可以自动根据SELECT字段的值创建分区语法INSERT OVERWRITE TABLE target_table PARTITION (dt) SELECT user_id, event_time, ..., event_date AS dt FROM source_table;这里要求SELECT的最后一个字段event_date作为分区字段值且目标表的分区字段必须是dt。我强烈建议在动态分区插入前设置以下参数防止误操作产生过多分区或文件数SET hive.exec.dynamic.partition.mode nonstrict; -- 允许所有分区都是动态的 SET hive.exec.dynamic.partition true; SET hive.exec.max.dynamic.partitions 1000; -- 防止产生过多分区 SET hive.exec.max.dynamic.partitions.pernode 100;如果不设置nonstrict默认是strict要求至少有一个静态分区可以减少风险。3. Hive SQL查询从简单SELECT到复杂聚合3.1 基本查询语法与执行顺序Hive SQL的SELECT语法和标准SQL基本一致但Hive的查询最终会转换为MapReduce或Tez/Spark作业所以理解它的执行顺序对优化很重要。一个典型的SQL执行顺序是FROM确定数据来源读取表或子查询。JOIN连接其他表如果有。WHERE过滤行注意分区裁剪在WHERE条件中对分区字段生效。GROUP BY分组产生聚合键。HAVING过滤分组后的结果。SELECT投影列计算表达式聚合函数窗口函数。ORDER BY全局排序注意Hive中ORDER BY会强制进一个Reduce大表慎用。LIMIT限制输出行数。Hive支持标准SQL的所有子句但有一些特殊行为需要注意DISTINCT去重Hive会生成一个MapReduce作业如果数据量巨大可能会很慢。可以考虑用GROUP BY替代实际效果类似。JOINHive支持INNER JOIN、LEFT/RIGHT/FULL OUTER JOIN、LEFT SEMI JOIN相当于INNER JOIN但只返回左表列高效存在性检查、CROSS JOIN。注意Hive的JOIN只能进行等值连接ON条件中只能使用等号不支持非等值连接如ON a b。如果确实需要非等值连接可以通过子查询或CROSS JOIN后过滤但效率很低。UNION ALL合并多个查询结果要求列数相同且类型兼容。Hive不支持UNION去重只支持UNION ALL不去重。如果需要去重可以在UNION ALL外面包一层SELECT DISTINCT。3.2 常用聚合函数与窗口函数初探聚合函数COUNT、SUM、AVG、MIN、MAX、COLLECT_LIST、COLLECT_SET等。COLLECT_LIST可以收集列值到一个数组保留顺序和重复COLLECT_SET去重后收集。这两个函数在处理嵌套数据时非常有用。窗口函数Window Function是Hive SQL的强大特性允许在不过度聚合的情况下进行排序、排名、滑动计算。基础语法SELECT user_id, event_time, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time DESC) AS rn, RANK() OVER (PARTITION BY user_id ORDER BY stay_seconds DESC) AS rk, DENSE_RANK() OVER (PARTITION BY user_id ORDER BY stay_seconds DESC) AS drk, SUM(stay_seconds) OVER (PARTITION BY user_id ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total FROM dwd.user_log WHERE dt 20250301;ROW_NUMBER()从1开始递增相同排序值的行顺序随机。RANK()相同排序值并列但会跳过后续排名如1,1,3。DENSE_RANK()相同排序值并列但不跳过排名如1,1,2。SUM() OVER ...窗口聚合计算累计值。窗口函数中PARTITION BY用于分组ORDER BY用于排序ROWS BETWEEN定义窗口帧范围。常用的窗口帧有ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW累计到当前行。ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING滑动窗口取当前行前后3行。ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING整个分区内所有行相当于没有窗口帧限制。注意Hive 2.0开始支持窗口函数但较早版本1.x可能不支持一些高级窗口帧建议使用较新版本如Hive 3.1.2。3.3 WHERE条件与分区裁剪这是Hive查询优化中最重要的一环。在WHERE条件中指定分区字段Hive可以直接跳过不相关的分区目录大幅减少数据扫描量。例如SELECT * FROM dwd.user_log WHERE dt 20250301 AND event_type click;如果dt是分区字段Hive只会读取dt20250301目录下的文件。如果WHERE条件中没有分区字段或者分区字段被函数包裹如WHERE dt from_unixtime(event_time, yyyyMMdd)Hive无法进行分区裁剪会扫描所有分区导致全表扫描。所以写查询时一定要尽可能把分区条件写清楚并且不要对分区字段使用函数。有时候我们会在WHERE条件中使用IN或BETWEEN来指定多个分区例如SELECT * FROM dwd.user_log WHERE dt BETWEEN 20250301 AND 20250307;Hive对BETWEEN和IN也能做分区裁剪但要注意如果IN列表中的分区目录不存在Hive会忽略它们不会报错但也不会报分区不存在而是直接跳过。这在某些场景下可能导致数据缺失需要留意。3.4 随机抽样的几种方法根据热词中有“Hive随机抽取100条数据”这里介绍几种常用方法使用ORDER BY rand() LIMIT 100这是最直观的方式但ORDER BY rand()会触发全局排序将所有数据拉到同一个Reduce中进行排序性能极差。小表可以大表不要用。使用SORT BY rand() LIMIT 100SORT BY不是全局排序它在每个Reduce内排序每个Reduce输出前100条然后取总输出前100条。这比ORDER BY rand()高效很多但结果并不是严格随机每个reduce内随机排序但通常足够。使用DISTRIBUTE BY rand() SORT BY rand() LIMIT 100先按随机数分发数据到各个Reduce每个Reduce内再随机排序最后取全局前100效率更高结果更随机。使用TABLESAMPLESELECT * FROM table TABLESAMPLE(100 ROWS)Hive会按照数据块大小随机采样行但具体实现可能因版本而异结果不一定严格随机但速度快。使用WHERE rand() 0.01按比例抽取比如抽取1%的数据。这种方法不需要排序随机性也好但无法精确控制行数。实际工作中我常用的是DISTRIBUTE BY rand() SORT BY rand() LIMIT N在数据量几亿条时也能在几十秒内完成随机抽样。如果只需要固定比例用WHERE rand() rate更快。4. 复杂类型处理数组、Map、Struct与LATERAL VIEW4.1 数组类型创建、查询与函数Hive支持数组Array类型定义方式ARRAYSTRING、ARRAYINT等。数据文件中数组元素用分隔符分隔如逗号。查询时可以通过索引访问arr[0]从0开始。常用函数size(arr)返回数组长度。array_contains(arr, value)判断数组中是否包含某值。sort_array(arr)返回排序后的新数组升序。explode(arr)将数组展开为多行每行一个元素。配合LATERAL VIEW使用是处理嵌套数据的核心。例如假设表中有字段tags ARRAYSTRING值如[休闲,美食,科技]。要统计每个标签出现的次数可以SELECT tag, COUNT(*) AS cnt FROM dwd.user_log LATERAL VIEW EXPLODE(tags) t AS tag GROUP BY tag;LATERAL VIEW将explode生成的虚拟表与原始行关联相当于把一行展开成多行。注意explode会忽略NULL数组如果数组为NULL那一行不会产生输出。如果希望保留NULL可以用LATERAL VIEW OUTER EXPLODE。4.2 Map类型键值对处理Map类型定义MAPSTRING, STRING。数据文件中键值对序列用分隔符分隔如key1:value1,key2:value2。访问Map元素map[key]。常用函数size(map)返回Map中的键值对个数。map_keys(map)返回所有键组成的数组。map_values(map)返回所有值组成的数组。explode(map)展开Map为多行每行两列key, value。例如SELECT user_id, k, v FROM dwd.user_log LATERAL VIEW EXPLODE(extra_info) info AS k, v;注意explode(map)与explode(array)返回的列数不同Map的explode会生成一个键列和一个值列。LATERAL VIEW后面的别名是给虚拟表起的别名AS后面是给列起的别名。4.3 Struct类型复合记录Struct类型定义STRUCTname STRING, age INT。访问方式struct.name。Struct可以嵌套比如STRUCTinfo STRUCTaddr STRING, phone STRING。Struct的explode函数没有直接支持但可以通过INLINE函数展开数组结构或自定义UDF处理。实际应用中Struct常见于嵌套复杂业务对象比如用户信息。4.4 LATERAL VIEW的高级用法LATERAL VIEW可以多次使用展开多个数组或Map。比如SELECT user_id, tag, kv FROM dwd.user_log LATERAL VIEW EXPLODE(tags) t AS tag LATERAL VIEW EXPLODE(extra_info) info AS key, value;这会生成笛卡尔积如果tags数组有3个元素extra_info Map有5个键那么一行会变成15行。注意控制数据膨胀避免爆炸。LATERAL VIEW还可以结合OUTER关键字当explode的数组或Map为NULL时保留原始行但展开的列为NULL。这在需要保留所有行场景下很有用。5. 常用函数与技巧日期、字符串、条件判断5.1 日期函数处理时间戳Hive SQL中日期处理是高频需求。常用函数from_unixtime(unix_timestamp, pattern)将时间戳秒转换为指定格式字符串。例如from_unixtime(1710100000, yyyy-MM-dd HH:mm:ss)。unix_timestamp(string_date, pattern)将字符串日期转换为时间戳。例如unix_timestamp(2025-03-10, yyyy-MM-dd)。to_date(string_date)提取日期部分返回yyyy-MM-dd格式。date_add(string_date, days)日期加减。date_sub(string_date, days)日期减法。datediff(end_date, start_date)计算两个日期相差天数。year/month/day/hour/minute/second提取日期部分。date_format(date, pattern)格式化日期类似from_unixtime但接受字符串日期。注意Hive中的日期字符串默认格式是yyyy-MM-dd如果使用其他格式需要显式指定pattern。5.2 字符串函数处理文本数据常用字符串函数length(str)字符串长度。substr(str, pos, len)截取子串pos从1开始。concat(str1, str2, ...)拼接字符串支持多个参数。concat_ws(separator, str1, str2, ...)用分隔符拼接。split(str, regex)按正则表达式拆分字符串返回数组。upper/lower大小写转换。trim/ltrim/rtrim去除空格。regexp_replace(str, regex, replacement)正则替换。regexp_extract(str, regex, idx)正则提取idx表示第几个捕获组从1开始如果idx0返回整个匹配。字符串处理中split和regexp_extract非常强大经常用于解析半结构化日志。例如从URL中提取参数regexp_extract(url, user_id(\\w), 1)。5.3 条件函数与空值处理CASE WHEN ... THEN ... ELSE ... END标准条件分支。IF(condition, true_value, false_value)简单二值判断。COALESCE(v1, v2, ...)返回第一个非NULL值常用于填充默认值。NVL(v, default)如果v为NULL返回default否则返回v。在Hive中NVL和COALESCE类似但NVL只接受两个参数。空值处理要特别注意Hive中NULL参与算术运算会返回NULL所以在计算前最好用COALESCE或NVL处理。例如COALESCE(amount, 0) 10。5.4 WITH AS 语法CTE热词中提到了with as用法这是Common Table ExpressionCTE将子查询封装成临时表提高可读性和复用性。语法WITH daily_stats AS ( SELECT dt, user_id, COUNT(*) AS pv, SUM(stay_seconds) AS total_stay FROM dwd.user_log GROUP BY dt, user_id ), ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY dt ORDER BY pv DESC) AS rn FROM daily_stats ) SELECT * FROM ranked WHERE rn 10;CTE可以定义多个用逗号分隔后跟主查询。CTE只在当前查询中有效不能跨查询。在复杂ETL中我经常用CTE组织逻辑让代码更清晰避免多层嵌套子查询。6. 常见问题与排查技巧实录6.1 数据倾斜任务跑不动某个Reduce一直卡住数据倾斜是Hive新手最容易碰到的坑。典型表现运行日志中大部分Reduce已完成但有一个或几个Reduce进度停在99%很久甚至失败。根本原因是某些key对应的数据量远超其他key导致该Reduce任务负载过大。常见原因和解决方法GROUP BY的键值分布不均比如按性别分组男女数据量可能均衡但按城市分组一线城市数据量远大于小城市。解决方法如果倾斜严重可以加盐对key进行hash打散成多个随机后缀先聚合一次再去除后缀聚合或者使用hive.groupby.skewindatatrueHive自动优化但会增加一个MR阶段。JOIN时大表关联小表但小表distribute不均匀如果小表某个key数据量特别大会导致大表对应key的数据都落到同一个Reduce。可以使用MAPJOIN提示强制将小表放到内存中在Map端完成Join避免Reduce阶段倾斜。语法SELECT /* MAPJOIN(b) */ a.*, b.* FROM big_table a JOIN small_table b ON a.key b.key。适用于小表足够小一般默认小于25MB可调整hive.mapjoin.smalltable.filesize。COUNT DISTINCT去重如果对某个字段做COUNT DISTINCT且该字段大量重复Hive默认使用一个Reduce。可以改用GROUP BY后再COUNT或者使用approx_count_distinct近似函数性能好但允许误差。排查数据倾斜可以查看YARN任务界面中每个Reduce的输入记录数如果某个Reduce记录数远大于其他则说明倾斜。也可以使用hive.exec.reducers.bytes.per.reducer调整每个Reduce处理的数据量但治标不治本。6.2 小文件过多NameNode压力大查询慢Hive表如果频繁写入会产生大量小文件比如每个分区下有几百个几KB的小文件。小文件会使HDFS的NameNode内存压力增大同时MapReduce任务启动时每个小文件对应一个Map任务启动开销大效率低。解决方法合并小文件使用INSERT OVERWRITE ... SELECT ...时设置hive.merge.mapfilestrueMap-only任务合并和hive.merge.size.per.task256000000合并后文件大小目标。或者使用ALTER TABLE table_name CONCATENATE仅限RCFILE和ORC格式。控制动态分区插入的并行度设置hive.exec.max.dynamic.partitions.pernode和hive.exec.reducers.max避免产生过多小分区。定期跑合并任务写一个脚本对特定表执行INSERT OVERWRITE TABLE ... SELECT ... DISTRIBUTE BY ...通过DISTRIBUTE BY确保数据均匀分布减少文件数。6.3 分区字段与数据目录不一致MSCK REPAIR 无效有时候我们把数据文件直接放到HDFS上手动创建了分区目录但Hive查询不到。执行MSCK REPAIR TABLE如果分区目录命名不规范比如目录名是dt2025-03-01而分区字段是dt STRING但实际值中包含了无效字符MSCK可能无法识别。解决办法手动ALTER TABLE ADD PARTITION或者使用MSCK REPAIR TABLE ... ADD PARTITIONS指定选项。不过最稳妥的方式是统一命名规范建议使用yyyyMMdd格式避免特殊字符。6.4 查询返回NULL但数据存在可能是SerDe解析问题如果数据文件中有数据但查询时某些字段显示NULL通常是SerDe序列化/反序列化解析失败。检查分隔符是否匹配比如数据文件用逗号但建表时指定了FIELDS TERMINATED BY \t。空值处理Hive默认将\N大写N视为NULL其他空字符串并不是NULL。如果数据中空字符串想转为NULL可以在查询时使用CASE WHEN col THEN NULL ELSE col END或者修改表属性TBLPROPERTIES (serialization.null.format)。字段类型不匹配比如数据是字符串abc但字段类型是INT会返回NULL。可以用cast(col as int)并检查错误。6.5 随机抽取100条数据别用ORDER BY rand()如前所述ORDER BY rand() LIMIT 100在数据量大的时候极其低效。推荐使用DISTRIBUTE BY rand() SORT BY rand() LIMIT 100或者在数据均匀分布的情况下使用TABLESAMPLE(100 ROWS)。但TABLESAMPLE可能不精确强烈建议用DISTRIBUTE BY方式。6.6 查看Map类型的size热词中有“hive 查看map类型的size”。直接使用size(map_column)函数即可返回Map中键值对的个数。注意如果Map列值为NULLsize()返回NULL而不是0。可以用COALESCE(size(map_col), 0)处理。6.7 stack函数行列转换stack函数用于将一行数据拆分成多行类似UNION ALL的变体但更灵活。语法stack(n, col1, col2, ..., colk)其中n表示要生成的行数后面参数是列值按顺序分配给每行。例如有一列是month1, month2, month3, month4想转成四行SELECT user_id, month_num, month_val FROM table LATERAL VIEW EXPLODE(ARRAY(1,2,3,4)) t AS month_num LATERAL VIEW EXPLODE(ARRAY(month1, month2, month3, month4)) t2 AS month_val;但更简洁的方式是使用stackSELECT user_id, month_num, month_val FROM table LATERAL VIEW EXPLODE(ARRAY(1,2,3,4)) t AS month_num LATERAL VIEW EXPLODE(ARRAY(month1, month2, month3, month4)) t2 AS month_val;注意stack的参数是列值不是数组。可以用stack(4, month1, month2, month3, month4)。但stack在Hive中需要配合LATERAL VIEW使用实际上stack本身是一个表生成函数可以单独使用SELECT stack(4, month1, month2, month3, month4) AS (month_num, month_val)但更常见的用法是嵌入到SELECT中。这里不展开建议参考官方文档。我实际工作中更喜欢用LATERAL VIEW EXPLODE(ARRAY(...))方式更直观。最后再分享一个小技巧调试Hive SQL时学会用EXPLAIN EXTENDED看执行计划很多新手写完SQL发现跑得慢但不知道瓶颈在哪。我每次遇到慢查询第一件事就是跑EXPLAIN EXTENDED SELECT ...查看Hive生成的执行计划。你会看到MapReduce的Stage划分每个Stage的输入输出以及是否有MapJoin、Bucket Map Join等优化提示。如果看到Stage-1中有Map Side File Merge或Partition Pruning说明分区裁剪生效了。如果看到Reduce阶段有Group By逻辑但数据量大的话可以考虑是否倾斜。执行计划是Hive调优的地图学会看它才能有的放矢地优化。再比如ORDER BY会强制一个ReduceSORT BY不会。如果你在EXPLAIN中看到Reduce阶段只有一个Reducer且数据量很大基本就是ORDER BY导致的。果断改成SORT BY或CLUSTER BY。Hive SQL基础就先讲到这里。从建表到查询从函数到复杂类型再到常见问题几乎覆盖了日常工作最高频的场景。这些内容我自己也反复踩坑总结出来的希望对你有帮助。如果你在实际使用中遇到其他问题欢迎留言讨论。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门