【Hive笔记】 日常踩坑
隐形转换异常
- Hive 版本为
1.2.1
- 场景简述: 针对字符串字段与常量数值进行对比判断
- 数据问题: 结果存在
null
情况
问题 SQL 举例如下:
SELECT 'default' <> -1
情景分析: SELECT 'default' <> -1
实际上相当于
SELECT CAST('default' AS INT) <> -1
由于左侧为
null
其结果也为 null
扩展思考
思考如下两种情况
SELECT 01 IN ('01')
-- 实际执行: SELECT CAST(01 AS STRING) IN ('01')
-- 请注意 01 会在分析阶段转换为 1
-- false
SELECT 01 = '01'
-- 实际执行: SELECT 01 = CAST('01' AS INT)
-- true
其实际原因在于 Hive
底层对于 IN
和
=
不同的隐形转换优先级方式
比较函数的隐形转换分析
针对比较函数('=')其文件位置在 GenericUDFOPEqual.java
根据针对 GenericUDF
的了解, 其 initialize
函数是用于解析输入参数的类型和进行初始化操作, 该文件并没有,
因此需要向上回溯其父类 GenericUDFBaseCompare.java
针对 initialize
进行分析可以知道
- 如果比较的两个变量类型相同, 即直接进行该类型的比较(设定
compareType
并在GenericUDFOPEqual.java
中使用) - 如果比较的两个变量类型不同, 通过
FunctionRegistry.getCommonClassForComparison
进行compareType
设定
仍需要向上追溯 FunctionRegistry.java, 可以找到最终的默认隐形转换代码:
public static TypeInfo getCommonClassForComparison(TypeInfo a, TypeInfo b) {
// 前面基本逻辑: 若均为 String 族, 则使用 String 比较; 若互相为数值族和时间戳, 则采用 double 比较
//
for (PrimitiveCategory t : numericTypeList) {
if (FunctionRegistry.implicitConvertible(pcA, t)
&& FunctionRegistry.implicitConvertible(pcB, t)) {
return getTypeInfoForPrimitiveCategory((PrimitiveTypeInfo)a, (PrimitiveTypeInfo)b, t);
}
}
//...
}
该部分则利用 implicitConvertible
判断若两者均可转为统一数值类型,
则进行该类型的转换(其中比较两者之间的优先级)
public static boolean implicitConvertible(PrimitiveCategory from, PrimitiveCategory to) {
// 前面基本逻辑:
// 允许从 String 转 Double, Decimal, Date
// 允许数值族 转 String族
// Allow implicit conversion from Byte -> Integer -> Long -> Float -> Double
// Decimal -> String
Integer f = numericTypes.get(from);
Integer t = numericTypes.get(to);
if (f == null || t == null) {
return false;
}
if (f.intValue() > t.intValue()) {
return false;
}
return true;
}
// 优先级部分的设置代码
static EnumMap<PrimitiveCategory, Integer> numericTypes =
new EnumMap<PrimitiveCategory, Integer>(PrimitiveCategory.class);
static List<PrimitiveCategory> numericTypeList = new ArrayList<PrimitiveCategory>();
static void registerNumericType(PrimitiveCategory primitiveCategory, int level) {
numericTypeList.add(primitiveCategory);
numericTypes.put(primitiveCategory, level);
}
static {
registerNumericType(PrimitiveCategory.BYTE, 1);
registerNumericType(PrimitiveCategory.SHORT, 2);
registerNumericType(PrimitiveCategory.INT, 3);
registerNumericType(PrimitiveCategory.LONG, 4);
registerNumericType(PrimitiveCategory.FLOAT, 5);
registerNumericType(PrimitiveCategory.DOUBLE, 6);
registerNumericType(PrimitiveCategory.DECIMAL, 7);
registerNumericType(PrimitiveCategory.STRING, 8);
}
IN 函数的隐形转换分析
针对比较函数('IN')其文件位置在 GenericUDFIn.java,
可以看出该函数是自身含有 initialize
函数利用
GenericUDFUtils
进行类型的优先级匹配
因此向上回溯 GenericUDFUtils.java
其中利用 update
函数不断比较每个变量的类型情况,
关键代码如下
private boolean update(ObjectInspector oi, boolean isUnionAll) throws UDFArgumentTypeException {
// ...
TypeInfo commonTypeInfo = null;
if (isUnionAll) {
commonTypeInfo = FunctionRegistry.getCommonClassForUnionAll(oiTypeInfo,
rTypeInfo);
} else { // 采用该情况, 因为无参数 update 默认 isunionall 为 false
commonTypeInfo = FunctionRegistry.getCommonClass(oiTypeInfo,
rTypeInfo);
}
if (commonTypeInfo == null) {
return false;
}
// ...
}
因此又回到 FunctionRegistry.java文件,
其中 getCommonClass
函数调用 getCommonCategory
函数, 关键隐形转换代码如下
public static PrimitiveCategory getCommonCategory(TypeInfo a, TypeInfo b) {
Integer ai = numericTypes.get(pcA);
Integer bi = numericTypes.get(pcB);
if (ai == null || bi == null) {
// If either is not a numeric type, return null.
return null;
}
return (ai > bi) ? pcA : pcB;
}
可以看出与比较的隐形转换相比, 该部分采用正常的向上优先级转换的情况.
总结
针对 HIVE 1.2.1
实际上存在三种隐形转换:
getCommonClass
: 采用正常的优先级排序比较, 非比较类型基本均为该逻辑getCommonClassForComparison
: 在针对同族情况无效时, 采用向下比较优先级的方式(为防止用户使用出现异常错误), 非比较类型基本均为该逻辑getCommonClassForUnionAll
: 在存在一方非 String族 情况下, 无法实现相互转化情况下, 则采用数值向下比较优先级的方式(为防止用户使用出现异常错误)
LEFT JOIN 多个相同表问题
- Hive 版本为
1.2.1
SPARK 版本2.4.5
均可复现 - 场景简述: 多个相同表取数, 并且
LEFT JOIN
到主表 - 数据问题: 多个表仅会有一个表成功
JOIN
出对应数据
问题 SQL 举例如下:
SELECT
KEY1,
KEY2,
NVL(VALUE1, 0) AS VALUE1,
NVL(VALUE2, 0) AS VALUE2,
NVL(VALUE3, 0) AS VALUE3
FROM (
SELECT KEY1, KEY2
FROM TABLE_1
) t1
LEFT JOIN (
SELECT KEY1, KEY2, VALUE1
FROM TABLE_2
WHERE type = 'value1'
) t2
ON t1.KEY1 = t2.KEY1
AND t1.KEY2 = t2.KEY2
LEFT JOIN (
SELECT KEY1, KEY2, VALUE2
FROM TABLE_2
WHERE type = 'value2'
) t3
ON t1.KEY1 = t3.KEY1
AND t1.KEY2 = t3.KEY2
LEFT JOIN (
SELECT KEY1, KEY2, VALUE3
FROM TABLE_2
WHERE type = 'value3'
) t4
ON t1.KEY1 = t4.KEY1
AND t1.KEY2 = t4.KEY2
通过利用
EXPLAIN
可以看出针对TABLE_2
的TableScan
会在同一个Stage
的Map Operator Tree
下, 并且可以看出最后一个TableScan
即为成功获取到数据的LEFT JOIN
, 因此推断在实际构建数据时, 发生针对该多个LEFT JOIN
的数据覆盖问题
解决方法: 利用 DISTINCT 从而拆分每个
LEFT JOIN
任务, 数据恢复正常