Spark函数平均和BigDecimal的缩放问题

时间:2019-09-04 13:00:46

标签: scala apache-spark

我有这个案例课:

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

1 个答案:

答案 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)