火花案例类 - 十进制编码器错误"Cannot up cast from decimal"



>我正在从MySQL/MariaDB中提取数据,在创建数据集期间,数据类型发生错误

线程"main"中的异常 org.apache.spark.sql.AnalysisException: 无法将AMOUNT从十进制 (30,6) 向上转换为十进制 (38,18) 截断 目标对象的类型路径为: - 字段(类:"org.apache.spark.sql.types.Decimal",名称:"AMOUNT") - 根类:"com.misp.spark.Deal" 您可以向输入数据添加显式强制转换,也可以选择更高精度的字段类型 在目标对象中;

案例类定义如下

case class
(
AMOUNT: Decimal
)

有人知道如何修复它而不接触数据库吗?

该错误表明 apache spark 无法自动将 BigDecimal(30,6) 从数据库转换为数据集中想要的 BigDecimal(38,18)(我不知道为什么它需要固定参数 38,18。更奇怪的是,spark不能自动将低精度的类型转换为高精度的类型)。

报告了一个错误:https://issues.apache.org/jira/browse/SPARK-20162(也许是你)。无论如何,我找到了很好的解决方法,可以通过将数据帧中的列转换为 BigDecimal(38,18),然后将数据帧转换为数据集来读取数据。

//first read data to dataframe with any way suitable for you
var df: DataFrame = ???
val dfSchema = df.schema
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types.DecimalType
dfSchema.foreach { field =>
field.dataType match {
case t: DecimalType if t != DecimalType(38, 18) =>
df = df.withColumn(field.name, col(field.name).cast(DecimalType(38,18)))
}
}
df.as[YourCaseClassWithBigDecimal]

它应该解决阅读问题(但我想不是写作问题)

如前所述,由于您的数据库使用DecimalType(30,6)这意味着您总共有 30 个插槽和 6 个超过小数点的插槽,这为小数点前面的区域留下了30-6=24。我喜欢称它为大十进制(24 left, 6 right)。这当然不适合(20 left, 18 right)(即DecimalType(38,18)),因为后者在左侧没有足够的插槽(需要 20 与 24)。我们DecimalType(38,18)只有 20 个左插槽,但我们需要 24 个左插槽来容纳您的DecimalType(30,6)

我们在这里能做的是将(24 left, 6 right)向下投射到(20 left, 6 right)(即DecimalType(26,6)),以便在它被自动转换为(20 left, 18 right)(即DecimalType(38,18))双方都将适合。您的DecimalType(26,6)将有 20 个左插槽,使其可以放入DecimalType(38,18)内,当然 6 个右侧插槽将适合 18 个。

执行此操作的方法是在将任何内容转换为数据集之前,对数据帧运行以下操作:

val downCastableData = 
originalData.withColumn("amount", $"amount".cast(DecimalType(26,6)))

然后转换为Dataset应该有效。

(实际上,您可以投射到任何(20 left, 6 right)或更少的内容,例如(19 left, 5 right)等等...

虽然我没有解决方案,但这是我对正在发生的事情的理解:

默认情况下,spark 将推断要DecimalType(38, 18)case classDecimal类型(或BigDecimal)的模式(请参阅org.apache.spark.sql.types.DecimalType.SYSTEM_DEFAULT)。38表示Decimal总共可以容纳38位数字(小数点的左侧和右侧),而18表示这38位数字中的18位保留在小数点的右侧。这意味着Decimal(38, 18)的小数点左侧可能有20位数字。您的 MySQL 架构是decimal(30, 6)的,这意味着它可能包含小数点左侧有24位 (30 - 6) 和小数点右侧有6位数字的值。由于24位数字大于20位,因此从 MySQL 架构转换为该Decimal类型的值可能会被截断。

不幸的是,从 scalacase class推断模式被 Spark 开发人员认为是一种方便,他们选择不支持允许程序员为case class中的DecimalBigDecimal类型指定精度和小数位数(参见 https://issues.apache.org/jira/browse/SPARK-18484)

基于@user2737635的答案,您可以使用foldLeft而不是foreach来避免将数据集定义为var并重新定义它:

//first read data to dataframe with any way suitable for you
val df: DataFrame = ???
val dfSchema = df.schema
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types.DecimalType
dfSchema.foldLeft(df){ 
(dataframe, field) =>  field.dataType match {
case t: DecimalType if t != DecimalType(38, 18) => dataframe.withColumn(field.name, col(field.name).cast(DecimalType(38, 18)))
case _ => dataframe
}
}.as[YourCaseClassWithBigDecimal]

我们正在通过定义我们在调用站点.as中使用的自己的Encoder来解决此问题。我们使用知道正确精度和小数位数的StructType生成Encoder(代码请参见下面的链接)。

https://issues.apache.org/jira/browse/SPARK-27339

根据 pyspark 的说法,Decimal(38,18)是默认的。

创建 DecimalType 时,默认精度和小数位数为 (10, 0)。从十进制推断架构时。十进制对象,它将是 DecimalType(38, 18)。

相关内容

  • 没有找到相关文章