crear esquema usando struct en Spark Scala
Soy nuevo en Scala y trato de crear un esquema personalizado a partir de una matriz de elementos para leer archivos basados en un nuevo esquema personalizado.
Estoy leyendo las matrices del archivo json y utilicé el método de explosión y creé un marco de datos para cada elemento en la matriz de columnas.
val otherPeople = sqlContext.read.option("multiline", "true").json(otherPeopleDataset)
val column_values = otherPeople.withColumn("columns", explode($"columns")).select("columns.*")
column_values.printSchema()
la salida obtenida es:
column_values: org.apache.spark.sql.DataFrame = [column_id: string, data_sensitivty: string ... 3 more fields]
root
|-- column_id: string (nullable = true)
|-- data_sensitivty: string (nullable = true)
|-- datatype: string (nullable = true)
|-- length: string (nullable = true)
|-- name: string (nullable = true)
val column_values = ddb_schema.withColumn("columns", explode($"columns")).select("columns.*")
val column_name = column_values.select("name", "datatype", "length")
column_name.show(4)
+------------------+--------+------+
| name|datatype|length|
+------------------+--------+------+
| object_number| varchar| 100|
| function_type| varchar| 100|
| hof_1| decimal| 17,3|
| hof_2| decimal| 17,2|
| region| varchar| 100|
| country| varchar| null|
+------------------+--------+------+
Ahora, para todos los valores enumerados anteriormente, estoy tratando de crear un esquema val dinámicamente usando el siguiente código
val schemaColumns = column_name.collect()
val schema = schemaColumns.foldLeft(new StructType())(
(schema, columnRow) => schema.add(columnRow.getAs[String]("name"), getFieldType(columnRow.getAs[String]("datatype")), true)
)
def getFieldType(typeName: String): DataType = typeName match {
case "varchar" => StringType
// TODO include other types here
case _ => StringType
}
El problema con lo anterior es que puedo obtener los tipos de datos en la estructura, pero también me gustaría obtener (escala y precisión) solo para el tipo de datos decimal con una condición de restricción máxima permitida con una condición de que si la longitud del decimal es nula o no presente, debemos tomar el valor predeterminado como (10,0) y si el valor presente es mayor que 38, debemos tomar el valor predeterminado como (38,0)
Respuestas
Este enfoque funciona bien.
Te muestro un ejemplo completo que completa tu código y el resultado esperado.
Podría introducir más variantes en val data.
/**
* to obtain a tuple with precision and scale
* @param precision Option[String]
* @return (Int, Int)
*/
def getDecimalScale(precision: Option[String]): (Int, Int) = {
precision match {
case Some(pr) => {
pr.split(",").toList match {
case List(h, _) if h.toInt >= 38 => (38,0)
case List(h, t) => (h.toInt,t.head.toString.toInt)
case _ => (10, 0)
}
}
case None => (10, 0)
}
}
val data = List(("object_number", "varchar", "100"), ("function_type", "varchar", "100"),
("hof_1", "decimal", "17,3"), ("hof_2", "decimal", "17,2"),
("hof_3", "decimal", null),("hof_4", "decimal", "39,2"),
("region", "varchar", "100"), ("country", "varchar", null))
import spark.implicits._
val column_name = sc.parallelize(data).toDF("name","datatype","length")
column_name.show()
/*
+-------------+--------+------+
| name|datatype|length|
+-------------+--------+------+
|object_number| varchar| 100|
|function_type| varchar| 100|
| hof_1| decimal| 17,3|
| hof_2| decimal| 17,2|
| hof_3| decimal| null|
| hof_4| decimal| 39,2|
| region| varchar| 100|
| country| varchar| null|
+-------------+--------+------+
*/
val schemaColumns = column_name.collect()
schemaColumns.foreach(println)
/*
[object_number,varchar,100]
[function_type,varchar,100]
[hof_1,decimal,17,3]
[hof_2,decimal,17,2]
[hof_3,decimal,null]
[hof_4,decimal,39,2]
[region,varchar,100]
[country,varchar,null]
*/
val schema = schemaColumns.foldLeft(new StructType())(
(schema, columnRow) => {
columnRow.getAs[String]("datatype") match {
case "varchar" => schema.add(columnRow.getAs[String]("name"), StringType, true)
case "decimal" => {
val (pr, sc) = getDecimalScale(Option(columnRow.getAs[String]("length")))
schema.add(columnRow.getAs[String]("name"), new DecimalType(precision = pr, scale = sc), true)
}
case _ => schema.add(columnRow.getAs[String]("name"), StringType, true)
}
}
)
schema.printTreeString()
/*
root
|-- object_number: string (nullable = true)
|-- function_type: string (nullable = true)
|-- hof_1: decimal(17,3) (nullable = true)
|-- hof_2: decimal(17,2) (nullable = true)
|-- hof_3: decimal(10,0) (nullable = true)
|-- hof_4: decimal(38,0) (nullable = true)
|-- region: string (nullable = true)
|-- country: string (nullable = true)
*/
El tipo de datos decimal con precisión se puede crear como se especifica aquí :
DataTypes.createDecimalType()
En la función "getFieldType" se puede agregar el caso del tipo decimal, algo. me gusta:
case "decimal" => DataTypes.createDecimalType(10,0)