Power Query:当特定值出现在另一列中时如何将一个添加到列中

Posted

技术标签:

【中文标题】Power Query:当特定值出现在另一列中时如何将一个添加到列中【英文标题】:Power Query: how to add one to a column when a specific values appear in an other column 【发布时间】:2020-04-28 19:14:31 【问题描述】:

我有一个 ID 列,并且我正在寻找每次在我的 Geography 列(ItalyZItalyMUKYUKM)中出现特定项目时增加我的 ID 的方法。

ItalyZ的ID从0开始,到4000结束。

ItalyB的ID从4000开始,到8000结束。

UKY的ID从0开始,到4000结束。

UKM的ID从4000开始,到8000结束。

但是,我正在刷新我的文件,因此我将不时有新来的“地理”没有起源或第一个 ID。这些边界/范围仅是已知的开始和结束。

这是我的数据示例:

  |---------------------|------------------|    
  |       ID            |   Geography      |
  |---------------------|------------------|
  |    AB0000           |      ItalyZ      |
  |---------------------|------------------|
  |    AB4041           |      ItalyB      |
  |---------------------|------------------|
  |    BC0000           |      UKY         |
  |---------------------|------------------|
  |    BC4001           |      UKM         |
  |---------------------|------------------|
  |    NULL             |      ItalyZ      |
  |---------------------|------------------|
  |    NULL             |      ItalyZ      |
  |---------------------|------------------|
  |    NULL             |      UKY         |
  |---------------------|------------------|
  |    NULL             |      UKM         |
  |---------------------|------------------|  

这是我的预期输出:

  |---------------------|------------------|    
  |       ID            |   Geography      |
  |---------------------|------------------|
  |    AB0000           |      ItalyZ      |
  |---------------------|------------------|
  |    AB4041           |      ItalyB      |
  |---------------------|------------------|
  |    BC0000           |      UKY         |
  |---------------------|------------------|
  |    BC4001           |      UKM         |
  |---------------------|------------------|
  |    AB0001           |      ItalyZ      |
  |---------------------|------------------|
  |    AB0001           |      ItalyZ      |
  |---------------------|------------------|
  |    AB4042           |      UKY         |
  |---------------------|------------------|
  |    BC0001           |      UKM         |
  |---------------------|------------------|  

我一直在尝试许多不同的方法并尝试调整运行的整体解决方案。我也一直在尝试将我的文件分成四个不同的文件,以免在不同情况下交替使用 If 函数,从而使其更简单,就像我的电源查询中这样:

 #"Added Custom2" = Table.AddColumn(#"Reordered Columns", "Sum", each if [Geography] = "UKM" then [Number AB range below 4000] + 1 
else if [Geography] = "UKY" then [Number AB range above 4000] + 1 
else if [Geography] = "ItalyB" then [Number BC range above 5000]
else [Number BC range below 5000] + 1)

但绝对没有任何效果。这令人抓狂。

【问题讨论】:

不断地发布然后一遍又一遍地删除同一个问题的变体不是一件好事 对不起。我意识到它的配方很糟糕。之后我会更新并返回原版。但我确实试图最终完全和绝对地理解功率查询中的条件计数/总和。我希望能够完全理解它。有没有办法重新更新线程中的问题?例如,如果策略是制定并详尽地查看某个问题的所有潜在解决方案? 【参考方案1】:

我将回答一个进一步简化的问题,因为我不想解决 ID 字母前缀。

假设我们有下表(我已包括:

ID,  Group
-----------
0,     A
1,     A
300,   B
525,   C
null,  A
null,  B
null,  B
null,  C

并且想要生成一个新列 NewID 将替换 ID

ID,  Group, NewID
------------------
0,     A,   0
1,     A,   1
300,   B,   300
525,   C,   525
null,  A,   2
null,  B,   301
null,  B,   302
null,  C,   526

这是一个使用Table.AddIndexColumn的方法:

let
    Source = <First Table Above>,
    #"Grouped Rows" = Table.Group(Source, "Group", "ID", each List.Max([ID]), type number),
    #"Added Custom" = Table.AddColumn(#"Grouped Rows", "Custom", (C) => Table.AddIndexColumn(Table.SelectRows(Source, each _[Group] = C[Group]),"NewID",C[ID],1)),
    #"Expanded Custom" = Table.ExpandTableColumn(#"Added Custom", "Custom", "NewID", "NewID"),
    #"Removed Columns" = Table.RemoveColumns(#"Expanded Custom","ID")
in
    #"Removed Columns"

首先,我们按Group 分组以找到每个Group 的最大ID

然后我们添加一个新列,其中列中的每一行都是一个,通过将原始表过滤到仅当前组然后添加一个从最大值ID 开始的索引列来定义我们刚刚发现。这是最复杂的一步。

从这里,我们扩展Custom 表列(选择我们没有的列)并删除旧的ID 列。我们现在需要减少我们选择执行的任何排序或列输入。


编辑:我在上面犯了一个错误。请注意,Group A 的 NewID1,2,3 而不是我尝试的 0,1,2

要解决这个简单示例的问题,您可以在分组步骤中使用List.Min 而不是List.Max

对于更复杂的示例,您可能需要向源表添加一个索引列,以便在展开后合并回它,并且仅将新的NewID 用于以前为空的ID 值,因为我们不能保证它们是连续的。

代码如下:

let
    Source = <First Table Above>,
    #"Added Index" = Table.AddIndexColumn(Source, "Index", 0, 1),
    #"Grouped Rows" = Table.Group(#"Added Index", "Group", "ID", each List.Max([ID]), type number),
    #"Added Custom" = Table.AddColumn(#"Grouped Rows", "Custom", (C) => Table.AddIndexColumn(Table.SelectRows(Table.Sort(#"Added Index","ID"), each _[Group] = C[Group]),"NewID",C[ID]+1,1)),
    #"Expanded Custom" = Table.ExpandTableColumn(#"Added Custom", "Custom", "Index", "NewID", "Index", "NewID"),
    #"Merged Queries" = Table.NestedJoin(#"Added Index", "Index", #"Expanded Custom", "Index", "Expanded Custom", JoinKind.LeftOuter),
    #"Expanded Expanded Custom" = Table.ExpandTableColumn(#"Merged Queries", "Expanded Custom", "NewID", "NewID"),
    #"Added Custom1" = Table.AddColumn(#"Expanded Expanded Custom", "ReplaceID", each if [ID] = null then [NewID] else [ID]),
    #"Removed Columns" = Table.RemoveColumns(#"Added Custom1","ID", "NewID")
in
    #"Removed Columns"

复杂的步骤变化了一点:

(C) => Table.AddIndexColumn(
           Table.SelectRows(
               Table.Sort(#"Added Index", "ID"),
               each _[Group] = C[Group]
           ),
           "NewID", C[ID] + 1, 1
       )

不同之处在于我们需要添加一个排序,以便空值出现在所有已分配的 ID 值之后,并开始在 C[ID] + 1 处索引空值,而不仅仅是 C[ID]


这是一个步骤更少(没有分组、展开或合并)但功能更复杂的版本:

let
    Source = <First Table Above>,    
    #"Added Index" = Table.AddIndexColumn(Source, "Index", 0, 1),
    #"Added Custom" = Table.AddColumn(#"Added Index", "Custom", (C) => Table.SelectRows(#"Added Index", each _[Group] = C[Group])),
    #"Added NewID" = Table.AddColumn(#"Added Custom", "NewID", (C) => if C[ID] = null then Table.SelectRows(Table.AddIndexColumn(Table.SelectRows(C[Custom], each _[ID] = null), "NewID", List.Max(C[Custom][ID])+1,1), each _[Index] = C[Index])0[NewID] else C[ID]),
    #"Removed Columns" = Table.RemoveColumns(#"Added NewID","Custom")
in
    #"Removed Columns"

第一个添加的Custom 列只是过滤到当前Group 的索引源表。然后我们添加NewID 列定义为:

(从内到外阅读。)

(C) =>
  if C[ID] = null
  then Table.SelectRows(
           Table.AddIndexColumn(
               Table.SelectRows(C[Custom], each _[ID] = null),
               "NewID", List.Max(C[Custom][ID]) + 1, 1
           ),
           each _[Index] = C[Index]
       )0[NewID]
  else C[ID]

与之前类似,我们采用组子表Custom,只需选择空ID 行并从最大非空ID 加一开始索引它们。这仍然给我们留下了一个表,所以我们只想要这个子表中与整个表中的Index 对应的行。我们使用0[NewID][NewID] 列中表格的第一(唯一)行的单元格中提取值。对于非空的ID 值,else 子句将保持原样。

【讨论】:

您好,非常感谢您。我有点沮丧,因为这对我来说很复杂。我因此问:这真的很复杂吗?我试图理解它。 (1)首先添加一个索引,然后用最大值对行进行分组(从而生成一个具有最大值的新表),(2)然后展开列并(3)将其与第一个表合并,只有匹配值(左外)? (4)最后你添加一个在特定条件下替换“ID”的列(为什么?)。我很抱歉我的问题... (1) ✓ (2) ✓ (3) 这是一对一的比赛。内部连接也可以。 (4) 如果现有的ID 值不连续,我们只替换原始表中的空ID 值。如果它们是连续的,或者您不需要保留原始 ID,那么您可以使用第一种更简单的方法(使用 min 而不是 max)。 您可能更喜欢我刚刚编辑的步骤更少的替代公式。 再次感谢您。但是,我仍然有点难以理解这些步骤,最重要的是为什么这样做比在 excel 中要痛苦得多。我试图阅读资源并理解原因......我确信有充分的理由......但我正在努力。我知道 M 的这种设置可以提高安全性和质量……我还读到这会导致 PBI 的性能变慢,对于更大的数据集……这对我来说太可怕了。我希望能真正理解这一点。【参考方案2】:

与我的其他答案一样,这是一个简化的问题,忽略了您拥有的 ID 字母前缀。

ID,  Group | NewID
-----------|------
4,     A   | 4
7,     A   | 7
300,   B   | 300
525,   C   | 525
null,  A   | 10
9,     A   | 9
null,  A   | 11
null,  B   | 301
null,  C   | 526
null,  A   | 12
null,  B   | 302

从表格左侧开始,我们要计算新列NewID

在这个答案中,我将编写一个使用List.Generate 函数递归编写的自定义函数。

从链接的文档中,功能是这样设置的

List.Generate(
    initial as function,                    /*Set all your initial variables*/
    condition as function,                  /*Stopping criteria.*/
    next as function,                       /*Define how to update at each step.*/
    optional selector as nullable function  /*Pick output element.*/
) as list

定义一个函数,该函数接受一个可能包含空值的列,并从最大非空值开始逐步填充空值:

(Column as list) as list =>
let
    Generate =
    List.Generate(
        () => [x = Column0, i = 0, n = List.Max(Column)],
        each [i] < List.Count(Column),
        each [
            i = [i] + 1,
            x = if Columni = null then [n] + 1 else Columni,
            n = if Columni = null then [n] + 1 else [n]
            ],     
        each [x]
    )
in
    Generate

当你定义函数时,它看起来像这样,并且可以在任何其他查询中重复使用:

您可以通过选择现有表中的现有列并单击“调用”按钮来使用它。

这将在您的查询窗格中创建一个名为 Invoked Function 的新列表,该列表是应用于您选择的列的函数。

您还可以创建一个空白查询并将其传递给一个列表。例如,FilterNulls(4,7,null,9,null,null) 返回4,7,10,9,11,12

这是查询编辑器中的样子。


我们真正想做的是把这个函数作为一个group by操作中的列变换然后展开:

let
    Source = <Data Table Source Here>,
    #"Grouped Rows" = Table.Group(Source, "Group", "FillNulls", each FillNulls([ID]), type list),
    #"Expanded FillNulls" = Table.ExpandListColumn(#"Grouped Rows", "FillNulls")
in
    #"Expanded FillNulls"

这是分组后展开前的样子:

注意函数在做什么。我们在ID 列上为每个单独的Group 应用函数FillNulls


这与其他答案的步骤和复杂性相似,但使用的函数以您可能更熟悉的递归方式构造。

【讨论】:

再次感谢您。然而,我还无法理解。我也没有更简单的选项,步骤更少(没有分组、展开或合并)。 再次感谢您。遗憾的是,我仍然没有得到它......我了解函数构造,但是当我尝试测试、调整和玩耍时,我得到“fx”,它出现在 Power BI 中,要求我输入特定参数...... 没错。正如我指定的那样,您在实际查询中使用该函数。好的部分是它可以用于多个查询。您可能需要将该查询更改为使用 fx 而不是 FillNulls 如果这是您命名函数的名称。 是的,类似于 Excel 用户定义的函数。它期望的参数是一个列表。如果您如上所述定义函数fx,则可以通过编写例如在新查询中调用它。 fx(1,null,2)fx(ExistingTableName[IndexColumn]) 列出一个列表,例如一个表列,在“洞”中“调用”该列表中的函数。 这个和另一个答案中的简单版本应该是相似的。另一个答案中更复杂的一个(没有分组、扩展或合并)对于大型表可能效率低下,因为它为每一行计算一个组子表,而不仅仅是为每个不同的组。

以上是关于Power Query:当特定值出现在另一列中时如何将一个添加到列中的主要内容,如果未能解决你的问题,请参考以下文章

Power Query 根据另一列转换一列

在 SQL 中,我可以在另一列中获取与它们没有关联的特定值的列中的值吗?

从表中选择行,其中具有相同 id 的另一个表中的行在另一列中具有特定值

循环遍历过滤的单元格列表以检查值是不是出现在另一列中,然后复制/粘贴

当分配规则存储在另一个tibble中时,如何用新值替换tibble中的数据?

Power Query 参数在一个表中起作用,但在另一个表中不起作用