Microsoft Power Query M语言规范介绍
Microsoft Power Query M语言规范介绍
Microsoft Power Query提供了一个强大的“获取数据”体验,其核心功能是筛选和合并数据。Power Query使用M语言来表示数据混合操作,并在Excel、Power BI等多个产品中嵌入M文档以实现可重复的数据处理。本文档详细介绍了M语言的规范,包括其语法结构、基本概念、值类型、计算模型、函数、库、运算符、元数据、let表达式、if表达式以及错误处理机制。
概述
Microsoft Power Query提供了一个强大的“获取数据”体验,其核心功能是筛选和合并数据。Power Query使用M语言来表示数据混合操作,并在Excel、Power BI等多个产品中嵌入M文档以实现可重复的数据处理。本文档详细介绍了M语言的规范,包括其语法结构、基本概念、值类型、计算模型、函数、库、运算符、元数据、let表达式、if表达式以及错误处理机制。
对于计算机语言理论家而言:本文档中指定的公式语言是一个大致纯粹的、高阶的、动态类型化的、部分延迟的函数语言。
表达式和值
M中的中央构造是表达式。可以对表达式求值(计算),然后得到单个值。
尽管许多值都可以按字面形式写成表达式,但值不是表达式。例如,表达式
1
的计算结果为值 1;表达式
1+1
的计算结果为值 2。这种区别很细微,但很重要。表达式是计算的方法;值是计算的结果。
下面的示例演示了M中可用的不同类型的值。通常,使用文本形式编写值,它们将显示在计算结果为仅该值的表达式中。(请注意,
//
指示注释的开头,注释延续至行尾。)
- “基元”值是单个部分值,如数字、逻辑、文本或NULL。NULL值可用于指示缺少数据。
123 // A number
true // A logical
"abc" // A text
null // null value
- “列表”值是值的有序序列。M支持无限列表,但如果作为文本写入,则列表具有固定长度。大括号字符
{
和
}
表示列表的开头和结尾。
{123, true, "A"} // list containing a number, a logical, and
// a text
{1, 2, 3} // list of three numbers
- “记录”是一组字段。字段是名称/值对,其中名称是在字段的记录中唯一的文本值。记录值的文本语法允许将名称写成不带引号的形式,这种形式也称为“标识符”。下面显示了一个记录,其中包含名为
A
、
B
和
C
的三个字段,这些字段具有值
1
、
2
和
3
。
[
A = 1,
B = 2,
C = 3
]
- “表”是一组按列(按名称标识)和行组织的值。不存在用于创建表的文本语法,但有几个标准函数可用于从列表或记录创建表。
例如:
#table( {"A", "B"}, { {1, 2}, {3, 4} } )
这将创建一个形状如下所示的表:
- “函数”是一个值,当带着参数进行调用时,将生成一个新值。函数编写的方法是在括号中列出函数的参数,后跟“转到”符号
=>
和定义函数的表达式。该表达式通常引用参数(按名称)。
(x, y) => (x + y) / 2`
计算
M语言的计算模型是根据电子表格中常见的计算模型建模的,这种模型可以基于单元格中公式之间的依赖关系来确定计算顺序。
如果你已经在Excel等电子表格中编写过公式,你应该知道左侧的公式在计算时会生成右侧的值:
在M中,表达式的各个部分可以按名称引用表达式的其他部分,并且计算过程会自动确定所引用表达式的计算顺序。
我们可以使用一个记录来生成一个与上述电子表格示例等效的表达式。初始化字段的值时,可以通过使用字段名称引用记录中的其他字段,如下所示:
[
A1 = A2 * 2,
A2 = A3 + 1,
A3 = 1
]
上述表达式等效于以下表达式(因为两者都计算出相等的值):
[
A1 = 4,
A2 = 2,
A3 = 1
]
记录可以包含在其他记录中,也可以嵌套在其他记录中。你可以使用查找运算符(
[]
)按名称访问记录的字段。例如,以下记录具有一个名为
Sales
的字段(包含一个记录)和一个名为
Total
的字段(用于访问
Sales
记录的
FirstHalf
和
SecondHalf
字段):
[
Sales = [ FirstHalf = 1000, SecondHalf = 1100 ],
Total = Sales[FirstHalf] + Sales[SecondHalf]
]
计算后,上述表达式等效于以下表达式:
[
Sales = [ FirstHalf = 1000, SecondHalf = 1100 ],
Total = 2100
]
记录也可以包含在列表中。我们可以使用位置索引运算符(
{}
)按其数字索引访问列表中的项目。从列表的开头开始,使用从零开始的索引来引用列表中的值。例如,索引
0
和
1
用于引用下面列表中的第一和第二项:
[
Sales =
{
[
Year = 2007,
FirstHalf = 1000,
SecondHalf = 1100,
Total = FirstHalf + SecondHalf // 2100
],
[
Year = 2008,
FirstHalf = 1200,
SecondHalf = 1300,
Total = FirstHalf + SecondHalf // 2500
]
},
TotalSales = Sales{0}[Total] + Sales{1}[Total] // 4600
]
列表和记录成员表达式(以及let表达式)使用延迟计算进行计算,这意味着它们只会根据需要进行计算。其他所有表达式都使用迫切计算进行计算,这意味着如果在计算过程中遇到它们,则将立即对其进行计算。一种好用的理解方法是记住计算列表或记录表达式将返回一个列表或记录值,该列表或值本身会记住在请求时(查找或索引运算符)需如何计算其列表项或记录字段。
函数
在M中,函数是一组输入值到单个输出值的映射。函数的编写方法是,首先命名所需的一组输入值(函数的参数),然后提供一个表达式,该表达式将使用“转到”(
=>
)符号后面的这些输入值(函数的主体)来计算函数的结果。例如:
(x) => x + 1 // function that adds one to a value
(x, y) => x + y // function that adds two values
函数是一个值,就像数字或文本值一样。以下示例演示中的函数是Add字段的值,该值随后将从其他几个字段调用或执行。调用函数时,将指定一组值,这些值会在逻辑上替换函数正文表达式中所需的输入值集。
[
Add = (x, y) => x + y,
OnePlusOne = Add(1, 1), // 2
OnePlusTwo = Add(1, 2) // 3
]
库
M包括一组通用的定义,来自标准库(简称为库)表达式。这些定义包含一组命名值。库提供的值的名称可在表达式中使用,无需通过表达式显式定义。例如:
Number.E // Euler's number e (2.7182...)
Text.PositionOf("Hello", "ll") // 2
运算符
M包含一组可在表达式中使用的运算符。将运算符运用于操作数即形成符号表达式。例如,在表达式
1 + 2
中,数字
1
和
2
是操作数,而运算符是加法运算符 (
+
)。
运算符的含义可以根据操作数值的类型而变化。例如,加号运算符可用于数字以外的值类型:
1 + 2 // numeric addition: 3
#time(12,23,0) + #duration(0,0,2,0)
// time arithmetic: #time(12,25,0)
另一个含义依赖于操作数的运算符示例是组合运算符 (
&
):
"A" & "BC" // text concatenation: "ABC"
{1} & {2, 3} // list concatenation: {1, 2, 3}
[ a = 1 ] & [ b = 2 ] // record merge: [ a = 1, b = 2 ]
请注意,某些运算符不支持部分值组合。例如:
1 + "2" // error: adding number and text isn't supported
如果表达式在计算时遇到未定义的运算符条件,计算结果将为错误。
Metadata
元数据是与其他值相关联的值的相关信息。元数据被表示为一个记录值,称为元数据记录。元数据记录的字段可用于存储值的元数据。
每个值都有一个元数据记录。如果尚未指定元数据记录的值,元数据记录则为空(没有字段)。
元数据记录提供了一种以非介入式方式将附加信息与任何类型的值相关联的方法。将元数据记录与值相关联不会更改该值或其行为。
使用语法
x meta y
将元数据记录值
y
与现有的值
x
相关联。例如,以下代码将带有
Rating
和
Tags
字段的元数据记录与文本值
"Mozart"
相关联:
"Mozart" meta [ Rating = 5, Tags = {"Classical"} ]
对于已经包含非空元数据记录的值,应用meta的结果是计算现有和新的元数据记录的记录合并的结果。例如,下面两个表达式等效于彼此和前面的表达式:
("Mozart" meta [ Rating = 5 ]) meta [ Tags = {"Classical"} ]
"Mozart" meta ([ Rating = 5 ] & [ Tags = {"Classical"} ])
可以使用Value.Metadata函数访问一个给定值的元数据记录。在下面的示例中,
ComposerRating
字段中的表达式访问
Composer
字段中值的元数据记录,然后访问元数据记录的
Rating
字段。
[
Composer = "Mozart" meta [ Rating = 5, Tags = {"Classical"} ],
ComposerRating = Value.Metadata(Composer)[Rating] // 5
]
Let 表达式
到目前为止,很多示例都在表达式的结果中包含了表达式的所有文本值。
let
表达式允许一组值进行计算、分配名称,然后在
in
后面的后续表达式中使用。例如,在我们的销售数据示例中,可以执行以下操作:
let
Sales2007 =
[
Year = 2007,
FirstHalf = 1000,
SecondHalf = 1100,
Total = FirstHalf + SecondHalf // 2100
],
Sales2008 =
[
Year = 2008,
FirstHalf = 1200,
SecondHalf = 1300,
Total = FirstHalf + SecondHalf // 2500
]
in Sales2007[Total] + Sales2008[Total] // 4600
上述表达式的结果是一个数字值 (
4600
),该值是根据绑定到名称
Sales2007
和
Sales2008
的值计算得出的。
If 表达式
if
表达式根据逻辑条件在两个表达式之间进行选择。例如:
if 2 > 1 then
2 + 2
else
1 + 1
如果逻辑表达式 (
2 > 1
)为true,则选择第一个表达式 (
2 + 2
);如果为false,则选择第二个表达式 (
1 + 1
)。将对选定的表达式(在本例中为
2 + 2
)进行计算,并成为
if
表达式 (
4
)的结果。
错误
错误指示计算表达式的过程不能产生值。
错误是由运算符和函数遇到错误条件,或使用了错误表达式导致的。可以使用
try
表达式来处理错误。引发某一错误时,将指定一个值,此值可用于指示错误发生的原因。
let Sales =
[
Revenue = 2000,
Units = 1000,
UnitPrice = if Units = 0 then error "No Units"
else Revenue / Units
],
UnitPrice = try Number.ToText(Sales[UnitPrice])
in "Unit Price: " &
(if UnitPrice[HasError] then UnitPrice[Error][Message]
else UnitPrice[Value])
上面的示例访问
Sales[UnitPrice]
字段并格式化产生结果的值:
"Unit Price: 2"
如果
Units
字段为零,
UnitPrice
字段会引发错误,而
try
表达式则会处理此错误。结果值将为:
"No Units"
try
表达式将正确的值和错误转换为一个记录值,此值指示
try
表达式是否处理了错误,以及在处理错误时所提取的是正确值还是错误记录。例如,请考虑以下引发错误,然后立即进行处理的表达式:
try error "negative unit count"
此表达式计算结果为以下的嵌套记录值,解释之前单价示例中的
[HasError]
、
[Error]
和
[Message]
字段查找。
[
HasError = true,
Error =
[
Reason = "Expression.Error",
Message = "negative unit count",
Detail = null
]
]
常见的情况是使用默认值替换错误。
try
表达式可以与一个可选的
otherwise
子句一起使用,从而以紧凑的形式实现:
try error "negative unit count" otherwise 42
// 42
