ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Kettle实战:Excel成绩数据清洗与排名计算全流程解析

2026/10/7 2:13:33 拓冰建站 浏览量
Kettle实战:Excel成绩数据清洗与排名计算全流程解析 前阵子接了一个教务数据整理的需求一堆学生成绩Excel表几十个班、七八个科目表格格式还彼此不一样有的叫“姓名”有的叫“学生姓名”有的成绩列带“分”字后缀有的里面混着缺考、缓考、空值。光靠手搓Excel函数和维护VLOOKUP一两个班还能对付到了全年级一起统计基本就是加班重灾区。我的方案是用Kettle做数据导入把散落各处的成绩统一清洗入库然后在库里用窗口函数做排名计算。整套流程跑下来一个“版本混乱”的Excel文件夹变成了按班级、科目稳定排名的数据表整个过程可复用、可调度、出问题可追溯。这篇就完整记录这个实战从Kettle的环境准备、数据清洗、Excel导入到排名计算的两种实现方式再到日常最容易踩的坑全部摊开讲。1. 场景与需求拆解为什么偏偏选Kettle1.1 这个需求真正要解决的问题学生成绩数据导入表面上是“把Excel塞进数据库”但实际做过的都知道真正的难点从来不是“塞”这个动作而是Excel里的脏数据。比如学号列被Excel自动变成了科学计数法一导入全是“4.02E09”明明每个班都有一份成绩表但列名不一样、科目顺序不一样还有同一个人重复出现在多次补考记录里怎么去重以哪条为准。所谓“排名”也分很多种。是全班排名还是年级排名是单科排名还是总分排名并列名次要不要占用后续名次这些规则如果不提前确认后面所有SQL都要返工。所以我把这个需求拆成四块多源Excel结构化读取支持不同文件格式、不同Sheet、动态识别列名。数据清洗与标准化包括字段名统一、类型转换、去重、空值处理、缺失值标记。数据落库提供稳定的目标表结构和批量写入能力。排名计算按不同维度生成排名结果并且要求规则可配置。这四块如果用Python脚本也能做但如果交给运维或者教学秘书去维护Python门槛有点高。Kettle的价值就在于图形化数据流一眼能看到头看到“哪一步挂了”“哪一行没过去”不用靠想象排查问题。1.2 为什么不用手写脚本或者纯SQL我确实考虑过纯SQL方案让Excel先转成CSV再用LOAD DATA INFILE导入。问题在于Excel文件的复杂性。有的Sheet里前三行是标题和考试说明表头其实在第四行有的单元格里既有缺考又带补考成绩需要根据状态位拆出两个字段。这些在SQL里处理非常痛苦但在Kettle里就是拖几个组件的事。Kettle还有两个实际好处。一是日志体系完整每个步骤都记了输入、输出、跳过、错误数量做数据核查时可以直接看统计数字。二是调度方便配合作业Job可以做定时增量同步比如把这次导入的配置存成模板下个月考试成绩直接跑同一套作业。我选Kettle没有选商业ETL工具主要是预算和部署方式的原因。Pentaho Data Integration社区版足够稳定安装简单一台普通服务器或者本地电脑就能跑Windows和Linux都能部署适合中小型项目快速落地。1.3 Kettle在这个场景里的角色用一句话概括Kettle是管道数据库是水槽排名规则是水阀。成绩数据从Excel出发经过Kettle的清洗和转换被送进数据库的原始成绩表和标准成绩表最后通过SQL窗口函数算出排名。Kettle在这个链路里做的是脏活累活但它把这些脏活变成了可配置、可复用、可视化的流程。这也是我认为最值得写进项目的部分不是Kettle本身多高深而是用它组织数据流的思路。2. 环境准备部署方式与基础配置详解2.1 下载安装与版本选择我建议不要追最新版。Kettle的版本节奏是跟着Pentaho走的目前社区版已经到9.x和10.x但版本越新对JDK版本要求越高反而在旧服务器上容易出问题。我做生产环境一般首选8.3或者9.x稳定版跑4.1.x的JDK内存配置既不激进也够用。下载时直接找Pentaho Data Integration的社区版压缩包下载后有个data-integration目录。Windows下双击Spoon.bat启动Linux下用./spoon.sh启动。如果服务器没有图形界面可以通过Kettle的Carte服务实现远程调度但这篇先不扩展到那么复杂本地Spoon开发、Job定时调度就够了。启动之前有个关键设置内存。默认Spoon脚本给的内存比较保守跑小数据量没问题一旦Excel有几万行而且还有去重、排序、排名这类内存密集型操作很容易直接GC overhead limit exceeded。我一般会修改spoon.bat或spoon.sh里的PENTAHO_DI_JAVA_OPTIONSPENTAHO_DI_JAVA_OPTIONS-Xms1024m -Xmx4096m -XX:MaxPermSize512m如果是64位系统可以再往上调。跑几十万行数据时我给到8G都碰到过关键还是看你的源文件大小和转换复杂度。2.2 数据库驱动准备Kettle本身不内置所有数据库驱动需要自己放jar包。我的目标库是MySQL所以把mysql-connector-java的jar放到lib目录重启Spoon就能识别。需要注意版本新版MySQL 8.x需要用mysql-connector-java 8.x老驱动连接时会报Public Key Retrieval is not allowed这种错。连接串我习惯显式写上参数避免时区、编码问题jdbc:mysql://localhost:3306/school?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue如果用的是PostgreSQL驱动也是同理丢到lib目录连接串写清楚。Oracle会麻烦一点但成绩导入场景一般用不到。2.3 启动后第一时间要改的配置新建转换之后先设置转换的“参数”不要把所有路径写死。项目里的Excel文件目录、输出表名、考试批次ID都应该做成变量。比如我用了一个自定义参数exam_batch每次跑不同考试的导入时只需要在运行配置里改一个值不需要动整个流程。Kettle里做参数化可以直接用${PARAM_NAME}引用。如果记不住参数名写Help说明或者干脆维护一个专门的参数配置文件用“表输入”或者“获取变量”步骤读进来。这个习惯在项目维护期特别受益尤其是管理员有时候自己跑作业改参数字段比改整个转换容易得多。3. 数据建模与流程设计先想清楚再拖组件3.1 源文件结构识别动手之前我建议先做一次源数据探查把每个Excel的Sheet名、表头行位置、列顺序、有没有合并单元格、有没有总分和平均分公式列全部记录下来。常见的Excel成绩表有两种样式宽表样式一行一个学生科目作为多列语文、数学、英语、物理……。长表样式一行是“学生科目分数”分数一列科目一列。排名如果是单科排名长表做起来最直接如果是总分排名宽表聚合最方便。Kettle不要求源数据必须转成某一种但目标表通常建议先转成标准长表再通过行列转换行转列组件输出宽表这样未来扩展性最好。我这次接收的原始Excel就是典型的宽表但有部分班级的Sheet里带“总分”列和“年级排名”列完全是手工算的维护成本特别高。我们的目标就是把这些手工计算列全部去掉入库后用SQL统一算。3.2 目标表结构设计根据导入和排名需求我设计了四张表表名用途关键字段temp_score_import暂存导入原始数据student_no, student_name, class_name, subject, scorescore_export导出给前端查看的成绩student_no, student_name, class_name, score_avg, total_scorescore_rank排名结果class_no, subject, student_no, score, rank_resultdict_exam_info考试批次字典exam_id, exam_name, grade_name其中最核心的是temp_score_import它是一张标准长表。所有Excel数据经过清洗后先落到这里再做后续处理。做这个设计的原因是清洗后的标准数据是后续排名的唯一数据源如果排名规则调整我们只需要重算排名表不需要重新导Excel。建表DDL里需要特别注意的是student_no字段不要用整型必须用VARCHAR否则学号前导零、科学计数法问题都处理不好。3.3 清洗规则和排名规则要提前对齐清洗规则列出来至少包括学号统一转字符串去掉回车换行和首尾空格。姓名字段做trim空值按无效记录处理。成绩字段转成DECIMAL处理“-”“缺考”“请假/缓考”这类非数字内容。同一学生同一科目只保留一条有效记录重复记录按最新考试时间或最后一次补考成绩为准。排名规则要和业务方确认清楚排名范围是全班还是全年级总分是否加权比如物理、化学按比例折算缺考学生要不要出现在排名结果里并列名次是否跳过比如两个第二名之后是第四名还是第三名这几点不确认到位后面一定返工。排名SQL本身不复杂复杂的是“排名口径”。4. 实战成绩导入与排名核心步骤实现4.1 Excel输入跳过表头、多Sheet与动态列处理Kettle里读取Excel用的是“Excel输入”步骤但新版Kettle更推荐“Microsoft Excel输入”老版本叫“Excel输入”。如果你下载的是9.x直接搜Excel输入就行支持xls和xlsx。关键配置有三处“工作表”页签选择要读取的Sheet。如果每个班一个Sheet可以配置多个Sheet并勾选“将Sheet名作为字段输出”这样能在数据流里拿到Sheet名用它反推班级。“内容”页签起始行、头部行。起始行是数据实际开始的那一行不是表头。如果前三行是标题第四行才是表头起始行要配成4。“字段”页签Kettle一般会自动扫描字段但建议手动检查字段名尤其是Excel列名里的空格和括号自动扫描经常带出怪异字符。遇到多个班的Excel文件格式不完全一致不建议硬读一个模板。最稳妥的做法是分班读取每个班配置一个Excel输入步骤再用“追加流”步骤合并。缺点是组件会多一些但每个分流的错误定位非常方便。如果Excel里出现了合并单元格比如“班级”列每行都合并了几个单元格Kettle读出来往往是第一行有值、后续行为空。处理方式是在后续用填充空值组件按班级分组向下填充。4.2 字段清洗类型、空值、非数字内容处理Excel输入读出来的数据99%都是字符串成绩99分也被当成文本。所以在字段选择之后紧跟着要加计算器或字段类型转换。成绩列建议做三个转换清洗空格在“字符串操作”步骤里选择清除空格类型“清除两侧空格”。类型转换把成绩文本转成Number类型。非法值兜底用“过滤记录”把转换失败的成绩行捞出来标记原因后进入错误日志表而不是直接中断整个转换。这里有个经验不要用“强制转换失败即停止”的模式。成绩表里出现缺考标记非常常见一行失败就停掉整批数据都要重来。应该让脏行进入单独的输出流最后统一汇总“成功多少条失败多少条失败原因是什么”。比如缺考、缓考这类字符串我通常用一个“值映射”步骤转换缺考 → NULL缓考 → NULL其余数字原样保留。值映射不认识的字符串就进入未知输出再通过另一个过滤记录判断是否保留。4.3 去重的细节不能只靠排序成绩数据最头疼的就是重复。同一个学生在一个Sheet里可能出现两次比如一次正常考试一次补考也可能因为Excel误操作复制了行。直接在去重组件里按学号科目分数去重有可能把一条缺考0分和一条补考85分当成不同记录都保留下来这就不对了。正确思路是先给数据流增加一个“考试时间”字段去重时按学号科目分组组内按考试时间取最新一条。Kettle里实现这个逻辑有两种方式先按学号、科目、考试时间排序再用“去除重复记录”步骤会保留每组排序后的第一条。这个方法直观但必须排序而排序在大数据量下容易吃内存。如果数据已经入库直接用SQL去重。写一条窗口函数插入目标表性能反而更好。我最终采用的方式是Excel先简单清洗入库再在MySQL里用窗口函数做去重把干净数据写入最终标准表。这样Kettle流程短数据可靠性高。4.4 表输出批量写入与主键策略表输出组件有两点要注意不要开“自动创建表结构”让Kettle猜类型很不靠谱尤其是DECIMAL精度和VARCHAR长度。我的做法是在数据库里手动建好表Kettle只负责批量插入。批量提交大小可以调默认1000但MySQL下可以提到5000或者10000。前提是目标表不要有太多索引否则写入会很慢。建议先写入无索引的临时表处理完再加索引。如果数据量大不建议走“表输出”逐行插入可以用“批量加载”步骤比如MySQL的“批量加载”直接走LOAD DATA方式速度能快一个数量级。但批量加载对字段类型匹配要求更严格出错后定位不如表输出直观。4.5 排名实现方式一SQL窗口函数优先成绩数据落地之后排名就不该在Kettle里做了除非你有强烈的“不想打开数据库客户端”的洁癖。我会在数据库端写视图或者存储过程SELECT student_no, student_name, class_name, subject, score, RANK() OVER(PARTITION BY class_name, subject ORDER BY score DESC) AS rank_in_class, ROW_NUMBER() OVER(PARTITION BY grade_name, subject ORDER BY score DESC) AS seq FROM temp_score_import WHERE score IS NOT NULL;窗口函数有个优势一个SQL同时算全班排名和年级排名不需要写多个子查询。RANK和DENSE_RANK的区别需要按业务要求选择RANK两个并列第二下一个就是第四。DENSE_RANK两个并列第二下一个还是第三。ROW_NUMBER绝不并列按物理顺序打序号。成绩排名通常用RANK更符合直觉因为并列的学生拿到的名次相同后续没有人数变化。但如果你要算“年级前100名人数”就要注意RANK和ROW_NUMBER的口径差异别把并列名次重复统计了。4.6 排名实现方式二纯Kettle实现排名有些场景下你不能连外部队列数据库或者数据库版本太老不支持窗口函数那就要在Kettle内部算排名。方法也不复杂核心是“先排序再逐行累计名次”。流程是按排名维度班级科目和成绩DESC排序。新增一个计数器每处理一行加1。比较当前行的“维度成绩”和上一行是否一致如果一致名次沿用上一行的值如果不一致名次等于当前计数器的值。Kettle实现可以用“排序记录”步骤加“用户自定义Java代码”步骤也可以在“计算器”里做条件判断但代码步骤最直接。需要注意排序记录步骤一定要选对升序降序并且排序字段顺序必须和排名维度一致否则后面比较逻辑全错。这个方案的性能瓶颈在排序因为要把所有数据放进内存。几万行以内没问题几十万行就会吃力。所以我的建议是数据量小、环境简单时用Kettle内排数据量大、规则复杂的场景把数据交给SQL。4.7 作业调度参数化考试批次导入和排名都不是一次性任务期中一次、期末一次可能还有月考和补考。为了复用我建了一个作业Job里面放三个转换清除上一次临时表。从Excel导入原始成绩。执行排名计算的SQL脚本。作业里的所有路径都不写死。Excel文件夹通过变量传入比如${input_dir}考试批次通过${exam_id}传入。每次跑新考试只需要修改作业参数或者把参数写在调用Job的命令行里./kitchen.sh -file/opt/kettle/jobs/score_import.kjb \ -param:input_dir/data/exam/202406 \ -param:exam_id202406用命令行方式跑就能接进系统的定时任务比如每天凌晨检查固定目录有没有新Excel有就自动导入。这种模式不仅适合成绩也适合所有周期性文件导入比如考勤、消费流水、设备巡检数据。5. 实战中踩过的坑与排查思路5.1 Excel里名字带空格导致的假重复第一个坑是源Excel里“张三”和“张三 ”结尾多了一个空格看起来是同一个人但按姓名分组时被当成两个人。解决方案是在清洗阶段统一对所有中文字段做trim并且把全角空格替换成半角再trim。只在最终排名阶段做的话重复数据已经进了临时表还得回炉。5.2 “0分”和“缺考0分”不能混淆很多考试会把缺考成绩记成0但如果只是缺考没有成绩就不应该参与排名。直接在SQL里WHERE score 0会把正常0分的考生一起过滤掉虽然0分很罕见但逻辑上就不准确。正确做法是在Excel清洗阶段就把缺考标记剥离出来存成一个状态字段最终排名时只算状态为“正常”的记录。5.3 表输出速度慢的真正原因第一次跑一个几百人的年级成绩表表输出居然跑了好几分钟。排查发现目标表建了主键索引和两个联合索引而Kettle默认是单条SQL循环插入。后来先把索引全部删掉插入完成后再重建索引几百人的数据几十秒就完成了。如果数据量在几十万行这个差距会极其明显。5.4 Spoon闪退或者报内存错误Kettle卡死或者闪退大多不是软件Bug而是内存配置不够。除了在启动脚本里调大Xmx还要注意不要在一个转换里塞几十个文件流能分批处理的就分批。Excel输入一次读取整个文件文件再大一点内存就不够用了。可以限制每个Sheet读取多少行或者用“Split Fields”按批次处理。5.5 不同数据库驱动导致的日期格式错误成绩表如果有“考试日期”字段在不同数据库里的日期处理逻辑不同。MySQL认yyyy-MM-ddOracle认dd-MM月-yy而Excel里的日期可能是个序列号。Kettle读取Excel日期时建议在“Excel输入”里直接转成字符串进库后再统一CAST不要在Kettle里做时间格式自动识别。自动识别经常出现8小时时差或者格式错乱排查起来很烦。6. 经验总结与扩展方向做完这个项目我个人的体会是Kettle作为ETL工具最舒服的点不是它的转换组件有多丰富而是它逼着你在动手之前想清楚“数据长什么样结果要怎么用”。Excel导入排名的流程看着简单但里面有大量的决策点去重规则怎么定、并列名次怎么处理、空值怎么标记、文件路径怎么传。如果你之后要做更复杂的版本比如多学校联考排名、按班级均分算达标率、历史成绩趋势对比只要把标准成绩表建好后面所有分析都可以靠在表上扩展不用回改Kettle流程。这也是我认为“数据导入”不应该只做到“能读到库”的原因它应该把数据整理成可被分析的状态排名只是第一层应用。最后分享一个实用建议无论项目多小都建一个import_log表每次Kettle跑完把这次导入的文件名、总行数、成功数、失败数、运行时间写进去。日后一旦有人问你“这批成绩是哪来的”你翻日志表就能直接说清楚。数据导入最怕的不是慢而是出错了说不清日志表就是救命的那个记录。