ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

利用 Power Query + VBA 实现 CSV 数据清洗

2020-12-26 09:29:33  阅读:269  来源: 互联网

标签:Case VBA 转换 Power rng result str Query 数据


相关背景

之前接到一个任务,要将旧系统中的业务数据导入到新的生产库(Oracle 11g)。由于大部分数据是按年份收集的CSV 文件,于是想到 PL/SQL Developer 中的文本导入器(Text Importer)。可惜历史数据质量很差,且年代越久远,数据越不规范,根本无法直接导入目标库。因此必须对原数据进行数据清洗。

这些数据质量问题主要包括:

  • 新业务字段的缺失;
  • 编码的大小写不统一;
  • 字段仅提供中文,缺乏入库必需的编码;
  • 长文本存在特殊字符(回车换行等);

由于每个 CSV 的数据量都在两万行以内,所以决定使用 Excel 中的 Power Query 完成数据清洗工作,中文转编码的工作由 VBA 完成,原因后面会说明。值得一提的是,若数据源是规范、标准的 CSV 导出文件,且数据量大,建议还是用传统方法,比如 Java。本文仅对 Power Query 的实践及遇到的问题作一个小结,以便后期查阅。

准备工作

  1. CSV 格式数据文件;
  2. ExcelOffice 365);

大致处理思路

1. 数据导入

Power Query数据导入
Power Query 导入数据预览
注意:这里务必使用 Power Query 打开,而不是直接用 Excel 打开。因为后者会自动进行一些无用的格式转换,比如将长文本编号识别为数字(第一列),从而丢失源数据的精度。

2. 主要的数据转换

2.1. 转为【文本】型:

转为文本型

图中圈中部分可以单击,以快速转为某种格式。虽然方便,但当要处理的字段列较多时,频繁单击鼠标也很低效。这里推荐使用【快速访问工具栏】:将转为“文本”型的操作直接添加到该工具栏内,再批量选中要转换的字段列,按组合键【Alt + 2】实现一键转换——

Quick Access Toolbar

注意:这里可能是【Alt+1】,也可能【Alt+3】,具体加几,取决于命令添加后的相对位置(顺序为从左往右)。

2.2. 字段值的批量提取

对于目标数据前后存在冗余符号的情况(如下图所示):

Data wrapped by quotation

Power Query 的【转换】选项卡提供了多种处理途径:既可以使用【替换值】的方式,也可以通过提取【分隔符之间的文本】。不过后者更适合数据较规范的场合,否则对于匹配失败的数据行,该列的提取内容 将变为空文本
Extract Data

2.3. 批量删除与撤销

选中若干列后,直接【Delete】删除选中列;任何情况下,想要撤回上一步操作,都可以通过【查询设置】工具栏内、【应用的步骤】列出的操作列表进行撤回,非常方便。

Batch Delete and Undo

2.4. 其他转换问题

除了上述三种常规操作,实操过程中还遇到了新的转换问题,如字典值到字典码的转换(户籍类型的文字描述转为数据库中的类型码)、文本的拆分与转换(把形如 20岁 的数据拆成 20 和 岁 ,同时把单位部分转换为字典编码,比如 001)。

虽然 Power Query 也提供了添加条件列(【添加列–常规–条件列】),但设置起来相对费劲,遇到多个类似的列也很难快速套用前面的设置,无法批量操作。

起初的处理方案是使用 Excel 函数公式(如 =LEFT([@年龄], LEN([@年龄]) - 1)),但弊端也很明显:每一列都要批量复制公式,遇到字典编码转换的问题,手写公式本就很长,效率低下且容易出错。

于是想到 VBA 自定义函数,参数只要一个单元格的引用,函数名也可以任意指定。例如户籍地址类型的转换,刚开始效果就不错:

'Convert address type text into dictionary code 
Public Function cvtType(ByVal rng As Range) As String
  Dim txt$, result$
  
  txt = Trim(rng.Text)
  Select Case txt
    Case "本县区"
      result = "01"
    Case "本市其它县区", "本市其他县区"
      result = "02"
    Case "本省其它地市", "本省其他地市"
      result = "03"
    Case "其它省", "其他省"
      result = "04"
    Case "港澳台"
      result = "05"
    Case "外籍"
      result = "06"
    Case Else
      result = ""
  End Select
  
  cvtType = result
End Function

这样,只需要使用公式 =cvtType([@地址类型]) 就能得出结果。

但使用公式会出现另一个性能问题:若遇到稍大一点的数据文件,公式批量复制、批量计算、后期批量选择性粘贴的执行速度将显著下降,严重时甚至卡死 Excel。这是频繁引用单元格区域造成的。

要想彻底解决这个性能问题,通常的做法是借助 VBA 中的数组,通过将参数批量放入 VBA 数组,实现一次读取、内存处理、一次写回,尽可能减少单元格的反复引用、计算结果的反复写入。最后,将处理逻辑写入宏过程,就能实现转换结果“一步到位”:

'Convert address type text into dictionary code:
Sub cvtAddrType()
    Dim rng As Range, col&, arr As Variant, i&, rowMax&, arr1 As Variant, str$
    
    '1. Retrieve data from Range cells
    Set rng = Range(ActiveCell.Address(0, 0)).Offset(0, -1)
    col = rng.Column
    rowMax = Cells(Rows.Count, col).End(xlUp).Row
    arr = Range(rng, Cells(rowMax, col))
    ReDim arr1(1 To rowMax - 1) As String

    '2. Core conversion process
    For i = 2 To rowMax
        Select Case Trim(arr(i - 1, 1))
            Case "本县区"
                str = "01"
            Case "本市其它县区", "本市其他县区", "本市其他区"
                str = "02"
            Case "本省其它地市", "本省其他地市", "本省其他市"
                str = "03"
            Case "其它省", "其他省"
                str = "04"
            Case "港澳台"
                str = "05"
            Case "外籍"
                str = "06"
            Case Else
                str = ""
        End Select
        arr1(i - 1) = str
    Next i

    '3. Write back results
    Set rng = Range(rng, Cells(rowMax, col))
    rng.NumberFormatLocal = "@"
    rng = Application.Transpose(arr1)
    Set rng = Nothing
End Sub

3. 收尾工作

  1. 数据清洗结束后,应另存为新的 CSV 文件,作为文本导入工具的新数据源;
  2. 为确保导入发生未知异常时,数据库能迅速快速标识已导入的数据,还可以在主键上添加统一规则的前缀,如 jan2020_uuid 表示 2020 年 1 月的历史数据,all2020_uuid 表示 2020 年全年数据;
  3. 分批次生成导入数据的 SQL 文件,方便后期查阅,并设置统一规范;
  4. 编写数据导入说明文档,提示关键操作,避免重复采坑。

4. 小结

  1. 对于结构相同的 CSV 文件,还可以利用内置的 M 公式实现数据清洗的【格式刷】式操作;
  2. Power Query 虽然强大,但也不是万能。需要从实际出发,选择最合适的解决方案;
  3. 实际操作过程中,面临新的问题要多问几个为什么,抓住问题的本质(如公式对性能影响的根本原因等),这样才能在实战中逐步提高;
  4. 良好的习惯需从平时培养,如英文注释、代码缩进、多留文档等。

标签:Case,VBA,转换,Power,rng,result,str,Query,数据
来源: https://blog.csdn.net/frgod/article/details/111715060

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有