Power Query 系列 (17) - BOM数据展开应用案例(2)

2021-03-25 09:46:16 浏览数 (1)

在本系列的第 14 篇,我介绍了类似 BOM 的数据应该怎么存储,以及在 Power Query 中如何展开。那篇文章使用创建辅助查询的方法,从而简化了在查询编辑器的操作,小白都是可以理解和掌握的。但如果我们来回顾一下处理的过程,有很多重复的操作,比如合并和展开第 2 级数据,合并和展开第 3 级数据等等。有编程概念的人立即想到,应该用循环来解决啊。在编程语言中,循环是一种基础的控制结构,是基础的东西,但在 PQ 中却没有直接的循环语句,需要通过函数的辅助来实现。上篇介绍了 List.Generate 函数之后,我们可以对 BOM 数据的展开进行优化了。

我们先来看看重复的代码:

这些代码一共重复了 3 次,如果数据的层级更多,则会有更多的重复。所以,首先考虑将这些重复的代码封装在一个函数中。在 Power Query 中,如果能借助查询编辑器操作的地方,尽量在 UI 中进行操作,没必要手写代码。为了简单,我用查询转换为函数的方式来操作:

新建一个空查询,在高级编辑器中输入下面的代码:

代码语言:txt复制
let
    Level1Table = Table.SelectRows(Hierarchy, each ([Level] = 1))
in
    Level1Table

然后将 Level1 复制为一个新的查询 Level2,将筛选条件改为 Level = 2:

代码语言:txt复制
let
    Level1Table = Table.SelectRows(Hierarchy, each ([Level] = 1))
in
    Level1Table

选中 Level1 查询,与 Level2 查询进行合并查询操作。因为本博客是一个系列,前面有专门文章讲合并操作的查询,就不每次重复贴图了。我们将重点放到 M 语言的代码中来。完成合并查询后的 M 语言代码如下:

代码语言:txt复制
let
    Level1Table = Table.SelectRows(Hierarchy, each ([Level] = 1)),
    MergedWithLevel2 = Table.NestedJoin(
        Level1Table,  // left table
        {"Child"},    // link field in left table
        Level2,       // right table
        {"Parent"},   // link field in right table 
        "Level2",     // column name
        JoinKind.LeftOuter),
    ExpandedLevel2 = Table.ExpandTableColumn(
        MergedWithLevel2, 
        "Level2",  // this column will be expanded
        {"Child"},  // old column names
        {"Child.1"}) // new column names
in
    ExpandedLevel2

经过观察,将下面几个作为参数:

代码语言:txt复制
- parentTable:父表,尽管现在连接是根据 level 就可以确定,但后面需要迭代逐步变化,所以作为参数
- linkFieldInParent: 父表的关联字段,子表的关联字段是固定的,总是 Parent
- level: 用于根据 Level 确定 childTable

根据这个思路,将代码稍作变更:

代码语言:txt复制
let
    parentTable = Table.SelectRows(Hierarchy, each [Level]=1),
    linkFieldInParent = "Child",
    level = 1,
    nextLevel = level   1,
    nextLevelText = Text.From(nextLevel),
    childTable = Table.SelectRows(Hierarchy, each ([Level] = nextLevel)),

    MergedData = Table.NestedJoin(
        parentTable,                 // left table
        {linkFieldInParent},         // link field in left table
        childTable,                  // right table
        {"Parent"},                  // link field in right table 
        "Level" & nextLevelText,     // column name
        JoinKind.LeftOuter),

    ExpandedData = Table.ExpandTableColumn(
        MergedData, 
        "Level" & nextLevelText,      // this column will be expanded
        {"Child"},                    // old column names
        {"Child" & nextLevelText})    // new column names
in
    ExpandedData

回到查询编辑器,没有错误,能够出来正确的结果,就可以将查询转换成函数了:

代码语言:txt复制
(
    parentTable as table,
    linkFieldInParent as text,
    level as number
) as table =>
    let
        nextLevel = level   1,
        nextLevelText = Text.From(nextLevel),
        ChildTable = Table.SelectRows(Hierarchy, each [Level] = nextLevel),        

        MergedData = Table.NestedJoin(
            parentTable, 
            {linkFieldInParent}, 
            ChildTable, 
            {"Parent"}, 
            "Level" & nextLevelText, 
            JoinKind.LeftOuter),

        ExpandedData = Table.ExpandTableColumn(
            MergedData, 
            "Level" & nextLevelText,  
            {"Child"}, 
            {"Child" & nextLevelText}) 
    in
        ExpandedData

将查询命名为 fnMergeChild,在查询编辑器中测试函数的功能。

这样就可以得到 Level2:

再对 Level2 调用 fnMergeChild 函数:

这样就得到了 level3:

测试没有问题,就可以创建查询来实现展开所有层级了。因为操作是类似的,这里直接给出代码:

代码语言:txt复制
let
    Source = Excel.CurrentWorkbook(){[Name="表1"]}[Content],    
    ChangedTypes = Table.TransformColumnTypes(
        Source,
        {{"Child", type text}, 
        {"Level", Int64.Type}, 
        {"Parent", type text}}),

    // max level
    maxLevel = List.Max(ChangedTypes[Level]),

    // select level 1
    Level1Table = Table.SelectRows(ChangedTypes, each ([Level] = 1)),

    // call fnMergeChild function 3 times
    TableList = List.Generate(
        () => [tbl=Level1Table, linkField="Child", level=1],
        each _[level] <= maxLevel,
        each [
            tbl = fnMergeChild([tbl], [linkField], [level]), 
            linkField = "Child" & Text.From(level), 
            level=[level] 1
        ],
        each [tbl]
    ),

    LastTable = List.Last(TableList),
    RemoveUnrelated = Table.RemoveColumns(LastTable,{"Level", "Parent"})
in
    RemoveUnrelated

示例数据

github - Hierarchy Data - 2.xlsx

0 人点赞