Introdução

Visão geral

O Microsoft Power Query fornece uma experiência avançada de obtenção de dados que abrange vários recursos. Uma das principais funcionalidades do Power Query é filtrar e combinar, ou seja, realizar o mashup de dados de uma ou mais coleções avançadas de fontes de dados compatíveis. Qualquer mashup de dados desse tipo é expresso usando a linguagem de fórmula Power Query (informalmente conhecida como "M"). O Power Query insere documentos M em uma ampla variedade de produtos da Microsoft, incluindo Excel, Power BI, Analysis Services e Dataverse, para permitir o mashup repetível de dados.

Este documento fornece a especificação para M. Após uma breve introdução que visa criar uma primeira intuição e familiaridade com a linguagem, o documento aborda a linguagem de maneira mais precisa, em várias etapas progressivas:

  1. A estrutura lexical define o conjunto de textos que são lexicalmente válidos.

  2. Os valores, as expressões, os ambientes, as variáveis, os identificadores e o modelo de avaliação que formam os conceitos básicos da linguagem.

  3. A especificação detalhada dos valores, tanto primitivos quanto estruturados, define o domínio de destino da linguagem.

  4. Os valores têm tipos, que são um tipo especial de valor, caracterizam os tipos fundamentais de valores e contêm metadados adicionais que são específicos para as formas de valores estruturados.

  5. O conjunto de operadores no M define que tipos de expressões podem ser formados.

  6. As funções, outro tipo de valores especiais, fornecem a base para uma biblioteca padrão avançada para o M e permitem a adição de novas abstrações.

  7. Os erros podem ocorrer ao se aplicar operadores ou funções durante uma avaliação de expressão. Embora os erros não sejam valores, há maneiras de tratar erros que mapeiam erros de volta para valores.

  8. As expressões let permitem a introdução de definições auxiliares usadas para criar expressões complexas em etapas menores.

  9. as expressões if dão suporte à avaliação condicional.

  10. As seções fornecem um mecanismo de modularidade simples. (O Power Query ainda não usa as seções.)

  11. Por fim, uma gramática consolidada coleta os fragmentos de gramática de todas as outras seções deste documento em uma definição completa.

Para teóricos de linguagem de computador: a linguagem de fórmula especificada neste documento é uma linguagem funcional essencialmente pura, de ordem mais alta, dinamicamente tipada e parcialmente lenta.

Expressões e valores

O constructo central no M é a expressão. Uma expressão pode ser avaliada (computada), produzindo um valor.

Embora muitos valores possam ser escritos literalmente como uma expressão, um valor não é uma expressão. Por exemplo, a expressão 1 é avaliada como o valor 1; a expressão 1+1 é avaliada como o valor 2. Essa distinção é sutil, mas importante. As expressões são receitas para avaliação; os valores são os resultados da avaliação.

Os exemplos a seguir ilustram os diferentes tipos de valores disponíveis em M. Como uma convenção, um valor é escrito usando o formato literal em que eles apareceriam em uma expressão que é avaliada apenas para esse valor. (Observe que as // indicam o início de um comentário que continua até o final da linha.)

  • Um valor primitivo é um valor de parte única, como um número, lógico, texto ou nulo. Um valor nulo pode ser usado para indicar a ausência de dados.

    123                  // A number
    true                 // A logical
    "abc"                // A text
    null                 // null value
    
  • Um valor de lista é uma sequência ordenada de valores. O M dá suporte a listas infinitas, mas, se escritas como um literal, as listas têm um comprimento fixo. Os caracteres de chave { e } denotam o início e o fim de uma lista.

    {123, true, "A"}     // list containing a number, a logical, and 
                          //     a text 
    {1, 2, 3}            // list of three numbers 
    
  • Um registro é um conjunto de campos. Um campo é um par de nome/valor em que o nome é um valor de texto exclusivo no registro do campo. A sintaxe literal para valores de registro permite que os nomes sejam gravados sem aspas, um formato também conhecido como identificadores. O exemplo a seguir mostra um registro que contém três campos denominados "A", "B" e "C", que têm os valores 1, 2 e 3.

    [ 
          A = 1,  
          B = 2,  
          C = 3 
    ]
    
  • Uma tabela é um conjunto de valores organizados em colunas (que são identificadas por nome) e linhas. Não há nenhuma sintaxe literal para criação de uma tabela, mas há várias funções padrão que podem ser usadas para criar tabelas com base em listas ou registros.

    Por exemplo:

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

    Isso cria uma tabela com o seguinte formato:

    Image of an example table in the M formula language.

  • Uma função é um valor que, quando invocado com argumentos, produz um novo valor. A função é escrita pela listagem dos parâmetros da função entre parênteses, seguidos pelo símbolo de ir para =>, seguido pela expressão que define a função. Essa expressão normalmente se refere aos parâmetros (por nome).

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

Avaliação

O modelo de avaliação da linguagem M é modelado após o modelo de avaliação normalmente encontrado em planilhas, em que a ordem dos cálculos pode ser determinada com base nas dependências entre as fórmulas nas células.

Se você já escreveu fórmulas em uma planilha como o Excel, poderá reconhecer que as fórmulas à esquerda resultarão nos valores à direita quando calculadas:

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

No M, uma expressão pode referenciar outras partes da expressão por nome, e o processo de avaliação determinará automaticamente a ordem na qual as expressões referenciadas serão calculadas.

Você pode usar um registro para produzir uma expressão equivalente ao exemplo da planilha acima. Ao inicializar o valor de um campo, você pode fazer referência a outros campos no registro pelo uso do nome do campo, da seguinte maneira:

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

A expressão acima é equivalente à seguinte (no sentido de que ambas são avaliadas para valores iguais):

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

Os registros podem estar contidos ou aninhados em outros registros. É possível usar o operador de pesquisa ([]) para acessar os campos de um registro pelo nome. Por exemplo, o seguinte registro tem um campo chamado Sales com um registro e um campo chamado Total que acessa os campos FirstHalf e SecondHalf do registro Sales:

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

A expressão acima é equivalente à seguinte, quando avaliada:

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

Os registros também podem estar contidos em listas. É possível usar o operador de índice posicional ({}) para acessar um item em uma lista pelo respectivo índice numérico. Os valores dentro de uma lista são referenciados usando um índice de base zero desde o início da lista. Por exemplo, os índices 0 e 1 são usados para referenciar o primeiro e o segundo itens na lista abaixo:

[ 
    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 
]

As expressões de membro de lista e de registro (bem como as expressões let, apresentadas mais adiante) são avaliadas usando avaliação lenta, o que significa que são avaliadas somente conforme necessário. Todas as outras expressões são avaliadas usando a avaliação rápida, ou seja, imediatamente, quando encontradas durante o processo de avaliação. Uma boa maneira de pensar nisso é lembrar que a avaliação de uma expressão de lista ou de registro retorna um valor de lista ou registro que lembra como os itens da lista ou os campos de registro precisam ser computados, quando solicitado (por operadores de pesquisa ou de índice).

Funções

No M, uma função é um mapeamento de um conjunto de valores de entrada para um valor de saída. Para escrever uma função, primeiro é preciso nomear o conjunto necessário de valores de entrada (os parâmetros para a função) e, em seguida, fornecer uma expressão que calcule o resultado da função usando esses valores de entrada (o corpo da função) após o símbolo de ir para (=>). Por exemplo:

(x) => x + 1                    // function that adds one to a value 
(x, y) =>  x + y                // function that adds two values

Uma função é um valor, assim como um valor de número ou de texto. O exemplo a seguir mostra uma função que é o valor de um campo Adicionar, que é invocada ou executada de vários outros campos. Quando uma função é invocada, um conjunto de valores é especificado, que são substituídos logicamente pelo conjunto necessário de valores de entrada na expressão do corpo da função.

[ 
    Add = (x, y) => x + y,
    OnePlusOne = Add(1, 1),     // 2 
    OnePlusTwo = Add(1, 2)      // 3
]

Biblioteca

O M inclui um conjunto comum de definições disponíveis para uso por meio de uma expressão chamada de biblioteca padrão ou simplesmente biblioteca. Essas definições consistem em um conjunto de valores nomeados. Os nomes de valores fornecidos por uma biblioteca estão disponíveis para uso em uma expressão sem terem sido definidos explicitamente pela expressão. Por exemplo:

Number.E                        // Euler's number e (2.7182...) 
Text.PositionOf("Hello", "ll")  // 2

Operadores

O M inclui um conjunto de operadores que podem ser usados em expressões. Os operadores são aplicados a operandos para formar expressões simbólicas. Por exemplo, na expressão 1 + 2, os números 1 e 2 são operandos e o operador é o operador de adição (+).

O significado de um operador pode variar dependendo do tipo de valores dos seus operandos. Por exemplo, o operador de adição pode ser usado com outros tipos de valores além de números:

1 + 2                   // numeric addition: 3 
#time(12,23,0) + #duration(0,0,2,0) 
                        // time arithmetic: #time(12,25,0)

Outro exemplo de um operador com significado dependente do operando é o operador de combinação (&):

"A" & "BC"              // text concatenation: "ABC" 
{1} & {2, 3}            // list concatenation: {1, 2, 3} 
[ a = 1 ] & [ b = 2 ]   // record merge: [ a = 1, b = 2 ]

Observe que alguns operadores não dão suporte a todas as combinações de valores. Por exemplo:

1 + "2"  // error: adding number and text isn't supported

As expressões que, quando avaliadas, encontram condições de operador indefinidas são avaliadas como erros.

Metadados

Os metadados são informações sobre um valor que está associado a um valor. Os metadados são representados como um valor de registro, chamado de registro de metadados. Os campos de um registro de metadados podem ser usados para armazenar os metadados de um valor.

Cada valor tem um registro de metadados. Se o valor do registro de metadados não for especificado, o registro de metadados estará vazio (não tem campos).

Os registros de metadados fornecem uma forma de associar informações adicionais a qualquer tipo de valor de maneira discreta. A associação de um registro de metadados com um valor não altera o valor nem o comportamento dele.

Um valor de registro de metadados y é associado a um valor x existente usando a sintaxe x meta y. Por exemplo, o seguinte código associa um registro de metadados aos campos Rating e Tags com o valor de texto "Mozart":

"Mozart" meta [ Rating = 5, Tags = {"Classical"} ]

Para valores que já contêm um registro de metadados não vazio, o resultado de aplicar o operador meta é que a mesclagem do registro de metadados novo e do existente é computada. Por exemplo, as seguintes duas expressões são equivalentes uma à outra e à expressão anterior:

("Mozart" meta [ Rating = 5 ]) meta [ Tags = {"Classical"} ] 
"Mozart" meta ([ Rating = 5 ] & [ Tags = {"Classical"} ])

Um registro de metadados pode ser acessado para um determinado valor usando a função Value.Metadata. No exemplo a seguir, a expressão no campo ComposerRating acessa o registro de metadados do valor no campo Composer e, em seguida, acessa o campo Rating do registro de metadados.

[ 
    Composer = "Mozart" meta [ Rating = 5, Tags = {"Classical"} ], 
    ComposerRating = Value.Metadata(Composer)[Rating] // 5
]

Expressão let

Muitos dos exemplos mostrados até agora incluíam todos os valores literais da expressão no resultado da expressão. A expressão let permite que um conjunto de valores sejam computados, que nomes sejam atribuídos a eles e, depois, que esses valores sejam usados em uma expressão subsequente que segue a instrução in. Por exemplo, em nosso exemplo de dados de vendas, é possível fazer:

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

O resultado da expressão acima é um valor numérico (4600) computado com base nos valores associados aos nomes Sales2007 e Sales2008.

Expressão if

A expressão if seleciona entre duas expressões com base em uma condição lógica. Por exemplo:

if 2 > 1 then
    2 + 2
else  
    1 + 1

A primeira expressão (2 + 2) será selecionada se a expressão lógica (2 > 1) for verdadeira e a segunda expressão (1 + 1) será selecionada se ela for falsa. A expressão selecionada (nesse caso, 2 + 2) é avaliada e torna-se o resultado da expressão if (4).

Errors

Um erro é uma indicação de que o processo de avaliação de uma expressão não pôde produzir um valor.

Os erros são gerados por operadores e funções que encontraram condições de erros ou usaram a expressão de erro. Os erros são tratados usando a expressão try. Quando um erro é gerado, é especificado um valor que pode ser usado para indicar por que o erro ocorreu.

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])

O exemplo acima acessa o campo Sales[UnitPrice] e formata o valor que produz o resultado:

"Unit Price: 2"

Se o campo Units for zero, o campo UnitPrice gerará um erro que será tratado capturado pelo try. O valor resultante teria sido:

"No Units"

Uma expressão try converte valores e erros adequados em um valor de registro que indica se a expressão try tratou um erro ou não, bem como o valor apropriado ou o registro de erro que ela extraiu ao tratar o erro. Por exemplo, considere a seguinte expressão que gera um erro e o trata imediatamente:

try error "negative unit count"

Essa expressão avalia valor de registro aninhado a seguir, explicando as pesquisas de campo [HasError], [Error] e [Message] no exemplo de preço unitário anterior.

[ 
    HasError = true, 
    Error = 
        [ 
            Reason = "Expression.Error", 
            Message = "negative unit count", 
            Detail = null 
        ] 
]

Um caso comum é substituir erros por valores padrão. A expressão try também pode ser usada com uma cláusula otherwise opcional para obter apenas isso em um formato compacto:

try error "negative unit count" otherwise 42 
// 42