尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

VBA实战指南:玩转Excel教务挂科统计与自动化报表生成

VBA实战指南:玩转Excel教务挂科统计与自动化报表生成 1. 项目背景为什么教务数据要用VBA来处理高校教务管理里挂科数据统计是非常高频的一项工作。学期末考试结束各科成绩出来教务员或者辅导员需要回答一系列问题每个班挂了几科哪个科目挂科率最高哪些学生挂科超过三门需要预警重修名单怎么生成这些问题看起来简单但当学生数量上千、科目几十门、数据分散在多个Excel文件里的时候手工处理就变得极其痛苦。我第一次接到这个需求是在某高校期末阶段。对方拿来的原始数据是一张全校成绩总表大概四千多行每行是一个学生的某一门课程成绩字段包括学号、姓名、班级、课程名称、学分、成绩。需求也很直接挂科名单、挂科率统计、分数段分布、以及预警名单挂科超过两门的要单独列出来。说实话这些需求用Excel自带的数据透视表和筛选功能也能做一部分但涉及到多条判定规则、跨科目统计、批量格式化和生成多个报表还是VBA更合适。写这篇博文就是把这套处理挂科数据的完整思路和代码实战过程拆开来讲。从数据清洗、挂科判定、按科目统计、按学生聚合、生成预警名单到最后的报表自动排版每一步都给出可以直接复用的方案。适合辅导员、教务员、教学秘书以及任何处理过这类考核数据的办公人员参考。如果你只会Excel的基础操作这篇文章会告诉你VBA处理批量数据的基本套路如果你已经有点VBA基础也可以从中找到一些边界情况处理和代码组织的方法。2. 项目整体设计与思路拆解2.1 先搞清楚数据长什么样拿到数据的第一步不是写代码而是观察数据。我用的是某高校模拟项目X的成绩总表字段和真实教务系统导出的格式基本一致。以下是原始数据的一个简化样例学号姓名班级课程名称学分成绩20230101张某计科2301高等数学55820230101张某计科2301大学英语37120230102李某计科2301高等数学56220230102李某计科2301线性代数34320230102李某计科2301大学英语355仔细观察有几个关键点决定了后续代码怎么写一行为一个学生的一门课成绩所以一个学生占用多行数据。按学生聚合统计挂科门数就不能用简单的筛选而是需要按学号分组。成绩字段的格式可能不统一有的单元格是文本类型有的看起来是数字但其实是缺考或缓考字样。这种脏数据必须先清洗。挂科的判定线和课程学分有关不同学校可能有所不同。有的学校60分及格有的课程按五级制优、良、中、及格、不及格这些都要在写代码前问清楚。2.2 方案选型为什么不用公式和透视表在处理这类数据时很多人第一反应是用COUNTIFS加IF嵌套不就行了。确实如果只需要统计个挂科人数公式完全可以胜任。但这套需求里有一个场景是公式不太好处理的判断每个学生挂科是否超过两门并把超过两门的学生自动归入预警名单。这样做是可行的但操作相对繁琐需要在辅助列里拉公式再对结果做二次筛选。根本的问题在于公式方案是静态的。数据源一旦更新比如补考成绩录入、缓考学生决定参加考试所有统计结果都得跟着改辅助列里的公式范围需要重新调整生成的报表还得手工重建。VBA方案是动态的原始数据放在Sheet1运行一次宏自动在Sheet2生成挂科名单Sheet3生成科目挂科率统计Sheet4生成预警名单。下次数据更新了再运行一次就行所有流程完全自动化。还有一个原因是逻辑可维护性。把代码读一遍就知道成绩小于60且不为空算挂科缺考标记为-1也按挂科处理挂科超过2门预警这些规则写在哪里。对后接手的人来说理解一套VBA代码并修改规则比在一大堆嵌套公式里翻找要容易得多。2.3 整体处理流程梳理整个方案分成五个阶段数据清洗、基本校验、挂科判定与统计、学生维度聚合、报表生成。每个阶段相对独立便于单独测试和调试。第一阶段规范成绩字段——把文本数字转成数值、识别特殊标记、剔除无效行。第二阶段检查有没有缺失关键字段的行比如学号为空的学生记录直接跳过。第三阶段遍历所有成绩记录按课程名称统计考试人数、挂科人数、挂科率同时输出每条挂科记录到名单表。第四阶段再遍历一次或者复用前面收集的数据按学号汇总每个学生的挂科门数生成预警名单。第五阶段用VBA控制格式设置边框、列宽、单元格颜色把挂科率高和预警级别高的信息突出显示。3. 核心逻辑与关键代码实现3.1 字典对象跨行聚合的利器做学生维度的挂科统计时最常用的技巧是使用VBA的Dictionary字典对象。它类似于哈希表可以按学号存储一个学生的累计挂科门数。遍历成绩记录时每遇到一条挂科记录就在字典里把对应学号的计数加1。最后把需要预警的学生计数值大于2输出到名单表。Dim dict As Object Set dict CreateObject(Scripting.Dictionary) 遍历成绩数据统计每个学生挂科数量 For i 2 To lastRow scoreVal Trim(CStr(ws.Cells(i, 6).Value)) 成绩列 stuID Trim(CStr(ws.Cells(i, 1).Value)) 学号列 判断是否挂科成绩为数字且小于60或包含缺考等标记 If IsNumeric(scoreVal) Then If CDbl(scoreVal) 60 Then If dict.Exists(stuID) Then dict(stuID) dict(stuID) 1 Else dict(stuID) 1 End If End If ElseIf InStr(scoreVal, 缺考) 0 Or InStr(scoreVal, 缓考) 0 Then If dict.Exists(stuID) Then dict(stuID) dict(stuID) 1 Else dict(stuID) 1 End If End If Next i这段代码并不复杂却解决了最关键的按学生聚合问题。成绩总表里同一个学生出现多行如果不想办法聚合就没法判断挂科超过两门。字典在这种场景下的处理思路值得反复参考。建议在处理学生数据时先想清楚需要按哪个维度聚合这个维度是否适合作为字典的Key。3.2 成绩数据的边界情况处理成绩数据远没有想象的那么干净这是我在实际处理中遇到最多坑的地方。真实数据里常见的情况包括成绩单元格显示为缺考或缓考。这类情况通常要按挂科处理因为学生没有成绩。成绩为空的单元格。不能简单判断为挂科需要先和对方确认空值含义。在我的模拟项目X数据里空值表示该学生本学期没有选修这门课属于无效记录应当跳过。成绩是文本格式的数字比如单元格左上角有个绿色小三角。直接比较会导致类型不匹配报错需要先用IsNumeric判断。成绩包含具体分值但带备注比如55作弊。这种数据格式不统一要么在数据源头规范要么在代码里做截取处理。我的建议是在VBA代码里先做一个数据清洗模块统一处理这些情况。这样主逻辑里的成绩判断就不用到处处理异常代码也更清晰。写一个独立函数来判断这条记录是否为挂科把所有边界情况封装进去Function IsFail(scoreCell As Range) As Boolean Dim raw As String raw Trim(CStr(scoreCell.Value)) 空值不算挂科视为无效记录 If raw Then IsFail False Exit Function End If 缺考或缓考视为挂科 If InStr(raw, 缺考) 0 Or InStr(raw, 缓考) 0 Then IsFail True Exit Function End If 纯数字小于60算挂科 If IsNumeric(raw) Then IsFail (CDbl(raw) 60) Exit Function End If 其他文本标记按非挂科处理 IsFail False End Function注意挂科判定线60分是我在模拟项目X中使用的规则。实际部署时这个临界值应该定义成常量或者放在配置单元格里方便不同院系按各自规则调整。3.3 按科目的挂科率统计科目维度的统计相对直接遍历所有成绩记录按课程名称分组记录两个数字——考试人数和挂科人数。考试人数指该课程有效成绩记录数排除空值挂科人数是其中判定为挂科的记录数。挂科率就是两者之比。这个统计逻辑用字典也能实现但字典存的值是数组会有些别扭。更稳妥的方式是直接在内存里维护两个字典一个存考试人数一个存挂科人数。遍历结束后再合并输出。或者用更自然的方式——先动态收集所有课程名称到字典A中第二次遍历时按课程统计。我在实践里更喜欢用Collection或字典来维护课程列表因为课程名称不一定在原始数据里有现成的枚举清单。你无法提前知道全校开了多少门课动态收集才是正解。以下是用字典统计科目挂科率的核心逻辑Dim examCount As Object, failCount As Object Set examCount CreateObject(Scripting.Dictionary) Set failCount CreateObject(Scripting.Dictionary) For i 2 To lastRow courseName Trim(CStr(ws.Cells(i, 4).Value)) If courseName Then GoTo NextRow 有效成绩记录计入考试人数 If examCount.Exists(courseName) Then examCount(courseName) examCount(courseName) 1 Else examCount(courseName) 1 End If 挂科记录计入挂科人数 If IsFail(ws.Cells(i, 6)) Then If failCount.Exists(courseName) Then failCount(courseName) failCount(courseName) 1 Else failCount(courseName) 1 End If End If NextRow: Next i统计完成后遍历examCount字典把课程名称、考试人数、挂科人数、挂科率逐行写入输出表。挂科率保留一位小数或百分比形式同时用条件格式把挂科率超过20%的科目标红方便快速定位问题课程。3.4 多字段核心判断组合挂科数据处理中有一个容易忽略的点同一个学生某一门课挂科了但在另一门课里是缓考缓考虽然也算挂科但定义略有不同。比如学校可能有规则缓考学生可以直接参加补考不占用挂科名额也不会进入学业预警名单。这种细微差别落到代码里就是一个布尔变量的调整。我的经验是把是否挂科和是否计入预警拆成两个独立的判断函数。挂科名单里包含缺考和缓考但预警名单只考虑成绩60的真实不及格。这样代码虽然多了一个函数但语义清晰也方便应对不同院系的不同规则。Function IsWarningFail(scoreCell As Range) As Boolean Dim raw As String raw Trim(CStr(scoreCell.Value)) 只有纯数字且小于60才计入预警 If IsNumeric(raw) Then IsWarningFail (CDbl(raw) 60) Else IsWarningFail False End If End Function这种把规则拆解到函数级别的写法是我在多次处理绩效数据的过程中逐渐养成的习惯。一次性把所有逻辑写在一个事件处理程序里看起来省事但后期要调整一个规则时改动影响范围会比较大容易顾此失彼。4. 完整实操流程与报表生成4.1 环境准备与数据约定实际操作前先约定输入输出格式。我用一个独立的工作簿来处理这类数据文件里通常包含四个工作表原始数据、挂科名单、科目统计、预警名单。输入数据固定在原始数据表中运行宏后会清空其他三个表并重新填充内容。在写代码前先确认几个约定成绩在原始数据表的第6列F列表头行是第1行。学号在第1列姓名第2列班级第3列课程名称第4列学分第5列。所有列的数据都不允许包含合并单元格如果原始导出文件里有合并单元格先用Excel取消合并并填充。提示在模拟项目X的实际操作中出现过原始数据用Excel公式生成导致部分单元格值为0但公式未计算的情况。建议拿到数据后先做一次粘贴为值处理再进入VBA流程。4.2 主流程代码结构整个宏的主体结构如下包含了数据清洗、校验、统计、输出四个环节并结合输入标题VBA实战案例处理挂科数据的全部需求点。该主过程采用模块化的设计方便按需裁剪或调试。Sub ProcessFailData() Dim wsData As Worksheet, wsFail As Worksheet, wsStat As Worksheet, wsWarn As Worksheet Set wsData ThisWorkbook.Sheets(原始数据) Set wsFail ThisWorkbook.Sheets(挂科名单) Set wsStat ThisWorkbook.Sheets(科目统计) Set wsWarn ThisWorkbook.Sheets(预警名单) Dim lastRow As Long lastRow wsData.Cells(wsData.Rows.Count, 1).End(xlUp).Row If lastRow 2 Then MsgBox 原始数据为空请检查数据表。 Exit Sub End If 清空输出表旧数据保留表头 ClearOutputSheets wsFail, wsStat, wsWarn 第一阶段遍历并过滤写入挂科名单 Dim i As Long Dim failRow As Long, statRow As Long, warnRow As Long failRow 2 statRow 2 warnRow 2 Dim dictStu As Object, dictExam As Object, dictFail As Object Set dictStu CreateObject(Scripting.Dictionary) Set dictExam CreateObject(Scripting.Dictionary) Set dictFail CreateObject(Scripting.Dictionary) For i 2 To lastRow 跳过无效记录学号为空或姓名为空 Dim stuID As String, stuName As String stuID Trim(CStr(wsData.Cells(i, 1).Value)) stuName Trim(CStr(wsData.Cells(i, 2).Value)) If stuID Or stuName Then GoTo ContinueLoop Dim courseName As String, scoreCell As Range courseName Trim(CStr(wsData.Cells(i, 4).Value)) Set scoreCell wsData.Cells(i, 6) 科目考试人数统计 If examCountExist(dictExam, courseName) Then dictExam(courseName) dictExam(courseName) 1 Else dictExam(courseName) 1 End If 判断挂科写入挂科名单表 If IsFail(scoreCell) Then 写入挂科名单 wsFail.Cells(failRow, 1).Value stuID wsFail.Cells(failRow, 2).Value stuName wsFail.Cells(failRow, 3).Value wsData.Cells(i, 3).Value wsFail.Cells(failRow, 4).Value courseName wsFail.Cells(failRow, 5).Value wsData.Cells(i, 5).Value wsFail.Cells(failRow, 6).Value scoreCell.Value failRow failRow 1 科目挂科人数统计 If failCountExist(dictFail, courseName) Then dictFail(courseName) dictFail(courseName) 1 Else dictFail(courseName) 1 End If End If 按学生聚合预警计数仅真实不及格 If IsWarningFail(scoreCell) Then If dictStu.Exists(stuID) Then dictStu(stuID) dictStu(stuID) 1 Else dictStu(stuID) 1 End If End If ContinueLoop: Next i 输出科目统计 Dim key As Variant For Each key In dictExam.Keys wsStat.Cells(statRow, 1).Value key wsStat.Cells(statRow, 2).Value dictExam(key) If dictFail.Exists(key) Then wsStat.Cells(statRow, 3).Value dictFail(key) wsStat.Cells(statRow, 4).Value Format(dictFail(key) / dictExam(key), 0.0%) Else wsStat.Cells(statRow, 3).Value 0 wsStat.Cells(statRow, 4).Value 0.0% End If statRow statRow 1 Next key 输出预警名单 Dim stuIDKey As Variant For Each stuIDKey In dictStu.Keys If dictStu(stuIDKey) 2 Then wsWarn.Cells(warnRow, 1).Value stuIDKey wsWarn.Cells(warnRow, 2).Value GetStudentName(stuIDKey, wsData, lastRow) wsWarn.Cells(warnRow, 3).Value dictStu(stuIDKey) Dim stuClass As String stuClass GetStudentClass(stuIDKey, wsData, lastRow) wsWarn.Cells(warnRow, 4).Value stuClass warnRow warnRow 1 End If Next stuIDKey 格式化输出表格 FormatOutputSheets wsFail, wsStat, wsWarn MsgBox 处理完成挂科名单 (failRow - 2) 条科目 (statRow - 2) 门预警 (warnRow - 2) 人。 End Sub写这段代码时有一个反复权衡的点预警名单里需要补充学生的班级信息。若在遍历成绩记录时把学号、姓名、班级对应关系存入字典内存开销会明显增加但访问速度更快。我在模拟项目X里选择了单独写一个GetStudentName和GetStudentClass函数每次按需在原始数据表里查找。学生人数在两千以内时速度尚可如果数据量超过一万行建议换成字典缓存方式。4.3 辅助函数实现上面主流程中用到了一些辅助函数这里一并列出来。重点看一下从字典中判断键是否存在的写法因为这几段代码在不同版本的Office里兼容性有细微差别Function examCountExist(dict As Object, key As String) As Boolean On Error Resume Next examCountExist Not IsError(dict(key)) Err.Clear End Function Function failCountExist(dict As Object, key As String) As Boolean On Error Resume Next failCountExist Not IsError(dict(key)) Err.Clear End Function Function GetStudentName(stuID As String, wsData As Worksheet, lastRow As Long) As String Dim i As Long For i 2 To lastRow If Trim(CStr(wsData.Cells(i, 1).Value)) stuID Then GetStudentName wsData.Cells(i, 2).Value Exit Function End If Next i End Function Function GetStudentClass(stuID As String, wsData As Worksheet, lastRow As Long) As String Dim i As Long For i 2 To lastRow If Trim(CStr(wsData.Cells(i, 1).Value)) stuID Then GetStudentClass wsData.Cells(i, 3).Value Exit Function End If Next i End Function补充说明判断字典中是否存在某个键还有一种更标准的写法是使用字典的Exists方法。上面之所以用错误捕获方式是因为在部分老版本兼容场景下直接调用Exists对中文键的支持有一些量化差异。但如果你用的Office版本较新2016及以上直接用dict.Exists(key)更稳妥代码也更短。实际部署时建议优先用Exists方法If Not dictExam.Exists(courseName) Then dictExam.Add courseName, 1 Else dictExam(courseName) dictExam(courseName) 1 End If4.4 报表格式化技巧数据统计完成后报表的呈现直接影响使用者阅读效率。以下这段是格式化输出的实现重点在于用单元格边框和颜色把信息分层Sub FormatOutputSheets(wsFail As Worksheet, wsStat As Worksheet, wsWarn As Worksheet) 设置统一表头样式 Dim headerRng As Range Set headerRng wsFail.Range(A1:F1) headerRng.Font.Bold True headerRng.Interior.Color RGB(0, 102, 204) headerRng.Font.Color RGB(255, 255, 255) 挂科名单边框 Dim lastFailRow As Long lastFailRow wsFail.Cells(wsFail.Rows.Count, 1).End(xlUp).Row If lastFailRow 2 Then wsFail.Range(A2:F lastFailRow).Borders.LineStyle xlContinuous wsFail.Range(A2:F lastFailRow).Borders.Weight xlThin End If 科目统计表挂科率超过20%的标记为红色 Dim lastStatRow As Long lastStatRow wsStat.Cells(wsStat.Rows.Count, 1).End(xlUp).Row For i 2 To lastStatRow Dim rateVal As Double rateVal 0 If IsNumeric(wsStat.Cells(i, 4).Value) Then rateVal CDbl(wsStat.Cells(i, 4).Value) End If If rateVal 0.2 Then wsStat.Cells(i, 4).Font.Color RGB(192, 0, 0) wsStat.Cells(i, 4).Font.Bold True End If Next i 自动调整列宽 wsFail.Columns(A:F).AutoFit wsStat.Columns(A:D).AutoFit wsWarn.Columns(A:D).AutoFit End Sub格式化时有一个经常被忽略的细节AutoFit对中文表头有时不够友好特别是当表头文字较长时自动列宽可能让单元格显示不全。遇到这种情况建议单独给表头列设置一个最小宽度值。wsFail.Columns(A).ColumnWidth 12 wsFail.Columns(B).ColumnWidth 10 wsFail.Columns(C).ColumnWidth 144.5 一键运行的入口设计为了让不熟悉VBA的教务老师也能方便使用我在工作簿中增加了一个刷新报表按钮并指定宏为ProcessFailData。这样使用者只需要在原始数据表粘贴新数据点击按钮所有结果就自动更新。按钮的添加方式很简单开发工具选项卡中插入表单控件按钮右键指定宏。如果所在的Excel版本不显示开发工具选项卡需要在文件-选项-自定义功能区里勾选开发工具。这一步骤对于没有VBA经验的人来说也可能卡住所以我在交接说明里特意写清楚了路径。5. 实操过程中遇到的坑与排查技巧5.1 成绩列存在隐藏空格导致比较失败实际处理中碰到过一个很隐晦的问题成绩列的值在表面上是58但实际上是58 数字后面带空格或者是全角数字。这些字符在视觉上和半角数字几乎一致但CStr转换后参与判断就会出问题。排查这类问题的最快方式是在VBA代码里临时输出每个成绩单元格的ASCII码。我当时用了一个调试小工具逐格检查才发现空格的问题。解决方案是统一用Trim加一个自定义的清理函数把全角字符转成半角Function CleanStr(text As Variant) As String Dim result As String result Trim(CStr(text)) 全角数字转半角 Dim i As Integer For i 1 To Len(result) Dim ch As String ch Mid(result, i, 1) If Asc(ch) 65281 And Asc(ch) 65374 Then ch Chr(Asc(ch) - 65248) End If result Left(result, i - 1) ch Right(result, Len(result) - i) Next i CleanStr result End Function全角转半角这段逻辑虽然简单但它解决了一类非常典型的数据导入问题。教务系统从网页端导出的数据经常混入全角字符不处理的话后续的学号匹配、姓名匹配都可能静默失败。5.2 同一课程名称表述不一致导致统计被拆开教务数据里同一门课在不同老师那里可能表述不同。比如高等数学在某些记录里写成高数大学英语有时写成英语。这种情况下科目维度的统计会被错误地拆分成两行挂科率算出来也会偏小。解决思路有两个方向一是在数据源头统一课程名称找教务系统重新导出或者在Excel里做一次查找替换二是在VBA代码里维护一个课程名称映射表。映射表更适合场景固定、课程数量有限的情况。我当时做了一个Dictionary映射把常见别名归一化到规范名称Dim courseMap As Object Set courseMap CreateObject(Scripting.Dictionary) courseMap.Add 高数, 高等数学 courseMap.Add 英语, 大学英语 courseMap.Add 线代, 线性代数在遍历数据时每读到一个课程名称先查映射表若有别名就替换成规范名再参与统计。这样做还有个额外好处输出报表里的课程名称是统一的不会让看报表的人产生困惑。5.3 数据量增大时性能变慢模拟项目X的初期版本四千多行数据跑一次宏大约需要十几秒主要耗在输出环节和GetStudentName/GetStudentClass的反复全表查找上。如果每个预警学生都要遍历一次全表性能开销确实不小。优化思路有两个预警名单输出前已经知道哪些学号需要查询姓名和班级可以边遍历边收集学生信息而不是最后再查。也就是在第一次遍历时把每个学号对应的姓名、班级存入字典后面直接取用。关闭屏幕更新和自动计算。在宏开头加上Application.ScreenUpdating False和Application.Calculation xlManual宏结束再恢复。这段小改动通常能带来三到五倍的性能提升。Sub ProcessFailData() Application.ScreenUpdating False Application.Calculation xlManual ... 原有逻辑 ... Application.Calculation xlCalculationAutomatic Application.ScreenUpdating True End Sub5.4 常见问题速查问题现象可能原因排查与解决办法运行宏时提示类型不匹配成绩列存在文本或全角字符用CleanStr统一转换数据或用IsNumeric先做类型判断科目统计中同一课程出现两行课程名称不统一建立课程别名映射表归一化名称后再统计预警名单漏人判断函数没覆盖缺考标记检查IsWarningFail逻辑确认缺考与真实不及格的区分规则输出表格没有边框/颜色格式化代码未执行检查FormatOutputSheets是否在主流程末尾被调用数据更新后旧结果被保留没有清空输出表旧数据在输出前调用ClearOutputSheets先做整表内容清除按钮点击后没有反应宏被禁用文件另存为启用宏的工作簿.xlsm并在选项里启用宏学号前导0丢失Excel自动把学号识别为数字在原始数据里把学号列设置为文本格式或从教务系统导出时选文本格式6. 这套方案还可以延伸去做什么处理挂科数据只是VBA教务自动化的一个入门场景。同一套思路——遍历、字典聚合、条件判定、格式化输出——迁移到其他场景时基本不需要大改。比如说综合测评排名。原始数据换成多个维度的评分项判定规则换成加权得分计算输出表换成排名表核心代码框架可以原样复用。再比如说选课名单核查。把挂科判定换成冲突检测判断逻辑可以检查同一个时间段内是否分配了两门课程。再比如毕业资格审核按学号聚合各学生已修学分和必修课完成情况辅助判断是否达到毕业条件。我个人实际使用这套方案时最大的体会就是永远不要在原始数据表里手工改数据。把数据清洗、统计、输出分成三个相对独立的过程每次有新的数据文件就新开一个工作簿把原始数据粘贴进去运行宏生成结果。这样原始数据永远可追溯结果也随时可以重新生成不会出现改了一个单元格结果忘记刷新统计的尴尬情况。另一个经验是交给别人使用的工具界面友好度比代码优雅度更重要。我在最终版本里特意在预警名单表顶部加了一行说明文字提示预警规则是真实挂科超过两门避免使用者和教务办对预警口径有不同的理解。代码里每一处规则都写了注释防止半年后自己回来看的时候还得重新猜逻辑。如果你刚接触VBA建议先拿这个案例的数据结构——一张成绩表、几个统计需求——练手。把代码一行行在本地跑通再试着修改挂科线、增加一个统计维度、或者把输出格式改得更符合自己的偏好。VBA的学习曲线不算平缓但这种接到一个真实需求解决一个具体问题的学习方式比单纯看语法书要有效得多。
返回列表