一次深刻的学习之旅:Power Query中真有循环吗?从获取GUID说起

2021-12-15 17:14:47 浏览数 (1)

在用Text.NewGuid生成GUID时发现,这一列的值全都是相同的:

也就是说,“得益于”pq强大的引擎,Text.NewGuid()只运行了一次,的确非常节省算力。

我们也可以据此推测,PQ中的添加自定义列,首先会检查表达式,引擎发现每一行都是一个Text.NewGuid(),既然是一样的,因此就计算一次填充到了所有行。

如果,每一行的输出结果是不同的呢,比如每一行是GUID和姓名:

代码语言:javascript复制
= Table.AddColumn(删除的副本, "自定义", each Text.NewGuid()&[NAME])

结果也是相同:

如果我们在列上加一个判断:

代码语言:javascript复制
= Table.AddColumn(源, "自定义", 
each 
if 
  Text.Length([NAME])>2 
then 
  Text.NewGuid() 
else Text.NewGuid())

就会发现:Text.NewGuid()运行了2次:

2个字的马云是c9开头,三个字的马斯克是00开头。

那么可以说明:pq的引擎并不是对每一行单独进行整个公式的运算,而是先进行if判断,如果为真,把所有为真的行标记出来,统一进行一次Text.NewGuid()计算,剩下的行再统一进行一次Text.NewGuid()计算。

而不是像上面那样先对Text.NewGuid()进行一次运算后再判断。

总之,以上两种办法,我们是无法得到随机生成的每一行不同的GUID的。

根本原因就是在于pq的引擎太过于强大与智能,此文也有过相关说明:

Power BI中的文本大写/小写自动更改现象

只能说,节省了计算机和云计算的算力,却消耗了大量的脑力。

正文开始

想到Power Query是有循环函数的:Transform类函数。

如列表循环:List.Transform;表循环函数:Table.TransformColumns;记录循环:Record.TransformFields。

循环就是将容器中的元素一个一个的拿出来,依次进行操作,例如我们构建一个{1,2,3}的列表,现在需要将列表中的每个元素都进行*10的运算,如下图

使用列表循环函数List.Transform,该函数的意思就是将列表{1,2,3}中的每个元素依次循环出来,赋予给后面的下划线,列表有几个元素,该函数就进行几次操作。

第一次:循环出列表中的1,赋予给后面的下划线,然后*10,得到10 第二次:循环出列表中的2,赋予给后面的下划线,然后*10,得到20 第三次:循环出列表中的3,赋予给后面的下划线,然后*10,得到30 最终返回得到的新列表{10,20,30}。 https://zhuanlan.zhihu.com/p/35653117?utm_source=wechat_session&utm_medium=social&utm_oi=74192003268608

循环函数最终得到的元素个数和初始列表的个数一致。

我们可以使用这个思路进行操作:

首先将table转为list:

然后对list进行循环运算:

代码语言:javascript复制
= List.Transform(NAME,(x)=>Text.NewGuid())

结果:

wonderful!

但是,这种写法只会出现一列GUID的list,并不是姓名和GUID的组合,我们试着改写一下:

代码语言:javascript复制
= List.Transform(NAME,(x)=>x&","&Text.NewGuid())

得到:

将其转为表,并使用逗号分隔符分为两列:

结果:

大功告成!

本文结束。

关闭并应用:

学谦:

这搞了个寂寞啊?!

实在是没招了,想起来大海老师的一篇文章:

你被Power Query里的随机数给“坑”了吗?

那就不重复造轮子了,按着这个改吧。

解决方案

方法一

可以通过在随机GUID前后各添加一列索引来锁定这一列:

然后再将两列索引删除,即可获取NAME和GUID列:

方法二

修改语句,对引用的表套个Table.Buffer函数,再对结果再套一个Table.Buffer函数:

方法三

使用List.Random函数来生成随机列,可以达到生成随机数的目的:

代码语言:javascript复制
= Table.AddColumn(已添加自定义, "自定义.1", each List.Random(1))

这个的原理猜测应当是每一行都生成一个独立的list,然后展开为行的时候每一行都是独立的,因此得到的结果也是独立的。

根据这个思路,我们试着来写一下生成Text.NewGuid()的列表:

代码语言:javascript复制
= Table.AddColumn(
  删除的副本, 
  "自定义", 
  each List.Transform({1},(x)=>Text.NewGuid()))

添加一列,每一列都是列表的转换,生成列表:

理论上,像List.Random函数一样,每一个list中的获取结果应当是不一样的,展开:

理论上不应该啊!

本着不服输的精神,继续改一下公式:

代码语言:javascript复制
= Table.AddColumn(
  删除的副本, 
  "自定义", 
  each List.Transform(
        List.Random(1),
        (x)=>Number.ToText(x)&","&Text.NewGuid()
  )
)

List.Random(1)每次生成的数是不同的,然而它旁边的Text.NewGuid()却只运算一次,我也是服了,我也是醉了:

再进一步,我们一次性list生成5个随机数:

代码语言:javascript复制
= Table.AddColumn(
  删除的副本, 
  "自定义", 
  each List.Transform(
        List.Random(5),
        (x)=>Number.ToText(x)&","&Text.NewGuid()
  )
)

结果:

这就不应该了!

本文一开始我们就写过一个公式,对list进行循环运算:

代码语言:javascript复制
= List.Transform(NAME,(x)=>Text.NewGuid())

结果出现的每一行都是不同的GUID:

或者我们新建一个空查询,将生成list的那一部分单独拿出来:

代码语言:javascript复制
= List.Transform(List.Random(5),(x)=>Number.ToText(x)&","&Text.NewGuid())

结果你会发现Text.NewGuid()同样运行了5次:

这就奇了怪了!

不过,从另一个侧面也可以看出来,本来我们以为List.Transform是可以循环的,但是在某些场景中,它暴露出来并不是循环的本质。

因此,解决办法3,无效。

方法四

Levi大神的指导:

代码语言:javascript复制
= Table.AddColumn(删除的副本, "自定义", each try error Text.NewGuid())

返回的是嵌套的record,适当改进一下:

代码语言:javascript复制
= Table.AddColumn(删除的副本, "自定义", 
each (try error Text.NewGuid())[Error][Message])

就直接得到结果了:

避免lazy evaluation!

方法五

畅心大神的指导:

代码语言:javascript复制
= Table.AddColumn(删除的副本, "自定义", each {_,Text.NewGuid()}{1})

这个是逻辑上是最简洁的,通过生成每一行不同列表的方式来获取第二个值。同样也是避免lazy evaluation!

结论

在Power BI中生成UUID并不是一个很好的想法。

的确,它只是我在MySQL和Python中生成UUID时,临时突发奇想而来,看看PQ是否也能实现同样的目的,通过向几位大神请教,获得了解决办法和解决思路。

并且,pq的每一次刷新都会导致UUID改变,就像Jack S老师说的,除非用来做加密,否则真是没有什么用途。

在Power BI中处理带有UUID的表同样不是一个很好的想法。因为UUID非常长,很大程度上会明显增大模型的数据量,处理时间会拉长,影响效率。关于这一点,很早之前在用MySQL时就是一个问题,最近在Power BI中测试也是同样的结果。改天单独说明一下这个问题的改进措施。

0 人点赞