概述
Microsoft Power Query 提供了强大的“获取数据”体验,其中包含许多功能。Power Query 的一项核心功能是筛选和组合,即“混搭”来自一个或多个受支持数据源的丰富集合中的数据。任何此类数据混搭均使用 Power Query 公式语言(非正式称为“M”)表示。Power Query 将 M 文档嵌入 Excel 和 Power BI 工作簿中,以实现可重复的数据混搭。
本文档提供了 M 的规范。经过旨在建立对语言的初步直觉和熟悉的简要介绍后,文档准确地分几个渐进步骤涵盖了该语言:
- 该词法结构定义了一组是词汇的有效文本。
- 值、表达式、环境和变量、标识符以及评估模型构成了语言的基本概念。
- 值的详细规范,包括原始的和结构化的,定义了语言的目标域。
- 值具有类型,它们本身是一种特殊的值,它既表征了值的基本类型,又携带了特定于结构化值形状的附加元数据。
- M 中的运算符集定义了可以形成哪些类型的表达式。
- Functions是另一种特殊值,它为 M 的丰富标准库提供了基础,并允许添加新的抽象。
- 在表达式求值期间应用运算符或函数时可能会发生错误。虽然错误不是值,但有一些方法可以处理将错误映射回值的错误。
- 让表达式允许引入辅助定义,用于以较小的步骤构建复杂的表达式。
- 如果表达式支持条件评估。
- 节提供了一个简单的模块化机制。(Power Query 尚未利用部分。)
- 最后,综合语法将来自本文档所有其他部分的语法片段收集到一个完整的定义中。
对于计算机语言理论家:本文档中指定的公式语言是一种主要是纯粹的、高阶的、动态类型的、部分惰性的函数式语言。
表达式和值
M 中的核心结构是表达式。一个表达式可以被评估(计算),产生一个单一的值。
尽管许多值可以按字面写成表达式,但值不是表达式。例如,表达式的1
计算结果为1;表达式的1 1
计算结果为2。这种区别很微妙,但很重要。表达式是求值的方法;值是评估的结果。
以下示例说明了 M 中可用的不同类型的值。按照惯例,值使用字面形式写入,它们将出现在计算结果为该值的表达式中。(请注意,//
表示继续到行尾的注释的开始。)
- 甲原始值是单部分的值,如数字,逻辑,文本或空。空值可用于指示不存在任何数据。 复制 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 - 一个记录是一组领域。字段是名称/值对,其中名称是字段记录中唯一的文本值。记录值的文字语法允许在不带引号的情况下写入名称,这种形式也称为identifiers。下面显示了包含三个字段的纪录名为“
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 中,表达式的部分可以通过名称引用表达式的其他部分,并且评估过程将自动确定引用表达式的计算顺序。
我们可以使用一个记录来生成一个表达式,它相当于上面的电子表格示例。在初始化一个字段的值时,我们可以通过字段名来引用记录内的其他字段,如下:
复制
代码语言:javascript复制[
A1 = A2 * 2,
A2 = A3 1,
A3 = 1
]
上面的表达式等效于以下表达式(因为两者的计算结果相等):
复制
代码语言:javascript复制[
A1 = 4,
A2 = 2,
A3 = 1
]
记录可以包含在或嵌套在其他记录中。我们可以使用查找运算符( []
) 按名称访问记录的字段。例如,以下记录有一个名为Sales
包含记录的字段,以及一个名为Total
访问记录的FirstHalf
和SecondHalf
字段的字段Sales
:
复制
代码语言:javascript复制[
Sales = [ FirstHalf = 1000, SecondHalf = 1100 ],
Total = Sales[FirstHalf] Sales[SecondHalf]
]
上面的表达式在计算时等效于以下内容:
复制
代码语言:javascript复制[
Sales = [ FirstHalf = 1000, SecondHalf = 1100 ],
Total = 2100
]
记录也可以包含在列表中。我们可以使用位置索引运算符( {}
) 通过其数字索引访问列表中的项目。列表中的值使用从列表开头开始的从零开始的索引来引用。例如,索引0
和1
用于引用以下列表中的第一项和第二项:
复制
代码语言:javascript复制[
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 表达式)使用惰性求值进行求值,这意味着它们仅在需要时进行求值。所有其他表达式都使用Eager Evaluation 求值,这意味着在求值过程中遇到它们时会立即求值。考虑这一点的一个好方法是记住,评估列表或记录表达式将返回一个列表或记录值,该值本身记住在请求时(通过查找或索引运算符)需要如何计算其列表项或记录字段。
职能
在 M 中,函数是从一组输入值到单个输出值的映射。编写函数时,首先命名所需的一组输入值(函数的参数),然后提供一个表达式,该表达式将使用这些输入值(函数的主体)来计算函数的结果(函数的主体)。=>
) 象征。例如:
复制
代码语言:javascript复制(x) => x 1 // function that adds one to a value
(x, y) => x y // function that adds two values
函数是一个值,就像数字或文本值一样。下面的示例显示了一个函数,它是一个 Add 字段的值,然后从其他几个字段调用或执行该函数。调用函数时,会指定一组值,这些值在逻辑上会替换函数体表达式中所需的一组输入值。
复制
代码语言:javascript复制[
Add = (x, y) => x y,
OnePlusOne = Add(1, 1), // 2
OnePlusTwo = Add(1, 2) // 3
]
图书馆
M 包括一组通用定义,可从称为标准库或简称为库的表达式中使用。这些定义由一组命名值组成。库提供的值的名称可在表达式中使用,而无需由表达式明确定义。例如:
复制
代码语言:javascript复制Number.E // Euler's number e (2.7182...)
Text.PositionOf("Hello", "ll") // 2
运营商
M 包括一组可在表达式中使用的运算符。运算符应用于操作数以形成符号表达式。例如,在表达式中1 2
,数字1
和2
是操作数,运算符是加法运算符 (
)。
运算符的含义可以根据其操作数是什么类型的值而有所不同。例如,加号运算符可用于数字以外的其他类型的值:
复制
代码语言:javascript复制1 2 // numeric addition: 3
#time(12,23,0) #duration(0,0,2,0)
// time arithmetic: #time(12,25,0)
具有操作数相关含义的运算符的另一个示例是组合运算符 ( &
):
复制
代码语言:javascript复制"A" & "BC" // text concatenation: "ABC"
{1} & {2, 3} // list concatenation: {1, 2, 3}
[ a = 1 ] & [ b = 2 ] // record merge: [ a = 1, b = 2 ]
请注意,运算符可能不支持所有值的组合。例如:
复制
代码语言:javascript复制1 "2" // error: adding number and text is not supported
计算时遇到未定义运算符条件的表达式计算为错误。稍后会详细介绍 M 中的错误。
元数据
元数据是关于与值关联的值的信息。元数据表示为记录值,称为元数据记录。元数据记录的字段可用于存储值的元数据。
每个值都有一个元数据记录。如果未指定元数据记录的值,则元数据记录为空(没有字段)。
元数据记录提供了一种以不显眼的方式将附加信息与任何类型的值相关联的方法。将元数据记录与值相关联不会改变该值或其行为。
元数据记录值使用语法y
与现有值相关联。例如,以下将元数据记录与文本值和字段相关联:xx meta yRatingTags"Mozart"
复制
代码语言:javascript复制"Mozart" meta [ Rating = 5, Tags = {"Classical"} ]
对于已经携带非空元数据记录的值,应用元数据的结果是计算现有和新元数据记录的记录合并。例如,以下两个表达式彼此等效,并且与前面的表达式等效:
复制
代码语言:javascript复制("Mozart" meta [ Rating = 5 ]) meta [ Tags = {"Classical"} ]
"Mozart" meta ([ Rating = 5 ] & [ Tags = {"Classical"} ])
可以使用Value.Metadata函数访问给定值的元数据记录。在下面的示例中,ComposerRating
字段中的表达式访问字段中值的元数据记录Composer
,然后访问Rating
元数据记录的字段。
复制
代码语言:javascript复制[
Composer = "Mozart" meta [ Rating = 5, Tags = {"Classical"} ],
ComposerRating = Value.Metadata(Composer)[Rating] // 5
]
让表达
迄今为止显示的许多示例都在表达式的结果中包含了表达式的所有文字值。该设表达式允许的一组值被计算,分配的名称,然后在随后的一个随后的表达式中使用的。例如,在我们的销售数据示例中,我们可以执行以下操作:
复制
代码语言:javascript复制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
基于逻辑条件两个表达式之间的表达进行选择。例如:
复制
代码语言:javascript复制if 2 > 1 then
2 2
else
1 1
2 2
如果逻辑表达式 ( 2 > 1
) 为真,则选择第一个表达式 ( ),如果逻辑表达式1 1
为假,则选择第二个表达式 ( )。选定的表达式(在本例中2 2
)被评估并成为if
表达式 ( 4
)的结果。
错误
的错误是计算表达式的过程中不能产生的值的指示。
错误由遇到错误条件的运算符和函数或通过使用错误表达式引发。使用 try 表达式处理错误。出现错误时,会指定一个值,该值可用于指示发生错误的原因。
复制
代码语言:javascript复制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]
字段并格式化产生结果的值:
复制
代码语言:javascript复制"Unit Price: 2"
如果该Units
字段为零,则该UnitPrice
字段将引发一个错误,该错误将由try
. 结果值将是:
复制
代码语言:javascript复制"No Units"
甲try
表达适当的值和错误转换成记录值,指示尝试表达是否处理和错误,或没有,并且或者适当的值或错误记录它处理错误时萃取。例如,考虑以下引发错误然后立即处理的表达式:
复制
代码语言:javascript复制try error "negative unit count"
该表达式评估为以下嵌套记录值,解释[HasError]
,[Error]
以及[Message]
在之前单价实例字段查找。
复制
代码语言:javascript复制[
HasError = true,
Error =
[
Reason = "Expression.Error",
Message = "negative unit count",
Detail = null
]
]