数据三大容器:table record list

基本概念:

table

即表,这个好理解,有行有列即为table。
在excel中,要指定一个单元格我们用比如A5,而在PQ中则略有不同。每一个table都有字段名,即使你没有给字段命名,也会有默认的如"Column1""Column2"这样的命名;也有索引号,即使你没有添加索引列,也可以根据索引号找到对应的唯一行。比如源[产品]{4}指定在源这张表的"产品"列的第5行的唯一位置,注意索引是从0开始。
大多数情况下我们的table都是从外部导入,而如果我们需要在PQ内部构建一张表,格式为=#table({"产品","数量"},{{"a",10},{"b",20}}),注意{}的数量和位置。

第一个{}内为字段名,我们有两个字段分别命名为"产品"和"数量"。第二个{}内为每一行,共有两行所以中间用一个逗号隔开,而每一行则再用一对{}包起来,表示每个字段对应的值。
注意,第一个{}内共有2个字段名,那么第二个{}中的每一行的顺序和数量也必须与之对应。不能是=#table({"产品","数量"},{{10,"a"},{"b",20}})也不能是=#table({"产品","数量"},{{"a",10},{"b"}})

record

即记录,简单点理解,record就是相当于table中的一行,凡是带[]的都和record有关。
我们对上面构建的table深化出第1行,可以看到record是长成这样的:

一个字段名,一个值,一一对应,有点像别的语言中的dict。
如果要构建一个record,格式为=[产品="a",数量=10],注意record中的字段名没有引号,而构建table中的字段名是要加引号的。

list

即列表,简单点理解,list就是相当于table中的不带字段名的一列,凡是带{}的都和list有关。
我们对上面构建的table深化出"产品"字段,可以看到list长这样:

如果要构建一个list,格式为={"a","b"}。和table一样,list是有序的,根据索引号找到的值是唯一的,比如={"a","b"}{1},结果为"b"。
在M中,可以用..来构建一个连续列表,..前后分别表示list的起和止,且必须起<=止,比如{1..100},表示一个number类型1-100的list。但是不能写成{100..1},如果要构建逆序list,可以=List.Reverse({1..100})
除了number型,我们也可以构建text型的list,比如{"0".."9"},但是不能写成{"1".."100"},因为这种写法只能使用单字节的字符串,而"100"显然不符合要求,如果要得到一个text型的1-100,可以先构建number型,然后使用转换函数将其转成text型=List.Transform({1..100},Text.From)
常见的文本list除了{"0".."9"}代表所有数字,还有{"a".."z"}表示所有小写英文字母,{"A".."Z"}表示所有大写英文字母,{"A".."z"}表示所有英文字母和部分标点,{"一".."龥"}表示绝大多数汉字等等。这个是根据Unicode编码来的,中文的范围大概在19968-40891之间,你可以用= List.Transform({1..50000},Character.FromNumber)来获取所有编码列表。

 

深化:

上面已经提到过,深化就是对table record list提取其中的部分内容。对record深化只能用[],对list深化只能用{},对table深化可以同时使用[]和{},但是对table使用[]后得到的是一个list,而使用{}得到的是一个record,这个一定要搞清楚不要混淆。比如表{0},表示表中第一行的所有列,显然是个record。同时获取行还有一个表示方式为表{[产品="a"]},但必须保证该字段下的命名唯一,也就是产品这一列中只能有一个"a"。
 

嵌套:

如果按照在excel中的概念,对table同时使用[]和{}深化,就可以精确定位到一个位置并获取到具体的值,已经是最小单位不可再分割了。但是在PQ中,list和record中每一个元素的类型可以各不相同,而且类型可以是any,什么概念呢?来看这样一个list:
= {1,"a",#date(2017,6,28),false,null,Text.From,{1},[产品=1],#table({"产品","数量"},{{"a",10},{"b",20}})}

我们发现这个list里面有各种不同类型的数据,如果用{8}对其深化,又会得到一个table,又可以用{}和[]继续深化下去。这就是嵌套,理论上可以无限套下去,record也同理。
 

转换:

list record和table三者之间可以互相转换,有专门的转换函数,比如Table.ToColumns,Record.FromList等等,这个以后会讲到。搞清楚三者之间的区别和联系,是学好M语言的重中之重。
 

连接:

在excel中我们可以用&将文本连接合并,在PQ中&不仅能连接文本,对于三大容器同样适用:
={1,2}&{3,4}得到{1,2,3,4},作用相当于List.Combine=[A=1,B=2]&[B=3,C=4]得到[A=1,B=3,C=4],相当于Record.Combine,因为一个字段只能对应一个值,所以不同字段会追加,相同字段会覆盖只保留最后一个值;=表1&表2则相当于Table.Combine,也就是功能界面的追加查询。
 

关于record的高级用法:

上面说过三大容器中的类型可以为any,回忆一下在高级编辑器中的语法结构,比如:

let
    a=c+5,
    b=a*3,
    c=2
in
    c

将三个变量赋给三个值,最后返回c的结果。而record的结构也同样为一个字段对应一个值,且各元素之间可以相互引用计算,所以我们完全可以将三个步骤写成一个步骤放进record里,然后深化出最后一个步骤,写成:

let
    r= [a=c+5,b=a*3,c=2][c]
in
    r

理论上所有分步的代码全部可以用record写成一步,当然在这里没有必要这么做,但是在很多情况下,使用record构建中间步骤然后重复调用,可以减少公式重复次数,使代码更加简洁。举个简单的例子:

我现在要每个名称下拆分后的第一个和第二个的合并,那么可以写成= Table.AddColumn(源, "结果", each [a=Text.Split([名称],"/"),b=a{0}&a{1}][b])
而如果不这么写,Text.Split就需要写两次,是不是简洁了许多?

26 Replies to “数据三大容器:table record list”

      1. 老师我特别感兴趣M,然而国外的材料比起dax都不算多,国内更少. 请教
        1.怎么可以加入你的交流群
        2.市面上曾和朱的视频哪个好一些,难度深一点.
        3.朱的关于M的书算是什么难度

        求解答谢谢

    1. 我写的是顺序和数量要对应,不能少字段。
      你这样写从语法上说是没有错的,但从规范性来说肯定有问题了。

  1. = Table.AddColumn(源, "结果", each [a=Text.Split([名称],"/"),b=a{0}&a{1}][b]),当中Text.Split([名称],"/"),第一参数不是应该是text么,单独写个[名称],我理解的应该是List的。我想分步写,第一步就是分列,Text.Split(源[名称],"/"),直接说是list类型不正确,这个我知道结果是list,但是为什么写到一块就是上面公式中[名称]就是文本呢

    1. [名称]本来指的是一列,应该是list,但是因为存在上下文,对于每一行来说,[名称]就是这一列中当前行的值,就是text。
      源[名称]表示深化出源这张表的名称列,不受上下文影响,就是list。

  2. 求教:数据源里有几个没有表头的字段,而且这种字段数量不一定,我想通过自动判断其数量和位置,把它们找出来,并合并成一列,应该怎么做啊,看了基础语法没看出类似VBA的for each之类的操作方法

    1. List.Accumulate有for each类似的效果,Table.ColumnNames获取表头名list,然后用List.Accumulate循环判断

  3. 弄了PQ这么长时间,感觉好多基础知识都没掌握,很大原因就是Table,List,Record的语法没完全掌握,这篇文章要反复阅读反复操作才行。

    1. choicechoi说道: 2020年4月26日 下午12:17
      弄了PQ这么长时间,感觉好多基础知识都没掌握,很大原因就是Table,List,Record的语法没完全掌握,这篇文章要反复阅读反复操作才行。
      同感同感,互勉!

  4. 施老师,如果您方便的话,可否把使用两次Text.Split的分步过程也写出来呢~
    = Table.AddColumn(源, "结果", each [a=Text.Split([名称],"/"),b=a{0}&a{1}][b])
    我尝试了几次都没有成功,很抱歉麻烦您。

  5. each [a=Text.Split([名称],"/"),b=a{0}&a{1}][b]
    研究了很久,终于明白了。

    自己举了一个栗子
    let
    a= [b=1,c=2][c]
    in
    a

    谢谢,老师。

  6. 最后一段“关于record的高级用法”的举例说明“在很多情况下,使用record构建中间步骤然后重复调用,可以减少公式重复次数,使代码更加简洁”很有启发啊,感谢分享!!

  7. record
    即记录,简单点理解,record就是相当于table中的一行,凡是带[]的都和record有关。
    ... ...

    list
    即列表,简单点理解,list就是相当于table中的不带字段名的一列,凡是带{}的都和list有关。

    施老师,看了文中在record部分所描述的[ ]和list部分所述的{ },我有些迷糊,方括号和大括号与下面的图也不对应,是不是写反了呀,请您留意一下。

    1. record那里用{0}表示是表示第一行,[产品]表示产品一列,这一点和excel超级表是一脉相承的

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注