使用spark-excel读取多行表头Excel触发scala.MatchError异常求助
解决spark-excel读取多行表头时的scala.MatchError异常
这个问题我之前也碰到过,根源在于spark-excel工具在解析多行表头生成列名时,生成的列名格式不符合Spark查询计划执行阶段的预期(从堆栈看是ExcelRelation.scala的122行模式匹配失败)。withColumnRenamed没用是因为错误发生在数据加载阶段的查询计划生成时,还没到可以重命名列的步骤,所以根本执行不到那一步。
下面给你几个可行的修复方案:
方案一:自定义表头解析,跳过工具的多行表头处理
直接绕开spark-excel的表头自动解析逻辑,自己读取表头行并合并成合法列名,再读取数据:
import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import java.util.ArrayList; import java.util.List; public class ExcelReader { public static void main(String[] args) { SparkSession spark = SparkSession.builder() .appName("MultiHeaderExcelReader") .master("local[*]") .getOrCreate(); // 第一步:读取前N行表头(这里假设是2行) Dataset<Row> headerRows = spark.read() .format("com.crealytics.spark.excel") .option("header", false) .option("inferSchema", false) .option("maxRowsInMemory", 2) // 只加载前2行表头 .load("path/to/your/excel.xlsx"); // 第二步:合并多行表头为唯一合法列名 List<String> customColumns = new ArrayList<>(); for (Row row : headerRows.collectAsList()) { for (int colIdx = 0; colIdx < row.length(); colIdx++) { String cellValue = row.getString(colIdx); if (customColumns.size() <= colIdx) { customColumns.add(cellValue); } else { // 合并已有列名和当前行的单元格内容,避免重复 String existingName = customColumns.get(colIdx); customColumns.set(colIdx, existingName + "_" + cellValue); } } } // 第三步:读取数据,跳过表头行,使用自定义列名 Dataset<Row> data = spark.read() .format("com.crealytics.spark.excel") .option("header", false) .option("inferSchema", true) .option("skipRows", 2) // 跳过已经处理的2行表头 .load("path/to/your/excel.xlsx") .toDF(customColumns.toArray(new String[0])); // 验证数据 data.show(); spark.stop(); } }
方案二:预处理Excel文件,合并多行表头为单行
用Apache POI直接修改Excel文件,把多行表头合并成单行后再用spark-excel读取,这种方法最直接,不需要修改Spark读取逻辑:
import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.FileInputStream; import java.io.FileOutputStream; public class ExcelPreprocessor { public static void main(String[] args) throws Exception { // 读取原始Excel FileInputStream fis = new FileInputStream("path/to/your/excel.xlsx"); XSSFWorkbook workbook = new XSSFWorkbook(fis); XSSFSheet sheet = workbook.getSheetAt(0); // 合并前2行表头(根据你的实际表头行数调整) int headerRows = 2; for (int colIdx = 0; colIdx < sheet.getRow(0).getLastCellNum(); colIdx++) { StringBuilder combinedHeader = new StringBuilder(); for (int rowIdx = 0; rowIdx < headerRows; rowIdx++) { if (rowIdx > 0) combinedHeader.append("_"); combinedHeader.append(sheet.getRow(rowIdx).getCell(colIdx).getStringCellValue()); } // 将合并后的表头写入第一行 sheet.getRow(0).getCell(colIdx).setCellValue(combinedHeader.toString()); } // 删除多余的表头行 for (int rowIdx = headerRows - 1; rowIdx > 0; rowIdx--) { sheet.removeRow(sheet.getRow(rowIdx)); // 注意:删除行后需要调整行索引,这里简单处理,适合表头行数少的情况 sheet.shiftRows(rowIdx + 1, sheet.getLastRowNum(), -1); } // 保存处理后的Excel FileOutputStream fos = new FileOutputStream("path/to/processed_excel.xlsx"); workbook.write(fos); // 关闭资源 workbook.close(); fis.close(); fos.close(); } }
处理完后,直接用正常的spark-excel读取逻辑即可:
Dataset<Row> data = spark.read() .format("com.crealytics.spark.excel") .option("header", true) .option("inferSchema", true) .load("path/to/processed_excel.xlsx");
方案三:修改spark-excel的列名生成逻辑(进阶)
如果你的项目允许修改依赖的spark-excel源码,可以找到ExcelRelation.scala的122行附近的代码,修改列名处理的模式匹配逻辑,兼容多行表头生成的特殊列名。比如添加对包含空格、特殊字符的列名的处理逻辑,或者强制将列名转换为Spark合法的标识符(比如替换空格为下划线,去掉特殊字符)。
不过这个方案成本较高,适合需要长期使用多行表头读取的场景。
内容的提问来源于stack exchange,提问作者Abdennacer Lachiheb
相关产品推荐
相关产品推荐

