Introducción

Información general

Microsoft Power Query proporciona una potente experiencia de "obtención de datos" que abarca muchas características. Una de las funciones básicas de Power Query es filtrar y combinar, es decir, "mezclar" los datos de una o varias colecciones enriquecidas de orígenes de datos admitidos. Cualquier mashup de datos se expresa mediante el lenguaje de fórmulas de Power Query (conocido informalmente como "M"). Para habilitar el mashup repetible de datos, Power Query inserta documentos M en una amplia gama de productos de Microsoft, incluidos Excel, Power BI, Analysis Services y Dataverse.

En este documento se proporciona la especificación para M. Después de una breve introducción que se centra en generar una intuición y familiaridad iniciales, se describe el lenguaje de forma precisa en varios pasos progresivos:

  1. La estructura léxica define el conjunto de textos que son léxicamente válidos.

  2. Los valores, las expresiones, los entornos y variables, los identificadores y el modelo de evaluación conforman los conceptos básicos del lenguaje.

  3. La especificación detallada de los valores, tanto primitivos como estructurados, define el dominio de destino del lenguaje.

  4. Los valores tienen tipos que, a su vez, son un tipo especial de valor, que caracterizan los tipos fundamentales de valores y contienen metadatos adicionales específicos de las formas de valores estructurados.

  5. El conjunto de operadores de M define qué tipos de expresiones se pueden formar.

  6. Las funciones, otro tipo de valores especiales, proporcionan la base para una biblioteca estándar enriquecida para M y permiten agregar abstracciones nuevas.

  7. Al aplicar operadores o funciones durante la evaluación de expresiones, se pueden producir errores. Aunque los errores no son valores, hay formas de controlar los errores que permiten volver a asignarlos a valores.

  8. Las expresiones let permiten la introducción de definiciones auxiliares que se usan para crear expresiones complejas en pasos más pequeños.

  9. Las expresiones if admiten la evaluación condicional.

  10. Las secciones proporcionan un mecanismo de modularidad simple. (En Power Query todavía no se aprovechan las secciones).

  11. Por último, en una gramática consolidada se recopilan los fragmentos de gramática de todas las demás secciones de este documento en una única definición completa.

Para los teóricos de los lenguajes de computación: el lenguaje de fórmulas que se especifica en este documento es un lenguaje funcional parcialmente diferido, principalmente puro, de orden superior y con tipos dinámicos.

Expresiones y valores

En M, la construcción central es la expresión. Una expresión se puede evaluar (calcular), lo que produce un único valor.

Aunque muchos valores se pueden escribir literalmente como una expresión, un valor no es una expresión. Por ejemplo, la expresión 1 se evalúa como el valor 1, mientras que 1+1 se evalúa como el valor 2. Esta distinción es sutil, pero importante. Las expresiones son las recetas de evaluación y los valores, los resultados de esa evaluación.

En los siguientes ejemplos se muestran los diferentes tipos de valores disponibles en M. Como convención, un valor se escribe con la forma literal en la que aparecería en una expresión que se evalúa solamente como ese valor. (Tenga en cuenta que // indica el inicio de un comentario que continúa hasta el final de la línea).

  • Un valor primitivo es un valor de una sola parte, como un número, un valor lógico, texto o un valor null. Se puede usar un valor null para indicar la ausencia de datos.

    123                  // A number
    true                 // A logical
    "abc"                // A text
    null                 // null value
    
  • Un valor de lista es una secuencia ordenada de valores. M admite listas infinitas, pero si se escriben como un literal, las listas tienen una longitud fija. Los caracteres de llave { y } indican el principio y el final de una lista.

    {123, true, "A"}     // list containing a number, a logical, and 
                          //     a text 
    {1, 2, 3}            // list of three numbers 
    
  • Un registro es un conjunto de campos. Un campo es un par nombre-valor en el que el nombre es un valor de texto único dentro del registro del campo. La sintaxis literal de los valores de registro permite escribir los nombres sin comillas, formato que también se conoce como identificadores. El registro siguiente contiene tres campos denominados "A", "B" y "C", que tienen los valores 1, 2 y 3.

    [ 
          A = 1,  
          B = 2,  
          C = 3 
    ]
    
  • Una tabla es un conjunto de valores organizados en columnas (que se identifican por nombre) y filas. No hay ninguna sintaxis literal para crear una tabla, pero se pueden usar varias funciones estándar para crear tablas a partir de listas o registros.

    Por ejemplo:

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

    Esto crea una tabla con la forma siguiente:

    Image of an example table in the M formula language.

  • Una función es un valor que, cuando se invoca con argumentos, genera un nuevo valor. Para escribir funciones, se enumeran los parámetros de la función en entre paréntesis, seguidos del signo igual =>, seguido de la expresión que define la función. Esa expresión normalmente hace referencia a los parámetros (por nombre).

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

Evaluación

El modelo de evaluación del lenguaje M se basa en el que normalmente se encuentra en las hojas de cálculo, donde el orden de los cálculos se puede determinar en función de las dependencias entre las fórmulas de las celdas.

Si ha escrito fórmulas en una hoja de cálculo como Excel, sabrá reconocer que, al calcularse, las fórmulas de la izquierda dan como resultado los valores de la derecha:

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

En M, los elementos de una expresión pueden hacer referencia a otros elementos de la expresión por el nombre, y el proceso de evaluación determinará de forma automática el orden en el que se calculan las expresiones a las que se hace referencia.

Se puede usar un registro para generar una expresión que sea equivalente al ejemplo de hoja de cálculo anterior. Al inicializar el valor de un campo, puede hacer referencia a otros campos del registro por el nombre del campo, de esta forma:

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

La expresión anterior es equivalente a la siguiente (las dos se evalúan como valores de igualdad):

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

Los registros se pueden incluir, o anidar, en otros registros. Se puede usar el operador de búsqueda ([]) para acceder a los campos de un registro por sus nombres. Por ejemplo, el registro siguiente tiene un campo denominado Sales que contiene un registro, y un campo denominado Total que accede a los campos FirstHalf y SecondHalf del registro Sales:

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

La expresión anterior es equivalente a la siguiente cuando se evalúa:

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

Los registros también se pueden incluir dentro de listas. Puede usar el operador de índice posicional ({}) para acceder a un elemento de una lista por su índice numérico. Para hacer referencia a los valores de una lista, se usa un índice de base cero desde el principio de la lista. Por ejemplo, los índices 01 se usan para hacer referencia al primer y segundo elemento de la lista siguiente:

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

Las expresiones de miembro de lista y de registro (así como las expresiones let) se evalúan mediante evaluación diferida, lo que significa que solo se evalúan cuando es necesario. Todas las demás expresiones se evalúan mediante la evaluación diligente; es decir, inmediatamente en cuanto se detectan durante el proceso de evaluación. Una buena forma de verlo es recordar que la evaluación de una expresión de lista o de registro devolverá un valor de lista o registro que recuerda cómo se deben calcular sus elementos de lista o campos de registro cuando se solicite (mediante operadores de búsqueda o de índice).

Functions

En M, una función es una asignación de un conjunto de valores de entrada a un único valor de salida. Para escribir una función, primero se asigna un nombre al conjunto de valores de entrada necesario (los parámetros de la función) y, después, se proporciona una expresión que calculará el resultado de la función con esos valores de entrada (el cuerpo de la función) que aparecen después del símbolo (=>). Por ejemplo:

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

Una función es un valor como un número o un valor de texto. En el ejemplo siguiente se muestra una función que corresponde al valor de un campo Add que después se invoca, o se ejecuta, desde otros campos. Cuando una función se invoca, se especifica un conjunto de valores que se sustituyen lógicamente por el conjunto necesario de valores de entrada dentro de la expresión del cuerpo de la función.

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

Biblioteca

En M se incluye un conjunto común de definiciones que se pueden usar desde una expresión llamada biblioteca estándar, o simplemente biblioteca. Estas definiciones se componen de un conjunto de valores con nombre. Los nombres de los valores proporcionados por una biblioteca están disponibles para su uso en una expresión sin que la expresión los haya definido de forma explícita. Por ejemplo:

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

Operadores

En M se incluye un conjunto de operadores que se pueden usar en expresiones. Los operadores se aplican a operandos para formar expresiones simbólicas. Por ejemplo, en la expresión 1 + 2, los números 1 y 2 son operandos y el operador es el de suma (+).

El significado de un operador puede variar en función del tipo de valores de sus operandos. Por ejemplo, el operador más se puede usar con otros tipos de valores que no sean números:

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

Otro ejemplo de un operador con significado, que depende del operando, es el de combinación (&):

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

Tenga en cuenta que algunos operadores no admiten todas las combinaciones de valores. Por ejemplo:

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

Las expresiones que, cuando se evalúen, tengan condiciones de operador sin definir se evaluarán como errores.

Metadatos

Los metadatos son información relativa a un valor que está asociado a otro valor. Los metadatos se representan como un valor de registro, denominado registro de metadatos. Los campos de un registro de metadatos se pueden usar para almacenar los metadatos de un valor.

Cada valor tiene un registro de metadatos. Si no se ha especificado el valor del registro de metadatos, el registro de metadatos estará vacío (no tendrá campos).

Los registros de metadatos proporcionan una manera de asociar información adicional con cualquier tipo de valor de forma discreta. La asociación de un registro de metadatos con un valor no cambia el valor ni su comportamiento.

Un valor de registro de metadatos y se asocia a un valor x existente mediante la sintaxis x meta y. Por ejemplo, el siguiente código asocia un registro de metadatos con los campos Rating y Tags con el valor de texto "Mozart":

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

Para los valores que ya contienen un registro de metadatos no vacío, el resultado de aplicar meta es el de calcular la combinación de registros del registro de metadatos existente y el nuevo. Por ejemplo, las dos expresiones siguientes son equivalentes entre sí y con la expresión anterior:

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

Se puede acceder a un valor concreto de un registro de metadatos mediante la función Value.Metadata. En el ejemplo siguiente, la expresión del campo ComposerRating accede al registro de metadatos del valor en el campo Composer y, después, accede al campo Rating del registro de metadatos.

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

Expresión let

Muchos de los ejemplos mostrados hasta ahora han incluido todos los valores literales de la expresión en el resultado de la expresión. La expresión let permite calcular un conjunto de valores, asignarles nombres y, después, usarlos en una expresión posterior que aparece después de in. Por ejemplo, en el ejemplo de datos de ventas, se podría hacer lo siguiente:

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

El resultado de la expresión anterior es un valor numérico (4600) que se ha calculado a partir de los valores enlazados a los nombres Sales2007 y Sales2008.

Expresión if

La expresión if selecciona entre dos expresiones posibles en función de una condición lógica. Por ejemplo:

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

Se selecciona la primera expresión (2 + 2) si la expresión lógica (2 > 1) es true, y se selecciona la segunda (1 + 1) si es false. La expresión seleccionada (en este caso, 2 + 2) se evalúa y se convierte en el resultado de la expresión if (4).

Errors

Un error es una indicación de que el proceso de evaluación de una expresión no ha podido generar un valor.

Los errores se generan mediante operadores y funciones que encuentran condiciones de error o mediante la expresión error. Los errores se controlan mediante la expresión try. Cuando se produce un error, se especifica un valor que se puede usar para indicar por qué se produjo el error.

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

En el ejemplo anterior se accede al campo Sales[UnitPrice] y se da formato al valor que genera el resultado:

"Unit Price: 2"

Si el campo Units hubiera sido cero, el campo UnitPrice habría generado un error que la expresión try habría controlado. De haber sido así, el valor resultante hubiera sido el siguiente:

"No Units"

Una expresión try convierte los valores y los errores en un valor de registro en el que se indica si la expresión try ha controlado un error (o no) y el valor correcto o el registro de error que ha extraído al controlar el error. Por ejemplo, considere la siguiente expresión, que genera un error y, después, lo controla inmediatamente:

try error "negative unit count"

Esta expresión se evalúa como el siguiente valor de registro anidado, que explica las búsquedas de campo [HasError], [Error] y [Message] en el ejemplo de precio de unidad anterior.

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

Un caso habitual es reemplazar los errores por valores predeterminados. La expresión try se puede usar con una cláusula otherwise opcional para lograr justamente eso con un formato compacto:

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