我正在尝试编写一个可以根据提供的输入字符串推断Spark DataTypes的Scala函数:
/**
* Example:
* ========
* toSparkType("string") => StringType
* toSparkType("boolean") => BooleanType
* toSparkType("date") => DateType
* etc.
*/
def toSparkType(inputType : String) : DataType = {
var dt : DataType = null
if(matchesStringRegex(inputType)) {
dt = StringType
} else if(matchesBooleanRegex(inputType)) {
dt = BooleanType
} else if(matchesDateRegex(inputType)) {
dt = DateType
} else if(...) {
...
}
dt
}
我的目标是支持可用DataTypes
的大部分(如果不是全部)。当我开始实现这个功能时,我开始思考:“ Spark / Scala可能已经有了一个helper / util方法,可以为我做这个。”毕竟,我知道我可以这样做:
var structType = new StructType()
structType.add("some_new_string_col", "string", true, Metadata.empty)
structType.add("some_new_boolean_col", "boolean", true, Metadata.empty)
structType.add("some_new_date_col", "date", true, Metadata.empty)
Scala和/或Spark会隐式地将我的"string"
参数转换为StringType
等等。所以我问:我可以用Spark或Scala做些什么来帮助我实现我的转换方法?
答案 0 :(得分:15)
Spark / Scala可能已经有了一个helper / util方法,可以为我做这个。
你是对的。 Spark已经拥有自己的架构和数据类型推断代码,用于从底层数据源(csv,json等)推断架构。所以你可以看看它实现自己的(实际的实现被标记为Spark的私有,是绑定到RDD和内部类,因此它不能直接从Spark之外的代码中使用,但应该让你知道如何去做它。)
鉴于csv是平面类型(并且json可以具有嵌套结构),csv模式推断相对更直接,并且可以帮助您完成上述尝试实现的任务。所以我将解释csv推理是如何工作的(json推理只需要考虑可能的嵌套结构,但数据类型推断非常类似)。
有了这个序幕,你要看的东西是CSVInferSchema对象。特别是,请查看infer
方法,该方法采用RDD[Array[String]]
并在整个RDD 中推断数组的每个元素的数据类型。它的作用是 - 它将每个字段标记为NullType
开始,然后当它迭代Array[String]
中的下一行值RDD
时,它会更新已经推断的{如果新DataType
更具体,则{1}}为新DataType
。这种情况正在发生here:
DataType
现在val rootTypes: Array[DataType] =
tokenRdd.aggregate(startType)(inferRowType(options), mergeRowTypes)
calls inferRowType
代表该行中的每个字段。 inferField
implementation是你可能正在寻找的东西 - 到目前为止它为特定字段推断类型,并将当前行的字段的字符串值作为参数。然后它返回现有的推断类型,或者如果推断的新类型更具体,则返回新类型。
代码的相关部分如下:
inferField
请注意,如果typeSoFar match {
case NullType => tryParseInteger(field, options)
case IntegerType => tryParseInteger(field, options)
case LongType => tryParseLong(field, options)
case _: DecimalType => tryParseDecimal(field, options)
case DoubleType => tryParseDouble(field, options)
case TimestampType => tryParseTimestamp(field, options)
case BooleanType => tryParseBoolean(field, options)
case StringType => StringType
case other: DataType =>
throw new UnsupportedOperationException(s"Unexpected data type $other")
}
为NullType,则它首先尝试将其解析为typeSoFar
,但Integer
调用是对较低类型解析的调用链。因此,如果它无法将值解析为Integer,那么它将调用tryParseInteger
,失败后将调用tryParseLong
,失败时将调用tryParseDecimal
w.o.f.w.i. tryParseDouble
w.o.f.w.i tryParseTimestamp
w.o.f.w.i.最后tryParseBoolean
。
所以你几乎可以使用类似的逻辑来实现你的用例。 (如果您不需要跨行合并,那么您只需逐字实现所有stringType
方法,只需调用tryParse*
。无需编写自己的正则表达式。)
希望这有帮助。
答案 1 :(得分:8)
是的,当然Spark有你需要的魔力。
在Spark 2.x中,它是CatalystSqlParser
对象,定义为here。
例如:
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser
CatalystSqlParser.parseDataType("string") // StringType
CatalystSqlParser.parseDataType("int") // IntegerType
等等。
但据我了解,它不是公共API的一部分,因此可能会在下一版本中发生变化而不会发出任何警告。
所以你可以将你的方法实现为:
def toSparkType(inputType: String): DataType = CatalystSqlParser.parseDataType(inputType)
答案 2 :(得分:0)
从scala来看,你似乎无法做出你想要的魔法,例如检查这个例子:
import com.scalakata._
@instrument class Playground {
val x = 5
def f[T](v: T) = v
f(x)
val y = "boolean"
f(y)
def manOf[T: Manifest](t: T): Manifest[T] = manifest[T]
println(manOf(y))
}
我在阅读I want to get the type of a variable at runtime后撰写的。
现在来自spark,由于我现在没有安装,我无法撰写示例,但没有什么明显可以使用,所以我建议你继续写{ {1}}已经开始,但请先查看Source code for pyspark.sql.types。
你看到的问题是你总是传递一个字符串。
答案 3 :(得分:0)
如果您将String Literals写为DataType名称,即“ StringType”,“ IntegerType”- 使用此功能-
def StrtoDatatype(str: String): org.apache.spark.sql.types.DataType = {
val m = ru.runtimeMirror(getClass.getClassLoader)
val module = m.staticModule(s"org.apache.spark.sql.types.$str")
m.reflectModule(module).instance.asInstanceOf[org.apache.spark.sql.types.DataType]
}
如果您的字符串文字为-字符串,整数等。
def sqlStrtoDatatype(str: String): org.apache.spark.sql.types.DataType = {
CatalystSqlParser.parseDataType(str)
}