Power Query:当特定值出现在另一列中时如何将一个添加到列
Power Query: how to add one to a column when a specific values appear in an other column
我有一个 ID 列,我正在寻找方法来在每次特定项目出现在我的 Geography
列时增加我的 ID(ItalyZ
、ItalyM
、UKY
或 UKM
) 被发现。
ItalyZ
的ID从0开始到4000结束
ItalyB
的ID从4000开始到8000结束
UKY
的ID从0开始到4000结束
UKM
的ID从4000开始到8000结束。
但是,我正在刷新我的文件,因此我会不时收到 "geographies" 的新邮件,但没有来源或第一个 ID。这些 boundaries/ranges 只是已知的开始和结束。
这是我的数据样本:
|---------------------|------------------|
| 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)
但绝对没有任何效果。这令人抓狂。
我将回答一个进一步简化的问题,因为我不想解决 ID
字母前缀。
假设我们有以下 table(我已经包括:
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
:
然后我们添加一个新列,其中列中的每一行都是一个 table,通过将原始 table 过滤到当前组来定义然后添加一个索引列,从我们刚刚找到的最大值 ID
开始。这是最复杂的一步。
从这里开始,我们展开 Custom
table 列(选择我们还没有的列)并删除旧的 ID
列。我们现在需要缺少我们选择执行的任何排序或列键入。
编辑: 我在上面写错了。请注意,Group
A 的 NewID
是 1,2,3
而不是我尝试的 0,1,2
。
为了解决这个简单示例的问题,您可以在分组步骤中使用 List.Min
而不是 List.Max
。
对于更复杂的示例,您可能需要向源table添加一个索引列,以便您可以在扩展后合并回它并且只使用新的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
的索引源 table。然后我们添加 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]
与之前类似,我们采用组 subtable Custom
,只需选择空 ID
行并从最大非空行 ID
开始对其进行索引加一。这仍然给我们留下了 table,所以我们只需要此子 table 中与整个 table 中的 Index
相对应的行。我们使用 {0}[NewID]
从 [NewID]
列中 table 的第一(唯一)行的单元格中提取值。对于非 null ID
值,else 子句将它们保持原样。
和我的其他回答一样,这是一个简化的问题,忽略了您拥有的 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
从 table 的左边部分开始,我们要计算新列 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 = Column{0}, i = 0, n = List.Max(Column)],
each [i] < List.Count(Column),
each [
i = [i] + 1,
x = if Column{i} = null then [n] + 1 else Column{i},
n = if Column{i} = null then [n] + 1 else [n]
],
each [x]
)
in
Generate
定义函数时,它看起来像这样,可以在任何其他查询中重复使用:
您可以通过在现有 table 中选择现有列并单击“调用”按钮来使用它。
这将在您的查询窗格中创建一个名为 Invoked Function 的新列表,该函数应用于您选择的列。
您还可以创建一个空白查询并向其传递一个列表。例如,FilterNulls({4,7,null,9,null,null})
returns {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
这与其他答案的步骤和复杂性相似,但使用的函数是以您可能更熟悉的递归方式构造的。
我有一个 ID 列,我正在寻找方法来在每次特定项目出现在我的 Geography
列时增加我的 ID(ItalyZ
、ItalyM
、UKY
或 UKM
) 被发现。
ItalyZ
的ID从0开始到4000结束
ItalyB
的ID从4000开始到8000结束
UKY
的ID从0开始到4000结束
UKM
的ID从4000开始到8000结束。
但是,我正在刷新我的文件,因此我会不时收到 "geographies" 的新邮件,但没有来源或第一个 ID。这些 boundaries/ranges 只是已知的开始和结束。
这是我的数据样本:
|---------------------|------------------|
| 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)
但绝对没有任何效果。这令人抓狂。
我将回答一个进一步简化的问题,因为我不想解决 ID
字母前缀。
假设我们有以下 table(我已经包括:
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
:
然后我们添加一个新列,其中列中的每一行都是一个 table,通过将原始 table 过滤到当前组来定义然后添加一个索引列,从我们刚刚找到的最大值 ID
开始。这是最复杂的一步。
从这里开始,我们展开 Custom
table 列(选择我们还没有的列)并删除旧的 ID
列。我们现在需要缺少我们选择执行的任何排序或列键入。
编辑: 我在上面写错了。请注意,Group
A 的 NewID
是 1,2,3
而不是我尝试的 0,1,2
。
为了解决这个简单示例的问题,您可以在分组步骤中使用 List.Min
而不是 List.Max
。
对于更复杂的示例,您可能需要向源table添加一个索引列,以便您可以在扩展后合并回它并且只使用新的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
的索引源 table。然后我们添加 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]
与之前类似,我们采用组 subtable Custom
,只需选择空 ID
行并从最大非空行 ID
开始对其进行索引加一。这仍然给我们留下了 table,所以我们只需要此子 table 中与整个 table 中的 Index
相对应的行。我们使用 {0}[NewID]
从 [NewID]
列中 table 的第一(唯一)行的单元格中提取值。对于非 null ID
值,else 子句将它们保持原样。
和我的其他回答一样,这是一个简化的问题,忽略了您拥有的 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
从 table 的左边部分开始,我们要计算新列 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 = Column{0}, i = 0, n = List.Max(Column)],
each [i] < List.Count(Column),
each [
i = [i] + 1,
x = if Column{i} = null then [n] + 1 else Column{i},
n = if Column{i} = null then [n] + 1 else [n]
],
each [x]
)
in
Generate
定义函数时,它看起来像这样,可以在任何其他查询中重复使用:
您可以通过在现有 table 中选择现有列并单击“调用”按钮来使用它。
这将在您的查询窗格中创建一个名为 Invoked Function 的新列表,该函数应用于您选择的列。
您还可以创建一个空白查询并向其传递一个列表。例如,FilterNulls({4,7,null,9,null,null})
returns {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
这与其他答案的步骤和复杂性相似,但使用的函数是以您可能更熟悉的递归方式构造的。