在用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中测试也是同样的结果。改天单独说明一下这个问题的改进措施。