如何将具有 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?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法将 Spark 数据帧写入 .dat 文件?

将 hive 表卸载到。使用 Spark 或 pyspark 或 python 的 dat 文件

spark遇到的decimal精度缺失的问题

pyspark读取csv文件multiLine选项不适用于具有换行符spark2.3和spark2.2的记录

如何将 pythons Decimal() 类型转换为 INT 和指数

我在 s3 中有 .dat 文件。我需要通过 spark 读取文件并做一些过滤器并再次加载到 S3