作者:IvanCodes
日期:2025年5月1日
專欄:Hive教程
在數據處理的廣闊天地中,我們常常需要對數據進行轉換、計算、清洗或提取特定信息。Hive 提供了強大的內置運算符和豐富的內置函數庫,它們就像魔法師手中的魔法棒,能幫助我們靈活高效地操控數據,挖掘其深層價值。
思維導圖
一、Hive 內置運算符概覽
在深入函數之前,我們先快速回顧一下 Hive 支持的常見內置運算符,這些是構成表達式的基本組件:
- 算術運算符:
+
,-
,*
,/
,%
,DIV
- 關系運算符:
=
,<>
,!=
,<
,>
,<=
,>=
,IS NULL
,IS NOT NULL
,BETWEEN
,IN
,NOT IN
,LIKE
,RLIKE
,REGEXP
- 邏輯運算符:
AND
(&&
),OR
(||
),NOT
(!
) - 位運算符:
&
,|
,^
,~
- 復雜類型構造器:
MAP()
,STRUCT()
,ARRAY()
,NAMED_STRUCT()
- 復雜類型訪問運算符:
A[key]
,A.col
二、Hive 內置函數詳解
我們可以通過 SHOW FUNCTIONS;
查看所有可用函數,DESC FUNCTION [EXTENDED] <function_name>;
查看特定函數詳情。
1. 數值函數 (Mathematical Functions)
round(DOUBLE a)
: 四舍五入為BIGINT。
SELECT round(3.14159); -- 輸出: 3
round(DOUBLE a, INT d)
: 四舍五入到小數點后d位。
SELECT round(3.14159, 2); -- 輸出: 3.14
floor(DOUBLE a)
: 不大于a的最大整數。
SELECT floor(3.7); -- 輸出: 3
ceil(DOUBLE a)
或ceiling(DOUBLE a)
: 不小于a的最小整數。
SELECT ceil(3.1); -- 輸出: 4
abs(DOUBLE a)
: 絕對值。
SELECT abs(-5.5); -- 輸出: 5.5
pow(DOUBLE a, DOUBLE p)
或power(DOUBLE a, DOUBLE p)
: a的p次方。
SELECT pow(2, 3); -- 輸出: 8.0
sqrt(DOUBLE a)
: 平方根。
SELECT sqrt(16); -- 輸出: 4.0
rand()
或rand(INT seed)
: 0到1之間的隨機數。
SELECT rand(); -- 輸出: (一個隨機小數)
bin(BIGINT a)
: 二進制字符串。
SELECT bin(10); -- 輸出: 1010
hex(BIGINT a)
或hex(STRING a)
: 十六進制字符串。
SELECT hex(255); -- 輸出: FF
conv(BIGINT num, INT from_base, INT to_base)
: 進制轉換。
SELECT conv('A', 16, 10); -- 輸出: 10
2. 字符串函數 (String Functions)
length(STRING a)
: 字符串長度。
SELECT length('hello'); -- 輸出: 5
concat(STRING|BINARY a, STRING|BINARY b...)
: 連接字符串。
SELECT concat('Hi, ', 'Hive'); -- 輸出: Hi, Hive
concat_ws(STRING separator, STRING a, STRING b...)
: 帶分隔符連接。
SELECT concat_ws('-', 'A', 'B', 'C'); -- 輸出: A-B-C
lower(STRING a)
或lcase(STRING a)
: 轉小寫。
SELECT lower('HELLO'); -- 輸出: hello
upper(STRING a)
或ucase(STRING a)
: 轉大寫。
SELECT upper('world'); -- 輸出: WORLD
trim(STRING a)
: 去兩端空格。
SELECT trim(' Hive '); -- 輸出: Hive
substring(STRING a, INT start[, INT len])
: 取子串。
SELECT substring('HelloWorld', 1, 5); -- 輸出: Hello
replace(STRING a, STRING old, STRING new)
: 替換子串。
SELECT replace('banana', 'na', 'PA'); -- 輸出: baPAPA
instr(STRING str, STRING substr)
: 子串首次出現位置。
SELECT instr('apple-banana-orange', 'banana'); -- 輸出: 7
split(STRING str, STRING pat)
: 按正則分割,返回數組。
SELECT split('a,b,c', ','); -- 輸出: ["a","b","c"]
lpad(STRING str, INT len, STRING pad)
: 左填充。
SELECT lpad('hi', 5, '*'); -- 輸出: ***hi
rpad(STRING str, INT len, STRING pad)
: 右填充。
SELECT rpad('hi', 5, '*'); -- 輸出: hi***
regexp_extract(STRING subject, STRING pattern, INT index)
: 正則提取。
SELECT regexp_extract('user_123_name', 'user_(\\d+)_name', 1); -- 輸出: 123
regexp_replace(STRING initial_string, STRING pattern, STRING replacement)
: 正則替換。
SELECT regexp_replace('100-200', '-', ':'); -- 輸出: 100:200
3. 日期函數 (Date Functions)
current_date()
: 當前日期。
SELECT current_date(); -- 輸出: (類似 2023-10-27)
current_timestamp()
: 當前日期時間。
SELECT current_timestamp(); -- 輸出: (類似 2023-10-27 10:30:00.123)
unix_timestamp()
: 當前Unix時間戳。
SELECT unix_timestamp(); -- 輸出: (當前秒級時間戳)
unix_timestamp(STRING date[, STRING pattern])
: 日期轉Unix時間戳。
SELECT unix_timestamp('2023-10-27', 'yyyy-MM-dd'); -- 輸出: (對應時間戳)
from_unixtime(BIGINT unixtime[, STRING format])
: Unix時間戳轉日期字符串。
SELECT from_unixtime(1698381000, 'yyyy/MM/dd HH:mm'); -- 輸出: 2023/10/27 10:30
to_date(STRING timestamp)
: 提取日期部分。
SELECT to_date('2023-10-27 10:30:00'); -- 輸出: 2023-10-27
date_format(STRING/TIMESTAMP/DATE ts, STRING fmt)
: 格式化日期。
SELECT date_format(current_date(), 'yyyy年MM月dd日'); -- 輸出: (類似 2023年10月27日)
year(STRING date)
,month(STRING date)
,day(STRING date)
: 提取年/月/日。
SELECT year('2023-10-27'), month('2023-10-27'); -- 輸出: 2023, 10
date_add(STRING/TIMESTAMP/DATE startdate, INT days)
: 日期加天數。
SELECT date_add('2023-10-27', 3); -- 輸出: 2023-10-30
date_sub(STRING/TIMESTAMP/DATE startdate, INT days)
: 日期減天數。
SELECT date_sub('2023-10-27', 3); -- 輸出: 2023-10-24
datediff(STRING enddate, STRING startdate)
: 日期天數差。
SELECT datediff('2023-10-30', '2023-10-27'); -- 輸出: 3
4. 條件函數 (Conditional Functions)
if(boolean testCondition, T valueTrue, T valueFalseOrNull)
: IF判斷。
SELECT if(1 > 0, 'Yes', 'No'); -- 輸出: Yes
CASE WHEN a THEN b [WHEN c THEN d]* [ELSE e] END
: 標準CASE WHEN。
SELECT
CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
ELSE 'C'
END AS grade
FROM (SELECT 85 AS score) t; -- 輸出: B
COALESCE(T v1, T v2, ...)
: 返回首個非NULL值。
SELECT COALESCE(NULL, 'Default1', 'Default2'); -- 輸出: Default1
NVL(T value, T default_value)
: value非NULL則返回value,否則返回default_value。
SELECT NVL(NULL, 'Is Null'); -- 輸出: Is Null
5. 類型轉換函數 (Type Conversion Functions)
cast(expr AS type)
: 強制類型轉換。
SELECT cast('123' AS INT) + cast('7' AS INT); -- 輸出: 130
6. 聚合函數 (Aggregate Functions) (常與 GROUP BY
聯用)
count(*)
或count(expr)
或count(DISTINCT expr)
: 計數。
SELECT count(*) FROM my_table;
SELECT count(DISTINCT user_id) FROM user_logs;
sum(col)
或sum(DISTINCT col)
: 求和。
SELECT sum(sales_amount) FROM sales_data;
avg(col)
或avg(DISTINCT col)
: 平均值。
SELECT avg(score) FROM student_scores;
min(col)
: 最小值。
SELECT min(price) FROM products;
max(col)
: 最大值。
SELECT max(temperature) FROM weather_records;
7. 集合函數 (Collection Functions) (處理ARRAY, MAP, STRUCT)
size(MAP<K,V> m)
或size(ARRAY<T> a)
: 返回Map或Array的大小。
SELECT size(array(1, 2, 3)); -- 輸出: 3
SELECT size(map('a', 1, 'b', 2)); -- 輸出: 2
array_contains(ARRAY<T> a, value)
: 判斷Array是否包含指定值。
SELECT array_contains(array('apple', 'banana'), 'apple'); -- 輸出: true
map_keys(MAP<K,V> m)
: 返回Map的所有鍵組成的Array。
SELECT map_keys(map('name', 'Alice', 'age', 30)); -- 輸出: ["name","age"] (順序可能不同)
map_values(MAP<K,V> m)
: 返回Map的所有值組成的Array。
SELECT map_values(map('name', 'Alice', 'age', 30)); -- 輸出: ["Alice","30"] (順序可能不同)
sort_array(ARRAY<T> a)
: 對Array進行排序。
SELECT sort_array(array(3, 1, 2)); -- 輸出: [1,2,3]
8. 表生成函數 (Table-Generating Functions - UDTF)
explode(ARRAY<T> a)
: 將數組每個元素轉為一行。
SELECT explode(array('A', 'B')) AS item;
-- 輸出:
-- A
-- B
explode(MAP<K,V> m)
: 將Map每個鍵值對轉為一行兩列。
SELECT explode(map('key1', 'val1')) AS (my_key, my_value);
-- 輸出:
-- key1 val1
posexplode(ARRAY<T> a)
: 類似explode(array)
,額外輸出元素在數組中的位置(從0開始)。
SELECT posexplode(array('X', 'Y')) AS (pos, val);
-- 輸出:
-- 0 X
-- 1 Y
inline(ARRAY<STRUCT<f1:T1, f2:T2, ...>> a)
: 將結構體數組展開,每個結構體的字段成為獨立的列。
-- 假設table t有列 arr_struct: ARRAY<STRUCT<name:STRING, age:INT>>
-- 且某行 arr_struct 值為 [named_struct('name','Tom','age',20), named_struct('name','Jerry','age',18)]
-- SELECT inline(arr_struct) FROM t;
-- 輸出 (對于該行會產生兩行結果):
-- Tom 20
-- Jerry 18
9. 數據脫敏函數 (Data Masking/Anonymization - 通過組合或UDF實現)
Hive 內置的直接、專用的脫敏函數較少。通常通過組合現有字符串函數來實現,或者編寫用戶自定義函數 (UDF) 來完成復雜的脫敏邏輯。
- 示例:部分屏蔽手機號 (組合字符串函數)
-- 假設 phone_number 是 '13812345678'
SELECT concat(substring(phone_number, 1, 3), '****', substring(phone_number, 8)) AS masked_phone
FROM (SELECT '13812345678' AS phone_number) t;
-- 輸出: 138****5678
- 示例:屏蔽郵箱用戶名 (組合字符串函數)
-- 假設 email 是 'user_example@domain.com'
SELECT concat(substring(email, 1, 1), -- 第一個字符'****', -- 屏蔽符substring(email, locate('@', email) - 1, 1), -- @前一個字符substring(email, locate('@', email)) -- @及之后的部分
) AS masked_email
FROM (SELECT 'user_example@domain.com' AS email) t;
-- 輸出: u****e@domain.com (這是一個簡化示例,更復雜的正則UDF效果更好)
對于更復雜或通用的脫敏需求(如身份證號、銀行卡號按規則屏蔽,或基于角色的動態脫敏),通常推薦開發 UDF。
10. 用戶自定義函數 (拓展)
當內置函數無法滿足特定的業務邏輯時,Hive 允許用戶使用 Java (或其他語言,但Java最常見) 編寫自己的函數。
-
UDF (User-Defined Function): 一對一或多對一函數,輸入一行中的一個或多個值,輸出一個值。
- 例如,創建一個
to_uppercase(string_col)
函數,將輸入字符串轉為大寫(雖然已有內置upper
)。 - 創建和使用步驟 (簡要):
- 編寫 Java 類繼承
org.apache.hadoop.hive.ql.exec.UDF
。 - 實現
evaluate()
方法。 - 將 Java 項目打成 JAR 包。
- 在 Hive 中注冊 JAR 包:
ADD JAR /path/to/your.jar;
- 創建臨時或永久函數:
CREATE TEMPORARY FUNCTION my_upper AS 'com.example.MyUpperUDF';
- 使用函數:
SELECT my_upper(name) FROM my_table;
- 編寫 Java 類繼承
- 例如,創建一個
-
UDAF (User-Defined Aggregate Function): 多對一函數,類似
SUM()
,COUNT()
,對分組數據進行聚合。編寫 UDAF 相對復雜。 -
UDTF (User-Defined Table-Generating Function): 一對多函數,類似
explode()
,輸入一行,輸出多行。
練習題
- 將字符串 “hive is great” 中的所有空格替換為下劃線
_
。 - 計算日期 “2023-01-15” 之后45天的日期。
- 有一個用戶評分表
ratings (user_id INT, score INT)
,如果用戶評分低于60則標記為 “不及格”,否則標記為 “及格”。寫出查詢。 - 將字符串 “ID:123,Name:Alice,Age:30” 按逗號分割,然后對每個部分按冒號分割,提取出 Name 的值 “Alice”。
- 有一個字段
tags
其值為 ARRAY 類型,如['TagA', 'TagB', 'TagC']
。檢查該數組是否包含 ‘TagB’。 - 將一個包含用戶月消費金額的數組
monthly_spend ARRAY<INT>
(例如[100, 150, 120]
) 展開成多行,每行顯示一個月的消費金額。 - 假設有一個字段
ip_address
存儲IP地址字符串,請使用內置函數(如果可能)將其最后一個點之后的部分替換為 ‘XXX’ (例如 ‘192.168.1.100’ -> ‘192.168.1.XXX’)。如果內置函數難以實現,請描述UDF的思路。 - 將 Unix 時間戳
1672531200
(代表 2023-01-01 00:00:00 UTC) 格式化為 “YYYY年MM月DD日 HH時mm分ss秒” 的形式。 - 從字符串 “apple,banana,orange,apple” 中提取出所有不重復的水果名稱,并計算不重復水果的數量。
- 如何使用
COALESCE
函數處理一個可能為NULL的middle_name
字段,如果它為NULL,則在拼接全名時用空字符串代替?例如,first_name
,middle_name
,last_name
。
練習題答案
- 將字符串 “hive is great” 中的所有空格替換為下劃線
_
。
SELECT replace('hive is great', ' ', '_');
- 計算日期 “2023-01-15” 之后45天的日期。
SELECT date_add('2023-01-15', 45);
- 有一個用戶評分表
ratings (user_id INT, score INT)
,如果用戶評分低于60則標記為 “不及格”,否則標記為 “及格”。寫出查詢。
SELECT user_id, score, if(score < 60, '不及格', '及格') AS status FROM ratings;
-- 或者使用 CASE WHEN
-- SELECT user_id, score, CASE WHEN score < 60 THEN '不及格' ELSE '及格' END AS status FROM ratings;
- 將字符串 “ID:123,Name:Alice,Age:30” 按逗號分割,然后對每個部分按冒號分割,提取出 Name 的值 “Alice”。
SELECT split(kv[1], ':')[1] AS name_value
FROM (SELECT split('ID:123,Name:Alice,Age:30', ',') AS kv_array
) t
LATERAL VIEW explode(kv_array) exploded_table AS kv_pair_str
WHERE split(kv_pair_str, ':')[0] = 'Name';
-- 更簡潔的方式如果知道Name總在第二個位置:
SELECT split(split('ID:123,Name:Alice,Age:30', ',')[1], ':')[1];
- 有一個字段
tags
其值為 ARRAY 類型,如['TagA', 'TagB', 'TagC']
。檢查該數組是否包含 ‘TagB’。
SELECT array_contains(tags, 'TagB') FROM (SELECT array('TagA', 'TagB', 'TagC') AS tags) t;
- 將一個包含用戶月消費金額的數組
monthly_spend ARRAY<INT>
(例如[100, 150, 120]
) 展開成多行,每行顯示一個月的消費金額。
SELECT spend
FROM (SELECT array(100, 150, 120) AS monthly_spend) t
LATERAL VIEW explode(monthly_spend) exploded_table AS spend;
- 假設有一個字段
ip_address
存儲IP地址字符串,請使用內置函數(如果可能)將其最后一個點之后的部分替換為 ‘XXX’ (例如 ‘192.168.1.100’ -> ‘192.168.1.XXX’)。如果內置函數難以實現,請描述UDF的思路。
使用內置函數比較復雜,需要多次使用instr
和substring
或regexp_replace
:
SELECT regexp_replace(ip_address, '\\.[^.]+$', '.XXX') AS masked_ip
FROM (SELECT '192.168.1.100' AS ip_address) t;
-- 或者更精確控制,找到最后一個點的位置
-- SELECT concat(substring(ip_address, 1, instr(reverse(ip_address), '.') * -1 + length(ip_address) -1), '.XXX') ... (這種方式更復雜且易錯)
UDF思路:編寫一個Java UDF,輸入IP字符串。在Java中使用 lastIndexOf('.')
找到最后一個點的位置,然后使用 substring()
截取前面的部分,并拼接上 “.XXX”。
- 將 Unix 時間戳
1672531200
(代表 2023-01-01 00:00:00 UTC) 格式化為 “YYYY年MM月DD日 HH時mm分ss秒” 的形式。
SELECT from_unixtime(1672531200, 'yyyy年MM月dd日 HH時mm分ss秒');
- 從字符串 “apple,banana,orange,apple” 中提取出所有不重復的水果名稱,并計算不重復水果的數量。
SELECT collect_set(fruit) AS unique_fruits, size(collect_set(fruit)) AS unique_fruit_count
FROM (SELECT explode(split('apple,banana,orange,apple', ',')) AS fruit
) t_fruits;
- 如何使用
COALESCE
函數處理一個可能為NULL的middle_name
字段,如果它為NULL,則在拼接全名時用空字符串代替?例如,first_name
,middle_name
,last_name
。
SELECT concat_ws(' ', first_name, COALESCE(middle_name, ''), last_name) AS full_name
FROM (SELECT 'John' AS first_name, NULL AS middle_name, 'Doe' AS last_nameUNION ALLSELECT 'Jane' AS first_name, 'M' AS middle_name, 'Smith' AS last_name
) t_names;
-- 對于 'John', NULL, 'Doe' 會輸出 "John Doe" (注意concat_ws對空字符串的處理)
-- 如果希望中間沒有多余空格,可以這樣:
-- SELECT trim(concat(first_name, ' ', COALESCE(concat(middle_name, ' '), ''), last_name)) AS full_name ...
更優雅的方式是針對 concat_ws
的特性,如果 COALESCE(middle_name, NULL)
結果是 NULL,concat_ws
會跳過它。但如果希望它是空字符串而不是被跳過(比如為了保持名字間的空格),上面的 COALESCE(middle_name, '')
是對的。