介绍

概述

Microsoft Power Query 提供了包含许多功能的强大“获取数据”体验。 Power Query 的核心功能是筛选和合并,即从支持的数据源的一个或多个丰富集合中“混合”数据。 任何此类数据混合都使用 Power Query 公式语言(通常也称为“M”)来表示。 Power Query 在各种 Microsoft 产品(包括 Excel、Power BI、Analysis Services、Dataverse)中嵌入 M 文档,以启用可重复的数据糅合。

本文档提供了 M 规范。在简要介绍了如何构建一些初步直觉并熟悉语言后,本文档将通过几个渐进式步骤精确介绍此语言:

  1. 词法结构定义一组在词法上有效的文本 。

  2. 此语言的基本概念的值、表达式、环境和变量、标识符和计算模型 。

  3. 值(包括基元值和结构化值)的详细规范定义了语言的目标域 。

  4. 值具有类型,它们本身就是一种特殊类型的值,既表示基本类型的值,又携带特定于结构化值的形状的其他元数据 。

  5. M 中的一组运算符定义可以形成的表达式类型 。

  6. 函数是另一种特殊值,为 M 提供丰富标准库的基础,并允许添加新抽象 。

  7. 在表达式计算过程中应用运算符或函数时,可能会出现错误 。 虽然错误不是数值,但可以通过多种方法将错误映射回值来处理错误

  8. Let 表达式可以引入辅助定义,以便在更小的步骤中生成复杂的表达式 。

  9. If 表达式支持条件计算 。

  10. 节提供简单的模块化机制 。 (Power Query 尚未使用节。)

  11. 最后,合并语法将此文档的所有其他节中的语法片段收集到一个完整的定义中 。

对于计算机语言理论家而言:本文档中指定的公式语言是一个大致纯粹的、高阶的、动态类型化的、部分延迟的函数语言。

表达式和值

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 
    
  • “记录”是一组字段 。 字段是名称/值对,其中名称是在字段的记录中唯一的文本值。 记录值的文本语法允许将名称写成不带引号的形式,这种形式也称为“标识符” 。 下面显示了一个记录,其中包含名为 ABC 的三个字段,这些字段具有值 123

    [ 
          A = 1,  
          B = 2,  
          C = 3 
    ]
    
  • “表”是一组按列(按名称标识)和行组织的值 。 不存在用于创建表的文本语法,但有几个标准函数可用于从列表或记录创建表。

    例如:

    #table( {"A", "B"}, { {1, 2}, {3, 4} } ) 
    

    这将创建一个形状如下所示的表:

    Image of an example table in the M formula language.

  • “函数”是一个值,当带着参数进行调用时,将生成一个新值。 函数编写的方法是在括号中列出函数的参数,后跟“转到”符号 => 和定义函数的表达式。 该表达式通常引用参数(按名称)。

    (x, y) => (x + y) / 2`
    

计算

M 语言的计算模型是根据电子表格中常见的计算模型建模的,这种模型可以基于单元格中公式之间的依赖关系来确定计算顺序。

如果你已经在 Excel 等电子表格中编写过公式,你应该知道左侧的公式在计算时会生成右侧的值:

Image of the formulas on the right resulting in the values on the left.

在 M 中,表达式的各个部分可以按名称引用表达式的其他部分,并且计算过程会自动确定所引用表达式的计算顺序。

我们可以使用一个记录来生成一个与上述电子表格示例等效的表达式。 初始化字段的值时,可以通过使用字段名称引用记录中的其他字段,如下所示:

[  
    A1 = A2 * 2,  
    A2 = A3 + 1,  
    A3 = 1  
]

上述表达式等效于以下表达式(因为两者都计算出相等的值):

[  
    A1 = 4,  
    A2 = 2,  
    A3 = 1  
]

记录可以包含在其他记录中,也可以嵌套在其他记录中。 你可以使用查找运算符 ([]) 按名称访问记录的字段。 例如,以下记录具有一个名为 Sales 的字段(包含一个记录)和一个名为 Total 的字段(用于访问 Sales 记录的 FirstHalfSecondHalf 字段):

[  
    Sales = [ FirstHalf = 1000, SecondHalf = 1100 ], 
    Total = Sales[FirstHalf] + Sales[SecondHalf] 
]

计算后,上述表达式等效于以下表达式:

[  
    Sales = [ FirstHalf = 1000, SecondHalf = 1100 ], 
    Total = 2100 
]

记录也可以包含在列表中。 我们可以使用位置索引运算符 ({}) 按其数字索引访问列表中的项目。 从列表的开头开始,使用从零开始的索引来引用列表中的值。 例如,索引 01 用于引用下面列表中的第一和第二项:

[ 
    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 中,数字 12 是操作数,而运算符是加法运算符 (+)。

运算符的含义可以根据操作数值的类型而变化。 例如,加号运算符可用于数字以外的值类型:

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 相关联。 例如,以下代码将带有 RatingTags 字段的元数据记录与文本值 "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),该值是根据绑定到名称 Sales2007Sales2008 的值计算得出的。

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