creare lo schema usando struct in spark scala
Sono nuovo in scala e sto cercando di creare uno schema personalizzato da un array di elementi per leggere i file in base a un nuovo schema personalizzato.
Sto leggendo gli array dal file json e ho usato il metodo di esplosione e ho creato un dataframe per ogni elemento nell'array di colonne.
val otherPeople = sqlContext.read.option("multiline", "true").json(otherPeopleDataset)
val column_values = otherPeople.withColumn("columns", explode($"columns")).select("columns.*")
column_values.printSchema()
l'output ottenuto è:
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|
+------------------+--------+------+
Ora per tutti i valori sopra elencati sto cercando di creare dinamicamente lo schema val utilizzando il codice sottostante
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
}
il problema con quanto sopra è che sono in grado di ottenere i tipi di dati in struct, ma vorrei anche ottenere (scala e prezzo) solo per il tipo di dati decimale con una condizione di restrizione massima consentita con una condizione che se la lunghezza per decimale è nullo o non presente dobbiamo prendere il valore predefinito come (10,0) e se il valore presente è maggiore di 38 dobbiamo prendere il valore predefinito come (38,0)
Risposte
Questo approccio funziona bene.
Ti mostro un esempio completo che completa il tuo codice e il risultato atteso.
Potresti introdurre più varianti in 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)
*/
Il tipo di dati decimale con precisione può essere creato come specificato qui :
DataTypes.createDecimalType()
Nella funzione "getFieldType" è possibile aggiungere il caso per il tipo Decimal, smth. piace:
case "decimal" => DataTypes.createDecimalType(10,0)