Cuando trabajamos con Dataset<Row> en Apache Spark, un desafío común es el formato inconsistente de fechas. Algunas columnas pueden traer la fecha como "1987-12-05", otras como "2007-06-15 00:00:00 +0100 +01:00", y algunas incluso como texto plano. Este problema puede causar errores de parseo y valores null inesperados.
En este artículo te mostraré cómo resolver esto de forma robusta y escalable con Java, renombrando columnas, manejando múltiples formatos y evitando valores nulos.
🎯 Objetivo
Queremos transformar varias columnas de un Dataset<Row> que contienen fechas en diferentes formatos, para:
-
Renombrar las columnas con nombres estándar.
-
Convertir el contenido a tipo Date si es necesario.
-
Agregar una columna formateada en estilo dd/MM/yy para persistencia o exportación.
📦 ¿Por qué puede fallar el parseo?
Spark utiliza to_date() con un patrón de formato específico. Si le pasas un valor que no se ajusta exactamente, te devolverá null.
Por ejemplo:
Solución: usar substring() y validar el tipo de dato antes de convertir.
🛠️ Código completo
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.types.StructType;
import static org.apache.spark.sql.functions.*;
import java.util.*;
public Dataset<Row> transformarFechas(Dataset<Row> dataset) {
Map<String, String> columnas = new LinkedHashMap<>();
columnas.put("fecha_nac", "fechaNacimiento");
columnas.put("fecha_ing", "fechaIngreso");
columnas.put("fecha_egre", "fechaEgreso");
columnas.put("fecha_doc", "fechaDocumento");
columnas.put("fecha_val", "fechaValidacion");
columnas.put("fecha_act", "fechaActualizacion");
columnas.put("fecha_baja", "fechaBaja");
columnas.put("fecha_crea", "fechaCreacion");
Dataset<Row> df = dataset;
StructType schema = df.schema();
for (Map.Entry<String, String> entry : columnas.entrySet()) {
String original = entry.getKey();
String nuevo = entry.getValue();
if (Arrays.asList(schema.fieldNames()).contains(original)) {
String tipo = schema.apply(original).dataType().typeName();
// Renombrar la columna
df = df.withColumnRenamed(original, nuevo);
// Si ya es tipo Date o Timestamp, solo dar formato
if (tipo.equals("date") || tipo.equals("timestamp")) {
df = df.withColumn(
nuevo + "Texto",
date_format(col(nuevo), "dd/MM/yy")
);
} else {
// Si es texto u otro tipo, recortar y convertir
df = df
.withColumn(
nuevo,
to_date(substring(col(nuevo).cast("string"), 0, 10), "yyyy-MM-dd")
)
.withColumn(
nuevo + "Texto",
date_format(col(nuevo), "dd/MM/yy")
);
}
}
}
return df;
}
public Dataset<Row> transformarFechas(Dataset<Row> dataset) {
Map<String, String> columnas = new LinkedHashMap<>();
columnas.put("fecha_nac", "fechaNacimiento");
columnas.put("fecha_ing", "fechaIngreso");
columnas.put("fecha_egre", "fechaEgreso");
columnas.put("fecha_doc", "fechaDocumento");
columnas.put("fecha_val", "fechaValidacion");
columnas.put("fecha_act", "fechaActualizacion");
columnas.put("fecha_baja", "fechaBaja");
columnas.put("fecha_crea", "fechaCreacion");
Dataset<Row> df = dataset;
StructType schema = df.schema();
for (Map.Entry<String, String> entry : columnas.entrySet()) {
String original = entry.getKey();
String nuevo = entry.getValue();
if (Arrays.asList(schema.fieldNames()).contains(original)) {
String tipo = schema.apply(original).dataType().typeName();
if (tipo.equals("date") || tipo.equals("timestamp")) {
// Si ya es tipo Date o Timestamp
df = df.withColumn(nuevo, col(original))
.withColumn(nuevo + "Texto",
date_format(col(original), "dd/MM/yy"));
} else {
// Para otros tipos (string, etc.)
df = df.withColumn(nuevo,
coalesce(
to_date(col(original).cast("string"), "yyyy-MM-dd"),
to_date(col(original).cast("string"), "dd/MM/yyyy"),
to_date(col(original).cast("string"), "MM/dd/yyyy")
))
.withColumn(nuevo + "Texto",
when(col(nuevo).isNotNull(),
date_format(col(nuevo), "dd/MM/yy"))
.otherwise(lit(null)));
}
// Eliminar la columna original si es diferente del nuevo nombre
if (!original.equals(nuevo)) {
df = df.drop(original);
}
}
}
return df;
}
public Dataset<Row> transformarFechas(Dataset<Row> dataset) {
// Mapa de columnas (igual que antes)
Dataset<Row> df = dataset;
for (Map.Entry<String, String> entry : columnas.entrySet()) {
String original = entry.getKey();
String nuevo = entry.getValue();
if (Arrays.asList(df.columns()).contains(original)) {
// Intentar con formato yyyy-MM-dd primero
Column fechaConvertida = to_date(col(original).cast("string"), "yyyy-MM-dd");
// Si falla, intentar con dd/MM/yyyy
fechaConvertida = when(fechaConvertida.isNull(),
to_date(col(original).cast("string"), "dd/MM/yyyy"))
.otherwise(fechaConvertida);
// Si aún falla, intentar con MM/dd/yyyy
fechaConvertida = when(fechaConvertida.isNull(),
to_date(col(original).cast("string"), "MM/dd/yyyy"))
.otherwise(fechaConvertida);
// Aplicar la conversión
df = df.withColumn(nuevo, fechaConvertida)
.withColumn(nuevo + "Texto", date_format(col(nuevo), "dd/MM/yy"))
.drop(original);
}
}
return df;
}
Comentarios
Publicar un comentario