4. Hive数据类型:基本数据类型、复杂数据类型、类型转换、数据类型的应用场景

聊到Hive,数据类型是绕不开的第一道坎。很多新手觉得这玩意儿就是定义字段时填个int、string就完事了。嗯,我刚开始也这么想,直到有一次在项目中因为类型搞错,跑了一整天的ETL任务全废了……从那以后,我对数据类型就格外上心。

说白了,Hive的数据类型设计,很大程度上借鉴了Java和SQL的思路。你如果写过Java,上手会很快。但Hive也有自己独特的地方,尤其是复杂数据类型,这是传统关系型数据库里不太常见的。

4.1 基本数据类型

基本数据类型,就是那些最基础、最常用的类型。我习惯把它们分成四类:数值型、字符串型、日期时间型、布尔型。

分类 类型 描述 示例
数值型 TINYINT 1字节整数,范围-128~127 100Y
SMALLINT 2字节整数,范围-32768~32767 100S
INT 4字节整数,最常用 100
BIGINT 8字节整数,处理大数时用 100L
浮点型 FLOAT 4字节单精度 3.14F
DOUBLE 8字节双精度,默认浮点类型 3.14
DECIMAL 高精度小数,适合金额 DECIMAL(10,2)
字符串型 STRING 变长字符串,最常用 'hello'
VARCHAR 定长字符串,Hive 0.12+ VARCHAR(50)
日期时间 DATE 日期,格式yyyy-MM-dd '2024-01-01'
TIMESTAMP 时间戳,精确到纳秒 '2024-01-01 12:00:00'
布尔型 BOOLEAN true/false true
我的习惯:能用INT就别用BIGINT,除非你确定数据会超过21亿。DECIMAL做金额计算时一定要指定精度,否则默认DECIMAL(10,0)会把你坑惨——我见过有人算工资时小数位全被截掉了。

4.2 复杂数据类型

复杂数据类型,这才是Hive的亮点。传统数据库一张表只能存扁平结构,但Hive允许你在一个字段里塞数组、字典甚至嵌套结构。你想想看,处理JSON日志或者多层嵌套的XML数据时,这有多方便。

4.2.1 ARRAY

数组,存储一组相同类型的元素。定义时用 ARRAY<类型>

-- 建表
CREATE TABLE student_scores (
    name STRING,
    scores ARRAY<INT>
);

-- 插入数据
INSERT INTO student_scores VALUES ('张三', array(85, 90, 78));

-- 查询:用下标访问,下标从0开始
SELECT name, scores[0] AS first_score FROM student_scores;
-- 结果:张三, 85
注意:ARRAY的下标从0开始,但如果你越界访问,Hive不会报错,而是返回NULL。我曾经排查过一个bug,查了半天才发现是下标写错了。

4.2.2 MAP

键值对集合,类似Java的HashMap。定义时用 MAP<键类型, 值类型>

-- 建表
CREATE TABLE employee_contact (
    name STRING,
    contact MAP<STRING, STRING>
);

-- 插入数据
INSERT INTO employee_contact VALUES 
('李四', map('phone', '13800138000', 'email', 'lisi@example.com'));

-- 查询:用['key']访问
SELECT name, contact['phone'] AS phone FROM employee_contact;

我个人建议,MAP里的键尽量用STRING类型。用INT做键虽然可以,但可读性会差很多。你想想看,别人维护你的代码时,看到map[1]和map['dept_id'],哪个更清晰?

4.2.3 STRUCT

结构体,可以包含不同类型的字段。定义时用 STRUCT<字段名:类型, ...>

-- 建表
CREATE TABLE user_info (
    user_id INT,
    address STRUCT<city:STRING, street:STRING, zip:INT>
);

-- 插入数据
INSERT INTO user_info VALUES 
(1, named_struct('city', '北京', 'street', '长安街', 'zip', 100000));

-- 查询:用.访问字段
SELECT user_id, address.city FROM user_info;
避坑指南:我曾经在项目中用STRUCT嵌套了3层,结果查询时性能惨不忍睹。复杂数据类型虽然灵活,但嵌套层级建议不超过2层,否则MapReduce的序列化开销会让你崩溃。

4.2.4 UNIONTYPE

联合类型,可以存储多种不同类型中的一种。说实话,这个类型在实际项目中用得很少,我做了这么多年Hive,也就用过一两次。定义时用 UNIONTYPE<类型1, 类型2, ...>

CREATE TABLE mixed_data (
    id INT,
    value UNIONTYPE<INT, STRING, FLOAT>
);

-- 插入时用create_union(标签, 值)
INSERT INTO mixed_data VALUES 
(1, create_union(0, 100)),      -- 标签0表示INT
(2, create_union(1, 'hello')),  -- 标签1表示STRING
(3, create_union(2, 3.14));     -- 标签2表示FLOAT

4.3 类型转换

类型转换分两种:隐式转换和显式转换。说白了,隐式转换是Hive帮你自动转,显式转换是你自己用函数强制转。

4.3.1 隐式转换规则

Hive在以下情况会自动做类型提升:

  • TINYINT → SMALLINT → INT → BIGINT → FLOAT → DOUBLE
  • STRING → DOUBLE(如果字符串是数字格式)
  • 所有类型都可以隐式转成STRING
-- 隐式转换示例
SELECT 1 + 2.5;  -- 结果是3.5,INT自动转成DOUBLE
SELECT '100' > 50;  -- 结果是true,STRING '100'转成INT 100
注意:隐式转换不是万能的。比如STRING转INT时,如果字符串不是纯数字,会返回NULL。我遇到过有人用STRING存手机号,然后跟INT做比较,结果一堆数据对不上。

4.3.2 显式转换

用CAST函数强制转换:

-- 显式转换语法
CAST(expression AS type)

-- 示例
SELECT CAST('123' AS INT);           -- 123
SELECT CAST(3.14159 AS INT);         -- 3,直接截断小数
SELECT CAST('2024-01-01' AS DATE);   -- 2024-01-01

-- 转换失败返回NULL
SELECT CAST('abc' AS INT);           -- NULL
我的建议:在ETL清洗阶段,尽量用显式转换。虽然代码会多写几个字,但可读性和稳定性都好很多。隐式转换有时候会给你"惊喜"——比如'100abc'转成INT时,Hive不会报错,而是默默返回NULL。

4.4 数据类型的应用场景

光知道类型定义还不够,关键是要知道什么时候用什么类型。我根据项目经验,总结了几条实用建议:

4.4.1 数值型选型

  • ID字段:用INT或BIGINT。别用STRING存数字ID,查询性能差很多。
  • 金额字段:必须用DECIMAL。FLOAT和DOUBLE有精度问题,算钱会出大事。
  • 计数类:用INT就够了,除非你统计的是全国人口。

4.4.2 字符串型选型

  • 普通文本:用STRING,长度不限,最灵活。
  • 固定长度字段:比如身份证号、手机号,可以用VARCHAR(18)或VARCHAR(11),能节省一点存储。
  • JSON或XML:用STRING存,配合get_json_object或json_tuple解析。

4.4.3 复杂数据类型选型

  • 一对多关系:用ARRAY。比如一个学生有多门课的成绩。
  • 键值对查询:用MAP。比如用户的各种属性配置。
  • 多字段组合:用STRUCT。比如地址信息、坐标信息。
  • 尽量避免:复杂类型嵌套超过2层,以及UNIONTYPE(除非你真的需要)。
避坑指南:我曾经接手过一个项目,前人把所有的用户标签都塞进了一个MAP<STRING, STRING>字段里,结果查询时每次都要遍历整个MAP,性能极差。后来我改成了用多个单独的字段存储,查询速度提升了10倍。复杂数据类型虽好,但不要滥用。

4.4.4 日期时间选型

  • 只需要日期:用DATE,占用4字节。
  • 需要精确时间:用TIMESTAMP,占用8字节。
  • 千万别:用STRING存日期。虽然也能用,但无法使用日期函数,而且排序会出问题。

嗯,关于Hive的数据类型,差不多就这些了。记住一句话:选类型时多花一分钟思考,后面能省一小时排错。我在项目中见过太多因为类型选错导致的线上事故了,希望你引以为戒。