152-技巧-Power Query 快速合并文件夹中表格之自定义函数 TableXlsxCsv

附件下载地址:https://jiaopengzi.com/2602.html

一、背景

在我们使用 Power BI 或者 Power Pivot 做数据分析模型时,使用 Power Query 做数导入,经常会遇到如下场景:

  • 同一文件夹下多个表格的合并。
  • 同一个 Workbook 中有多个相同的字段的 WorkSheet 。
  • Xlsx, Xls, Csv 并存。
  • 字段的类型更改每次都要打开 Power Query 去修改。
  • 字段增加和删除每次都要打开 Power Query 去修改。
  • 字段重命名后要修改类型等多处操作。
  • 合并的表格表头有多行不需要的信息,影响数据的读取。
  • 有多个项目文件夹都是合并表格,要重复写 M 语句或者重复面板操作。
  • 把文件名称按照规则添加列到对应的合并的表格里。
  • Excel 文件筛选后,会出现数据变多的情况。

基于上述的场景,我们写好了一个能应对的自定义函数:TableXlsxCsv ,让我们来看一下函数的介绍。

这个函数的提示和 Power Query 内置函数的提示是一样的,基本函数的使用和说明都清晰了。

二、TableXlsxCsv 的使用方法

1、参数介绍

Ⅰ、必填参数

1参:FolderName

data 文件夹下需要合并表的文件夹名称,比如demo中的文件夹:01_订单

2参:RemoveFirstRows

数据从顶部开始需要移除的行数,一般情况需要把标题及以前的数据都要移除,在名称管理中已经管理好了对应的标题。

3参:RemoveLastRows

数据从底部开始需要移除的行数,比如底部有类似汇总行的数据。

4参:IsAddColumn

是否需要把文件名称按照规则添加列,只能填写 true 或者 false 两个参数,true 表示需要添加列,false 表示不需要添加列。

注意:当 4 参 IsAddColumn 为 false,后面四个参数不用填写,为 true 时,后面四个参数必须填写。

Ⅱ、可选参数

5参:ColunmName

当 4 参 IsAddColumn 为 true 表示需要把文件名称按照规则添加列,ColunmName 即为新增列的名称,注意不要与合并表中的字段名称重复。

6参:ColumnType

5 参 ColunmName 添加列的数据类型,只能填写如下类型:

type text

type number

Int64.Type

type date

type datetime

type time

7参:NameStartNumber

文件名称字符开始的索引,比如文件名称:订单信息-2022-05-16;索引是从 0 开始的,需要取年与日的话索引的开始就是 5 。

8参:Length

文件名称在 6 参 NameStartNumber 后的长度,比如文件名称:订单信息-2022-05-16;索引是从 0 开始的,需要取年与日的话索引的开始就是 5 ,Length 就是 2022-05-16 的长度 10 个字符。

2、必要配置

Ⅰ、项目文件夹

项目新建一个文件夹,名称随意,我们当前的案例项目文件夹:demo152

Ⅱ、数据文件夹 data

项目文件夹建立好后,需要把项目文件和数据文件区分开,我们建立 data 文件夹来管理数据,当然这个文件夹的名称也是随意,根据自己的需求来即可。

Ⅲ、数据字段的名称管理

在 数据文件夹 data 下有一个 00_辅助表 的文件夹,这个文件名称不能更改,是和函数绑定的名称。

Ⅳ、需合并表格的名称管理

在辅助表文件夹中,有一个 Excel 文件: 01_名称管理,这个文件就是把需要合并的文件夹中的表格的重命名、数据类型、是否显示等都解耦出来,在这个 Excel 文件中操作即可。

具体配置如下图:

1、文件夹:表示需要合并的文件夹,比如当前的:01_订单 文件夹。

2、ID:表示需要合并表格从左开始的字段序号,序号从1开始依次递增,新增加的文件夹需要再次从 1 开始。

3、原始名称:表示需要合并表格从左开始的字段名称。

4、统一名称:表示在原始名称的基础下重命名后的字段名称,比如当前的配置就把所有字段都重命名了。

5、字段类型ID:表示表格合并后最后统一数据类型标识,具体对应参照左边表格。

6、是否显示:表示该字段是否在最终上载到模型中,只能填写:**是 ** 或者 ,填写其它会报错。

Ⅴ、Power Query 路径配置

打开项目文件把路径参数:Path 配置好,注意这个参数就是 前面 data 文件夹的路径。参数 Path 名称不能更改,,注意大小写。

Ⅵ、配置自定义函数 TableXlsxCsv

在 Path 下面 新建一个空白查询,点击高级编辑器,把 TableXlsxCsv 的 M 代码复制到编辑框里面保存,重命名查询为 TableXlsxCsv即可。

TableXlsxCsv 的 M 代码:

  1. let
  2. fx0 = (
  3. FolderName as text,
  4. RemoveFirstRows as number,
  5. RemoveLastRows as number,
  6. IsAddColumn as logical,
  7. optional ColunmName as text,
  8. optional ColumnType as text,
  9. optional NameStartNumber as number,
  10. optional Length as number
  11. ) as table =>
  12. let
  13. Types0 = List.Buffer({type text, type number, Int64.Type, type date, type datetime, type time}),
  14. Types1 = List.Buffer({"文本", "小数", "整数", "日期", "日期时间", "时间"}),
  15. ColumnNameTable0 = Table.Buffer( Excel.Workbook(File.Contents(Path & "\00_辅助表\01_名称管理.xlsx"), true, true){[Item = "ColumnNameTable", Kind = "Table"]}[Data]),
  16. ColumnNameTable0Fileds = List.Buffer(Table.ColumnNames(ColumnNameTable0)),
  17. TypeListNewjpz = List.Buffer(List.Zip({ColumnNameTable0Fileds, List.Transform({0, 2, 0, 0, 2, 0}, each Types0{_})})),
  18. ColumnNameTable1 = Table.Buffer(Table.TransformColumnTypes(ColumnNameTable0, TypeListNewjpz)),
  19. ColumnNameTable2 = Table.Buffer(Table.SelectRows(ColumnNameTable1, each ([文件夹] = FolderName))),
  20. ColumnCount = Table.RowCount(ColumnNameTable2),
  21. ColumnNameTableX = Table.Buffer(Table.SelectRows(ColumnNameTable2, each [是否显示] = "是")),
  22. ColumnNameListOld = List.Buffer(List.Transform(ColumnNameTableX[ID], each "Column" & Text.From(_))),
  23. ColumnNameListNew = List.Buffer(List.Zip({ColumnNameListOld, ColumnNameTableX[统一名称]})),
  24. TypeListNew = List.Buffer(List.Zip({ColumnNameTableX[统一名称], List.Transform(ColumnNameTableX[字段类型ID], each Types0{_})})),
  25. FileList0 = Table.Buffer(Folder.Files(Path & "\" & FolderName)),
  26. BinaryList0 = List.Buffer(FileList0[Content]),
  27. List0 = List.Buffer({0 .. List.Count(BinaryList0) - 1}),
  28. Extension0 = List.Buffer(FileList0[Extension]),
  29. NameList0 = List.Buffer(FileList0[Name]),
  30. NameList1 = List.Transform(NameList0, each Text.Middle(_, NameStartNumber, Length)),
  31. TableList0 = List.Buffer(
  32. List.Transform(
  33. List0,
  34. (n) =>
  35. let
  36. wb = Table.SelectColumns(
  37. if Text.Contains(Extension0{n}, "Csv", Comparer.OrdinalIgnoreCase) then
  38. Table.RemoveLastN(
  39. Table.RemoveFirstN(
  40. Csv.Document(BinaryList0{n}, [Delimiter = ",", Columns = ColumnCount]),
  41. RemoveFirstRows
  42. ),
  43. RemoveLastRows
  44. )
  45. else
  46. Table.Combine(
  47. List.Transform(
  48. Table.SelectRows(
  49. Excel.Workbook(BinaryList0{n}, null, true),
  50. each not Text.Contains([Name], "Filter", Comparer.OrdinalIgnoreCase)
  51. )[Data],
  52. each Table.RemoveLastN(Table.RemoveFirstN(_, RemoveFirstRows), RemoveLastRows)
  53. )
  54. ),
  55. ColumnNameListOld
  56. )
  57. in
  58. if IsAddColumn then Table.AddColumn(wb, ColunmName, each NameList1{n}) else wb
  59. )
  60. ),
  61. Rename0 = Table.Buffer(Table.RenameColumns(Table.Combine(TableList0), ColumnNameListNew)),
  62. Result =
  63. if IsAddColumn then
  64. Table.TransformColumnTypes(
  65. Rename0,
  66. TypeListNew & {{ColunmName, Types0{List.PositionOf(Types1, ColumnType)}}}
  67. )
  68. else
  69. Table.TransformColumnTypes(Rename0, TypeListNew)
  70. in
  71. Result,
  72. TypeMeta0 = type function (
  73. FolderName as (
  74. type text
  75. meta [
  76. Documentation.FieldCaption = "1参:FolderName, Path 路径下需要合并表的文件夹名称.",
  77. Documentation.SampleValues = {"订单信息"},
  78. Documentation.AllowedValues = List.RemoveItems(
  79. List.Transform(
  80. List.Distinct(Folder.Files(Path)[Folder Path]),
  81. each Replacer.ReplaceText(Replacer.ReplaceText(_, Path, ""), "\", "")
  82. ),
  83. {"00_辅助表"}
  84. )
  85. ]
  86. ),
  87. RemoveFirstRows as (
  88. type number
  89. meta [
  90. Documentation.FieldCaption = "2参:RemoveFirstRows,从顶部开始移除的行数.",
  91. Documentation.SampleValues = {1},
  92. Documentation.AllowedValues = {0 .. 50}
  93. ]
  94. ),
  95. RemoveLastRows as (
  96. type number
  97. meta [
  98. Documentation.FieldCaption = "3参:RemoveLastRows,从底部开始移除的行数.",
  99. Documentation.SampleValues = {0},
  100. Documentation.AllowedValues = {0 .. 50}
  101. ]
  102. ),
  103. IsAddColumn as (
  104. type logical
  105. meta [
  106. Documentation.FieldCaption = "4参:IsAddColumn,是否需要把文件名称按照规则添加列.",
  107. Documentation.SampleValues = {false},
  108. Documentation.AllowedValues = {false, true}
  109. ]
  110. ),
  111. optional ColunmName as (
  112. type text
  113. meta [
  114. Documentation.FieldCaption = "5参:ColunmName,添加列的名称.",
  115. Documentation.SampleValues = {"NewName"}
  116. ]
  117. ),
  118. optional ColumnType as (
  119. type text
  120. meta [
  121. Documentation.FieldCaption = "6参:ColumnType,添加列的类型.",
  122. Documentation.SampleValues = {"文本"},
  123. Documentation.AllowedValues = {"文本", "小数", "整数", "日期", "日期时间", "时间"}
  124. ]
  125. ),
  126. optional NameStartNumber as (
  127. type number
  128. meta [
  129. Documentation.FieldCaption = "7参:NameStartNumber,文件名称字符开始的索引.",
  130. Documentation.SampleValues = {0},
  131. Documentation.AllowedValues = {1 .. 50}
  132. ]
  133. ),
  134. optional Length as (
  135. type number
  136. meta [
  137. Documentation.FieldCaption = "8参:Length,文件名称在 NameStartNumber 后的长度.",
  138. Documentation.SampleValues = {1},
  139. Documentation.AllowedValues = {1 .. 50}
  140. ]
  141. )
  142. ) as table
  143. meta [
  144. Documentation.Name = "TableXlsxCsv",
  145. Documentation.LongDescription
  146. = "返回同一文件夹下相同字段表格合并后的表格,兼容 XLSX, XLS, CSV 三种常见的格式, 同时兼容一个 WorkBook 多个相同字段的 WorkSheet 的 Excel 文件.【注意】:当参数 IsAddColumn 为 true 时,后四个参数 ColunmName, TypeNumber, NameStartNumber, Length 必填; 【00_辅助表】文件夹下的名称管理 Excel 文件需要在使用 TableXlsxCsv 提前配置好.",
  147. Documentation.WhoAskedTheRightQuestion = "www.jiaopengzi.com",
  148. Documentation.Author = "焦棚子",
  149. Documentation.Examples = {
  150. [
  151. Description = "1、从文件夹【订单信息】中合并表格,且不用文件名称添加列.",
  152. Code = "TableXlsxCsv( " & """订单信息""" & ", 1, 0, false )",
  153. Result = "当参数 IsAddColumn 为 false 时,后四个参数可以不写;从文件夹【订单信息】中每个表格从顶部移除 1 行后合并表,且不用文件名称添加列."
  154. ],
  155. [
  156. Description = "2、从文件夹【订单信息】中合并表格,使用文件名称按照规则添加列.",
  157. Code = "TableXlsxCsv( " & """订单信息""" & ", 1, 3, true," & """数据日期""" & ", "&"""日期"""&", 12, 10 )",
  158. Result
  159. = "当参数 IsAddColumn 为 true 时,后四个参数必填;从文件夹【订单信息】中每个表格从顶部移除 1 行并且从底部移除 3 行后合并表,添加列的名称:数据日期,数据类型为:日期格式,按照文件名称从 12 个字符开始取 10 个字符."
  160. ]
  161. }
  162. ],
  163. fx1 = Value.ReplaceType(fx0, TypeMeta0)
  164. in
  165. fx1

M 代码复制到高级编辑器后保存后,必要配置就算完毕了。

3、TableXlsxCsv 调用

Ⅰ、数据源

首先来看一下数据源,在函数说明中介绍到,我们的自定义函数 TableXlsxCsv ,可以兼容 Xlsx、Xls、Csv 三种常见的本地文件,我们直接通过一个函数就可以解决了,不用去考虑表格是否筛选,表头有多行,同一个 Workbook 有多个相同的 WorkSheet等问题了。

在 data 文件夹下新建文件夹来管理同一字段表格,案例中文件夹是:01_订单,在前面配置名称管理时也可以看。

细心的朋友发现了,我们这个文件夹下是有三种数据类型。

我再看下具体的每个文件下的内容:

订单信息-2022-03-15 文件类型是 Xlsx,里面有 2 个Sheet 分别对应 15 号的数据和 16 号的数据,为了方便演示 15 号的 Sheet 中有 5 行数据, 16 号的 Sheet 中有 6 行数据。

订单信息-2022-03-17 文件类型是 Xls,里面只有 1 个Sheet 为了方便演示 17 号的文件中有 7 行数据。

订单信息-2022-03-15 文件类型是 Csv,为了方便演示 18 号的文件中有 8 行数据。

Ⅱ、函数调用

A、不添加列

按照图示输入信息后点击调用函数,即可得到新的查询,更改查询名称为自己需要的名称即可。

我们看到查询语句非常简单了,而且可以复用。

  1. let
  2. = TableXlsxCsv("01_订单", 1, 0, false )
  3. in

B、添加列

按照图示输入信息后点击调用函数,即可得到新的查询,更改查询名称为自己需要的名称即可。

注意:在需要添加列的情况下,后四个参数都是必填。

M 语句如下:

  1. let
  2. = TableXlsxCsv("01_订单", 1, 0, true, "数据日期", type date, 5, 10)
  3. in

C、结果对比
  • 结合前面的数据源,可以看到 Xlsx, Xls, Csv 在同一个文件夹中是可以通过一个我们写的自定义函数合并的。虽然我们做了三个中格式兼容,但从效率来讲我们更希望看到的是 Csv 格式。导出的平面文件 PQ 读取的效率高低顺序: Csv > Xlsx > Xls ,尽量不使用Xls, Xls 这种格式读取是比较慢。
  • 看到下单日期,3 月 15 号和 3 月 16 号的数据在同一个 Workbook 的不同的 Sheet 里面,也是合并到了一起。
  • 筛选的零时表也是剔除掉的。
  • 每个字段的名称按照辅助表里名称管理的 Excel 配置的来重命名的。
  • 每个字段的格式按照辅助表里名称管理的 Excel 配置的数据类型来设置的。
  • 每个字段的显示按照辅助表里名称管理的 Excel 配置字段是否显示来显示的,如自动编号不显示,其它都显示出来了。
  • 下图第一张是没有后四个参数的,也就没有添加列,第二张图片有后四个参数,在数据后添加了一列。需要注意的我的文件名称的格式:订单信息-2022-03-15 这个数据名称也是有将就的,年是四位数占位,月是和日都必须是两位数占位,比如这里的 3 月,就是 03 ;这里的规则其实使用的函数 Text.Middle 这个规则可以自己去修改。
  • 最后的添加列,是对文件名称使用规则,而不是 Sheet 名称,比如这里的订单信息-2022-03-15 ,其中有两个 Sheet,最后添加列也是 3 月 15 号。
  • 第二张第 4 参数为 2,每个表在合并前都移除了最后两行,可以和上述数据源作对比。

三、总结

1、我们自定义的这个函数,其实就是封装了 Excel.Workbook 和 Csv.Document 两个主要的函数。

2、当然如果自己写一个这样的函数的成本是很高的,我们写好的函数可以直接拿来用即可。

3、有了这函数以后,我们在做项目的时候就提高数据导入的效率,同时一张 Excel 来管理我们的数据字段。

4、有朋友可能会说直接使用数据库不更方便嘛,使用数据库也是完全可行的,但是更多的业务场景给的就是 Excel 、Csv 等文件,使用这个 PQ 自定义函数会更方便快捷。

5、这个函数的复用还体现在,我们的项目会多张,这样就有多个文件夹,我们通过名称管理的 Excel 文件,把需要的表格名称管理起来,同时建立好对应的文件夹,这样我们就可以直接快速的合并文件夹里面的内容了。比如如这样:

6、最后需要说明的一点,使用自定义函数合并的表格一定要自己校验。

7、附件中有详细的分解步骤,有兴趣的可以研究下。

by 焦棚子

152-技巧-Power Query 快速合并文件夹中表格之自定义函数 TableXlsxCsv的更多相关文章

  1. 【转载】C#代码开发过程中如何快速比较两个文件夹中的文件的异同

    在日常的使用电脑的过程中,有时候我们需要比较两个文件夹,查找出两个文件夹中不同的文件以及文件中不同的内容信息,进行内容的校对以及合并等操作.其实使用Beyond Compare软件即可轻松比较,Bey ...

  2. PDF 补丁丁 0.4.1.804 测试版发布:合并文件夹的图片和PDF文件,自由生成多层次书签

    新的测试版增强了合并文件的功能,可以合并文件夹内的图片和PDF文件,还可以在合并文件列表上直接指定与合并文件对应的PDF书签标题.通过拖放文件项目生成多层次的PDF书签.如下图所示: 另外,新的测试版 ...

  3. 运用CMD命令关于快速获取文件夹名称和快速建立文件夹

    前些天头儿让我建立一本本的文件夹,让后交给我了几个命令,快速获取文件夹的名称和快速建立文件夹,省去了一个个的按F2,一个个的复制,粘贴,一个个的新建,再复制粘贴. 首先讲一下第一个问题,快速获取文件夹 ...

  4. [R语言]读取文件夹下所有子文件夹中的excel文件,并根据分类合并。

    解决的问题:需要读取某个大文件夹下所有子文件夹中的excel文件,并汇总,汇总文件中需要包含的2部分的信息:1.该条数据来源于哪个子文件夹:2.该条数据来源于哪个excel文件.最终,按照子文件夹单独 ...

  5. C#合并文件夹图片列表 自定义排版顺序

    本次程序编写主要为了将pdf word等文档转换为图片后设置不同的打印排版 前提 目标文件夹中的图片高宽都是一致的 /// <summary> /// 合并图片 /// </summ ...

  6. windows合并文件夹窗口

      windows合并文件夹窗口 CreateTime--2017年7月26日16:28:14Author:Marydon 右击任务栏-->属性-->任务栏按钮选项-->选择“始终合 ...

  7. python 读取文件夹中所有同类型的文件 并用pandas合并

    import globimport osimport pandas as pd read_path = 'D:/Data' # 要读取的文件夹的地址read_excel = glob.glob(os. ...

  8. Python win32com模块 合并文件夹内多个docx文件为一个docx

    Python win32com模块 合并文件夹内多个docx文件为一个docx #!/usr/bin/env python # -*- coding: utf-8 -*- from win32com. ...

  9. 【C#操作Excel】同名Excel放入同一文件夹中,然后合并为同一个Excel文件

    近期有对Excel操作的需求,由于都是重复劳动,故分享代码如下,本人也是技术菜鸟没有考虑性能,如果有大牛能够指教就再好不过了 事先电脑中需要安装Excel,然后Vs中引用Microsoft.Offic ...

随机推荐

  1. 【动态系统的建模与分析】8_频率响应_详细数学推导 G(jw)_滤波器

  2. 让弹幕给 PPD 生个孩子

    怎样才能跑起来我写的弹幕程序 资源下载 申请野狗后端云账号注册 创建应用: 复制appId到index.html的 var ref = new Wilddog("https://<ap ...

  3. 使用electron制作满屏心特效

    图片被压缩了 看起来很难看 主进程代码 import {BrowserWindow, app, ipcMain} from 'electron' createWindow(); ipcMain.on( ...

  4. python-人物风云榜(实现排名)

    Description 又到了云之国一年一度的任务风云榜更新的大日子了.给出每个人风云力数值,需要你给出每个人的排名.注意,排名存在并列的情况. Input 一共有 22 行.第一行一个整数 n ,表 ...

  5. 【Android开发】富文本

    SpannableString spannableString = new SpannableString("设置文字的前景色为淡蓝色"); ForegroundColorSpan ...

  6. HTML表格CSS美化

    效果展示 style.css html{ width: 100%; height: 100%; overflow: hidden;}body{ width: 100%; height: 100%; f ...

  7. SQLite实现用户数据存储+Android之app:lintVitalRelease解决办法

    今日所学 SQLite实现用户数据存储 遇到的问题 界面没能显示出存在数据库中的信息 明日计划 查找界面没能显示出存在数据库中的信息的原因 报错:app:lintVitalRelease 解决办法: ...

  8. 布局框架frameset

    <!DOCTYPE html>//demo.html <html> <head> <meta charset="UTF-8"> &l ...

  9. 帝国cms修改成https后后台登陆空白的解决办法

    以下方法适用帝国cms7.5版本: 7.5版本已经有了http和https自动识别,但是因为一些疑难杂症的原因,自动识别判断的不准,后台登录也是空白, 我们可以打开e/config.php查找'htt ...

  10. Unity通过脚本创建Mesh(网格)

    ##1.创建一个带Mesh的物体 Unity中的网格作为组件不能脱离物体单独存在 新建脚本CreateMesh public class CreateMesh: MonoBehaviour { voi ...