如何将具有 Decimal 的 spark DataFrame 转换为具有相同精度的 BigDecimal 的 Dataset?
Posted
技术标签:
【中文标题】如何将具有 Decimal 的 spark DataFrame 转换为具有相同精度的 BigDecimal 的 Dataset?【英文标题】:How to convert a spark DataFrame with a Decimal to a Dataset with a BigDecimal of the same precision? 【发布时间】:2019-11-14 15:33:07 【问题描述】:如何以给定的精度创建具有 BigDecimal 的 spark 数据集?请参阅 spark shell 中的以下示例。你会看到我可以创建一个具有所需 BigDecimal 精度的 DataFrame,但不能将其转换为 Dataset。
scala> import scala.collection.JavaConverters._
scala> case class BD(dec: BigDecimal)
scala> val schema = StructType(Seq(StructField("dec", DecimalType(38, 0))))
scala> val highPrecisionDf = spark.createDataFrame(List(Seq(BigDecimal("12345678901122334455667788990011122233"))).map(a => Row.fromSeq(a)).asJava, schema)
highPrecisionDf: org.apache.spark.sql.DataFrame = [dec: decimal(38,0)]
scala> highPrecisionDf.as[BD]
org.apache.spark.sql.AnalysisException: Cannot up cast `dec` from decimal(38,0) to decimal(38,18) as it may truncate
The type path of the target object is:
- field (class: "scala.math.BigDecimal", name: "dec")
- root class: "BD"
You can either add an explicit cast to the input data or choose a higher precision type of the field in the target object;
同样,我无法从使用更高精度 BigDecimal 的案例类创建数据集。
scala> List(BD(BigDecimal("12345678901122334455667788990011122233"))).toDS.show()
+----+
| dec|
+----+
|null|
+----+
有没有办法创建一个包含 BigDecimal 字段的数据集,该字段的精度与默认小数 (38,18) 不同?
【问题讨论】:
【参考方案1】:默认情况下,spark 会将案例类中的 Decimal 类型(或 BigDecimal)的架构推断为 DecimalType(38, 18)(请参阅org.apache.spark.sql.types.DecimalType.SYSTEM_DEFAULT
)
解决方法是将数据集转换为数据框,如下所示
case class TestClass(id: String, money: BigDecimal)
val testDs = spark.createDataset(Seq(
TestClass("1", BigDecimal("22.50")),
TestClass("2", BigDecimal("500.66"))
))
testDs.printSchema()
root
|-- id: string (nullable = true)
|-- money: decimal(38,18) (nullable = true)
解决方法
import org.apache.spark.sql.types.DecimalType
val testDf = testDs.toDF()
testDf
.withColumn("money", testDf("money").cast(DecimalType(10,2)))
.printSchema()
root
|-- id: string (nullable = true)
|-- money: decimal(10,2) (nullable = true)
您可以查看此链接以获取更多详细信息https://issues.apache.org/jira/browse/SPARK-18484)
【讨论】:
谢谢。听起来现在根本不可能拥有具有特定精度 BigDecimal 的数据集。不幸的是,我们代码库中的大多数 DataFrame 中都有这样的 BigDecimal。在任何地方使用 DataFrame 意味着我们在类型安全和可读性方面损失了很多,因为读者知道 DataFrame 在不同点上存在哪些列:( 看起来像。 Dataframes 将为您提供与 Dataset 类似的类型安全性,但如果您认为 Dataset 是他们的方式,那么请提出功能请求,让我们看看反应是什么。如果这个答案有帮助,请接受答案。 DataFrames 不提供类型安全,因为编译器对列的名称或类型一无所知,因此无法验证。问题的另一半是他们也没有向读者提供这些信息。例如如果您在某些代码中将表格读取到数据框,则无法(无需手动检查表格)知道您拥有哪些列。我将等待一天,看看是否有人为具有自定义精度 BigDecimals 的数据集提供解决方案。如果没有其他答案,我会接受这个作为答案。 啊,我明白你的意思了。我从类型安全的角度考虑了其他事情【参考方案2】:我发现的一种解决方法是在数据集中使用字符串来保持精度。如果您不需要将值用作数字(例如排序或数学),则此解决方案有效。如果您需要这样做,您可以将其转回 DataFrame,转换为适当的高精度类型,然后再转换回您的 Dataset。
val highPrecisionDf = spark.createDataFrame(List(Seq(BigDecimal("12345678901122334455667788990011122233"))).map(a => Row.fromSeq(a)).asJava, schema)
case class StringDecimal(dec: String)
highPrecisionDf.as[StringDecimal]
【讨论】:
以上是关于如何将具有 Decimal 的 spark DataFrame 转换为具有相同精度的 BigDecimal 的 Dataset?的主要内容,如果未能解决你的问题,请参考以下文章
将 hive 表卸载到。使用 Spark 或 pyspark 或 python 的 dat 文件
pyspark读取csv文件multiLine选项不适用于具有换行符spark2.3和spark2.2的记录