我有这个案例课:
case class AllData(positionId: Long, warehouse: String, product: String, amount: BigDecimal, amountTime: Long)
和数据集:
val data: Dataset[AllData]
和此代码:
val statisticForAmounts = data.groupByKey(record => record.warehouse + ", " + record.product)
.agg(
max($"amount").as("maxAmount").as[BigDecimal],
avg($"amount").as("avgAmount").as[BigDecimal]
)
产生这种模式:
root
|-- value: string (nullable = true)
|-- maxAmount: decimal(38,18) (nullable = true)
|-- avgAmount: decimal(38,22) (nullable = true)
avgAmount的比例较大,会导致问题,产生错误
org.apache.spark.sql.AnalysisException: Cannot up cast `avgAmount` from decimal(38,22) to decimal(38,18) as it may truncate
The type path of the target object is:
- root class: "scala.math.BigDecimal"
我还尝试过这种方式对数据进行取整:
val statisticForAmounts = data.groupByKey(record => record.warehouse + ", " + record.product)
.agg(
round(max($"amount"), 4).as("maxAmount").as[BigDecimal],
round(avg($"amount"), 4).as("avgAmount").as[BigDecimal]
)
此架构已更改为:
root
|-- value: string (nullable = true)
|-- maxAmount: decimal(38,4) (nullable = true)
|-- avgAmount: decimal(38,4) (nullable = true)
这次错误是:
org.apache.spark.sql.AnalysisException: Cannot up cast `maxAmount` from decimal(38,4) to decimal(38,18) as it may truncate
The type path of the target object is:
- root class: "scala.math.BigDecimal"
为什么会这样,如何防止这种情况发生?我有Spark 2.4
答案 0 :(得分:0)
似乎是这样的情况:avg
函数在小数点后又增加了4个槽,以解决分数除法时对缩放的需求增加。 DecimalType(38,18)
表示您总共有38个插槽,小数点后还有18个插槽(38-18=20
保留小数点前的区域)。
一种更简单的思考方法是,左侧有20个插槽,右侧有18个插槽,因此DecimalType(38,18) = (20 left, 18 right)
。同样,DecimalType(38,22) = (16 left, 22 right)
。当然不可能将(16,22)
塞入(20,18)
,因为将会丢失4个左槽(即十进制槽)(尽管您将有4个右槽)
现在,由于AVG函数似乎在左侧(即小数点后)增加了4个插槽,因此基本上是(+0 right, +4 left)
的操作。一旦完成了这个奇怪的过程,我们就不得不将(16 left, 22 right)
转换回标准DecimalType(38,18)
,即(20 left, 18 right)
,因为这是Scala / Java的默认设置。
所以...我们需要确保生成的数字实际上可以放入这些插槽中。因此,如果我们从一开始就从(20 left, 18 right)
向下广播到(20 left, 14 right)
(即DecimalType(34, 14)
),那么(+0 right, +4 left)
只会把我们带回到{{1} },我们正在寻找。换句话说,执行以下操作:
(20 left, 18 right)
然后,这样做应该可以:
val averageableData =
data.withColumn("amount", $"amount".cast(DecimalType(34,14)))
有趣的是,您实际上可以将任何类型转换为小于averageableData.groupByKey(record => record.warehouse + ", " + record.product)
.agg(
max($"amount").as("maxAmount").as[BigDecimal],
avg($"amount").as("avgAmount").as[BigDecimal]
)
的内容。也就是说,您可以做(20 left, 14 right)
或更少的事情。当.cast(DecimalType(33, 13))
操作发生时,它仍将适合(+0 left, +4 right)
。