Spark:转换小数而不更改列的可空属性

时间:2018-06-14 10:02:29

标签: apache-spark apache-spark-sql

将列投射到DecimalType中的DataFrame似乎会更改可空属性。具体来说,我有一个类型为DecimalType(12, 4)的非可空列,我使用DecimalType(38, 9)将其投放到df.withColumn(columnName, df.col(columnName).cast(dataType))。这会产生具有预期数据类型的字段,但该字段现在可以为空。有没有一种方法可以在不改变列的可空属性的情况下进行转换?

我在Spark 2.2.1和Spark 2.3.0中都观察到了这种行为。

2 个答案:

答案 0 :(得分:2)

感谢有趣的一点。我挖了一点源代码来理解这种行为,IMO答案是在Cast.scala中代表演员表达式。暴露可空性的属性计算如下:

override def nullable: Boolean = Cast.forceNullable(child.dataType, dataType) || child.nullable

  def forceNullable(from: DataType, to: DataType): Boolean = (from, to) match {
  case (NullType, _) => true
  case (_, _) if from == to => false

  case (StringType, BinaryType) => false
  case (StringType, _) => true
  case (_, StringType) => false

  case (FloatType | DoubleType, TimestampType) => true
  case (TimestampType, DateType) => false
  case (_, DateType) => true
  case (DateType, TimestampType) => false
  case (DateType, _) => true
  case (_, CalendarIntervalType) => true

  case (_, _: DecimalType) => true  // overflow
  case (_: FractionalType, _: IntegralType) => true  // NaN, infinity
  case _ => false
}

如您所见,从任何类型到DecimalType的转换始终返回可空类型。我想知道为什么,这可能是因为这里表达的溢出风险:

/**
 * Change the precision / scale in a given decimal to those set in `decimalType` (i  f any),
 * returning null if it overflows or modifying `value` in-place and returning it if successful.
 *
 * NOTE: this modifies `value` in-place, so don't call it on external data.
 */
private[this] def changePrecision(value: Decimal, decimalType: DecimalType): Decimal = {
  if (value.changePrecision(decimalType.precision,   decimalType.scale)) value else null
}

changePrecision方法检查是否可以修改精度,如果是则返回true,否则返回false。它解释了为什么上面的方法可以返回null,因此为什么在源类型独立转换时,DecimalType默认设置为可为空。

由于IMO,没有简单的方法来保持原始列的可空性。也许你可以试着看一下UserDefinedTypes并构建你自己的,source-properties-keeping,DecimalType?但是,IMO的可空性并非没有原因,我们应该尊重这一点,以避免在管道中或之后出现一些不良意外。

答案 1 :(得分:0)

df.withColumn(columnName, df.col(columnName).cast(dataType)) 可以改写为:

import org.apache.spark.sql.catalyst.expressions.objects.AssertNotNull

df.withColumn(columnName, new Column(AssertNotNull(df.col(columnName).cast(dataType).expr)))

注意:如果结果强制转换导致空值,这将抛出 NullPointerException。

来源:https://dev.to/kevinwallimann/how-to-make-a-column-non-nullable-in-spark-structured-streaming-4b62