martes, 31 de agosto de 2021

Power Query: Agregar Múltiples Columnas a la Vez

Revisaremos en el día de hoy dos opciones para poder crear columnas (vacías, con null) en nuestras consultas, de manera fácil y dinámica.
Con bastante frecuencia se hace necesaria, antes de realizar, por ejemplo, un anexado 'especial' entre dos tablas, una acción de 'homogeneización' del número y nombre de las columnas... por supuesto, podemos emplear el clásico: Table.AddColumn que permite incorporar de una en una cuantas columnas necesitemos...
pero esto puede resultar una labor larga y tediosa si el número de columnas es elevado.
Tengamos claro que el objetivo es añadir columnas sin valores!!!, solo para igualar estructura de columnas.

Veamos en primer lugar los dos métodos comentados sobre una tabla 'TblORIGEN' con campos: Id, Año, País y Unidades. Tabla que hemos cargado a nuestro editor de Power Query solo como conexión.

Dentro del editor accederemos al editor avanzado y escribiremos, dentro de una consulta en blanco, el siguiente código M donde emplearemos una función recursiva:
let
    origen = TblORIGEN,

    //lista con las nuevas columnas a incorporar
    Lst={"Nueva columna 1","Nueva columna 2"},
    //contamos el número de elementos/columnas de la lista
    NumCols = List.Count( Lst ),

    //creamos una función recursiva
    AddColumns = ( tabla as table, contador as number ) as table =>
        let
            //agregamos a una tabla con datos vacíos en este caso (each null)
            //con el nombre tomado de nuestra lista
            AgregoCol = Table.AddColumn( tabla, 
                                Text.From(Lst{contador}), 
                                each null ),
            //montamos el bucle y salida de este
            //mientras que el contador no supere el número de columnas a incorporar
            //OJO!!, listas en base 0
            ColAgregada = if ( contador < NumCols ) then @AddColumns( AgregoCol, contador + 1 ) else tabla
        in
            ColAgregada,

    //para finalmente aplicar nuestra función recursiva sobre la TblORIGEN
    TablaFinal = AddColumns( origen, 0 )
in
    TablaFinal

Power Query: Agregar Columnas de forma dinámica

Este método recursivo tiene la ventaja que permitiría personalizar qué datos añadimos a las columnas nuevas...
aunque como desventaja diremos que bajo ciertas condiciones puede ralentizar la ejecución de nuestras consultas :-(

El siguiente método es muchísimo más rápido, ágil y fácil de implementar... pero por contra, solo permite añadir columnas sin valores...

Añadamos desde el editor de Power Query una nueva consulta en blanco con el siguiente código M.
Donde emplearemos nuestra conocida Table.SelectColumns, con una 'faceta' desconocida...
let
    origen = TblORIGEN,

    //lista con las nuevas columnas a incorporar
    Lst={"Nueva columna 1","Nueva columna 2"},

    //creamos una nueva lista resultado de unir las columnas ya existentes
    //con las nuevas descritas en la lista previa
    LstCompleta=List.Union({Table.ColumnNames(origen),Lst}),

    //la función Table.SelectColumns aporta en su tercer argumento opcional la alternativa necesaria
    TablaCompleta=Table.SelectColumns(origen, LstCompleta, MissingField.UseNull)

in 
    TablaCompleta

Con idéntico resultado al código anterior!!.

Que nos aporta la función:
Table.SelectColumns(table as table, columns as any, optional missingField as nullable number) as table
Los dos argumentos primeros son los clásicos:
-table: la tabla sobre la que trabajamos
-columns: lista de columnas que deseamos seleccionar
Y normalmente al omitir el tercero opcional, si se da el caso que entre el listado de columnas hubiera alguna que no existiera, el resultado predeterminado sería un error.
Para eso tenemos el tercer argumento:
-missingField: argumento opcional con dos posibilidades:
+ MissingField.Ignore: hará que no aparezca mensaje de error alguno... simplemente no se incluirá esa columna no existente.
+ MissingField.UseNull: cuando alguna/s de las columnas incluidas en la lista no exista, la opción MissingField.UseNull creará una columna de valores NULL.

Lo que necesitábamos!!... más fácil imposible ;-)

Un punto importante... incluye y cambia el tipo de datos de esas columnas para asentar el agregado.. sobre todo si quieres volcar el resultado sobre la hoja de cálculo!!!.

jueves, 26 de agosto de 2021

Power Query: Consigue el Tamaño de Carpetas

Días atrás tuve la necesidad de 'limpiar' un poco mi disco duro de almacenamiento... estaba marcado en rojo, y el espacio disponible evitaba incluso una sencilla operación de desfragmentación...
Obviamente el Explorador de archivos de Windows NO MUESTRA el tamaño de las carpetas, salvo yendo una por una mostrando sus propiedades, lo cual es largo y tedioso (sobre todo cuando las estructuras de carpetas-subcarpetas es larga y profunda).
Así pues, aplicativos de terceros aparte, me acordé de una posibilidad que ofrece Power Query respecto a los Atributos de los archivos: el tamaño (Size) de los ficheros.

Y en esto consiste la entrada de hoy. Veremos como con unos sencillos pasos desarrollados en una consulta de Power Query, podemos analizar el tamaño de nuestras carpetas.... para posteriores acciones, como eliminar o borrar el contenido de algunas de ellas...
Power Query: Consigue el Tamaño de Carpetas


Por tanto, el primer paso consistirá en navegar en un libro vacío de Excel hasta la ficha Datos > grupo Obtener y transformar > Obtener datos > Desde un archivo > Desde una carpeta.
Se abrirá una ventana donde indicaremos cuál es la ruta a analizar (desde una carpeta en concreto, a todo un disco duro raiz).
En mi ejemplo analizaré una carpeta en concreto:
F:\excelforo\PowerAutomate

Al Aceptar veremos una pantalla donde se listan todos los ficheros contenidos en todas las carpetas y subcarpetas dependientes de la anterior indicada.
Power Query: Consigue el Tamaño de Carpetas

Incluso antes de presionar 'Transformar datos' ya previsualizamos la columna sobre la que trabajaremos: Atributtes
Al hacer clic en 'Transformar datos' accederemos al editor de consultas de Power Query donde grabaremos los siguientes pasos:
Paso uno: Expandir la columna 'Attributes' y marcar únicamente 'Size'
Power Query: Consigue el Tamaño de Carpetas

Paso dos: Quitamos otras columnas excepto la anterior-Size- y la columna que muestra la ruta de la carpeta -Folder path-
Power Query: Consigue el Tamaño de Carpetas

Paso tres: Duplicamos la columna anterior-Size- y aprovechamos para renombrarla como 'bytes' (que es la unidad de medida mostrada).
Power Query: Consigue el Tamaño de Carpetas

Paso cuatro: Transformamos la columna de 'Size' dividiéndola entre 1.048.576 (=1024 x 1024). Resultado de pasar de bytes a Mb.
Power Query: Consigue el Tamaño de Carpetas

Paso cinco: Agrupamos por la columna 'Folder path' y resumimos sumando la columna Size y la de bytes.
Power Query: Consigue el Tamaño de Carpetas

Paso seis: Duplicamos la columna 'Folder path'.
Power Query: Consigue el Tamaño de Carpetas

Paso siete: Dividimos la columna 'Folder path' por el delimitador o separador \.
Esto nos permitirá, posteriormente al trabajar con una tabla dinámica sobre la consulta generada, gestionar la jerarquía de la estructura de carpetas y subcarpetas.
Power Query: Consigue el Tamaño de Carpetas

Paso ocho y último: Reordenamos las posiciones de las columnas...

El código generado con los pasos anteriores sería:
let
    Origen = Folder.Files("F:\excelforo\PowerAutomate"),
    Se_expandió_Attributes = Table.ExpandRecordColumn(Origen, "Attributes", {"Size"}, {"Attributes.Size"}),
    Otras_columnas_quitadas = Table.SelectColumns(Se_expandió_Attributes,{"Attributes.Size", "Folder Path"}),
    Columna_duplicada1 = Table.DuplicateColumn(Otras_columnas_quitadas, "Attributes.Size", "bytes"),
    Columna_dividida = Table.TransformColumns(Columna_duplicada1, {{"Attributes.Size", each _ / 1048576, type number}}),
    Filas_agrupadas = Table.Group(Columna_dividida, {"Folder Path"}, {{"Mb", each List.Sum([Attributes.Size]), type number},{"bytes", each List.Sum([bytes]), type number}}),
    Columna_duplicada = Table.DuplicateColumn(Filas_agrupadas, "Folder Path", "Folder Path."),
    Dividir_columna_por_delimitador = Table.SplitColumn(Columna_duplicada, "Folder Path", Splitter.SplitTextByDelimiter("\", QuoteStyle.Csv), {"Folder Path.1", "Folder Path.2", "Folder Path.3", "Folder Path.4", "Folder Path.5", "Folder Path.6", "Folder Path.7"}),
    Columnas_reordenadas = Table.ReorderColumns(Dividir_columna_por_delimitador,{"Folder Path.", "Folder Path.1", "Folder Path.2", "Folder Path.3", "Folder Path.4", "Folder Path.5", "Folder Path.6", "Folder Path.7", "Mb", "bytes"})
in
    Columnas_reordenadas

El resultado...
Power Query: Consigue el Tamaño de Carpetas

La tabla dinámica se ha generado a partir del resultado de la consulta, llevando los campos:
-Área de filtros: Campos Folder Path.1, Folder Path.2
-Área de filas: Campos Folder Path.3, Folder Path.4
-Área de valores: Campos Mb, bytes

Se puede comprobar con las propiedades extraídas del explorador de Windows que la cantidades corresponden...
Solo quedará tomar las decisiones de qué borrar y que mantener... pero eso es otra historia.

martes, 24 de agosto de 2021

Power Query: Table.SelectRows entre Tablas

Días atrás expliqué una alternativa al clásico Table.NestedJoin (i.e., a la acción de Combinar consultas).
Hoy veremos una nueva posibilidad empleando otra función M importante: Table.SelectRows:
Table.SelectRows(table as table, condition as function) as table
Es una función conocida por todos, ya que es la que sirve para aplicar filtros sobre una tabla, es decir, muestra solo las filas de una tabla que cumplen las condiciones dadas...
Si bien, lo habitual es trabajar sobre columnas y filas de la misma tabla...

Y ahí reside lo interesante del artículo de hoy. Veremos como con esta función podremos obtener datos de terceras tablas!!

Partiremos de las cinco tablas fuentes siguientes, situadas en nuestro libro de trabajo: 'TblDATOS','TblREGION','TblDESCUENTOS','TblPRECIOS' y 'TblCOMISION':
Power Query: Table.SelectRows entre Tablas


Comenzaremos cargando las cinco tablas, solo como conexión, en nuestro editor de consultas de Power Query.

En un primer ejercicio, con el objetivo de comparar tiempos de ejecución, montaremos una consulta donde emplearemos la herramienta 'Combinar consultas' (por tanto: Table.NestedJoin).
Empezando desde la 'TblDATOS' iremos combinando y expandiendo los distintos campos necesarios... El código quedaría:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblDATOS"]}[Content],
    Tipo_cambiado = Table.TransformColumnTypes(Origen,{{"Fecha", type datetime}, {"País", type text}, {"Comercial", type text}, {"Producto", type text}, {"Unidades", Int64.Type}}),
    Consultas_combinadas = Table.NestedJoin(Tipo_cambiado, {"País"}, TblREGION, {"Country"}, "TblREGION", JoinKind.LeftOuter),
    Se_expandió_TblREGION = Table.ExpandTableColumn(Consultas_combinadas, "TblREGION", {"Region", "UE/No UE"}, {"Region", "UE/No UE"}),
    Consultas_combinadas1 = Table.NestedJoin(Se_expandió_TblREGION, {"Comercial"}, TblDESCUENTO, {"Comerciales"}, "TblDESCUENTO", JoinKind.LeftOuter),
    Se_expandió_TblDESCUENTO = Table.ExpandTableColumn(Consultas_combinadas1, "TblDESCUENTO", {"Dirección", "Descuento"}, {"Dirección", "Descuento"}),
    Consultas_combinadas2 = Table.NestedJoin(Se_expandió_TblDESCUENTO, {"Producto"}, TblPRECIOS, {"Artículo"}, "TblPRECIOS", JoinKind.LeftOuter),
    Se_expandió_TblPRECIOS = Table.ExpandTableColumn(Consultas_combinadas2, "TblPRECIOS", {"Precio"}, {"Precio"}),
    Consultas_combinadas3 = Table.NestedJoin(Se_expandió_TblPRECIOS, {"Producto", "Comercial"}, TblCOMISION, {"Cod", "Vendedor"}, "TblCOMISION", JoinKind.LeftOuter),
    Se_expandió_TblCOMISION = Table.ExpandTableColumn(Consultas_combinadas3, "TblCOMISION", {"Comisión"}, {"Comisión"})
in
    Se_expandió_TblCOMISION

Power Query: Table.SelectRows entre Tablas

Puesto que solo he seguido la mecánica tantas veces vista en el blog, no me detengo en esta parte... (Para más detalle accede a la categoría de Power Query).

Lo importante viene ahora, donde empleando la función Table.SelectRows replicaremos esas relaciones entre distintas consultas o tablas!!.

En una nueva consulta en blanco escribiremos:
let
    Origen = TblDATOS,
    //Agregamos una nueva columna con el Precio (de la TblPRECIOS) correspondiente
    //cuando coincidan los códigos de los productos ('Producto' = 'Artículo')
    AddCol_PRECIO=
                Table.AddColumn(
                Origen,
                "N_Precio", each 
                            let a=[Producto]
                            in Table.SelectRows(TblPRECIOS, each [Artículo] = a )[Precio]{0}),
    //Dividimos una columna en dos.
    //Columna que hemos agregado con información de la TblREGION, tras verificar la coincidencia del País
    //Al necesitar recuperar dos columnas de REGION,
    //he realizado una concatenación (Table.CombineColumns) de la info de dos columnas
    AddCol_REGION=
        Table.SplitColumn(
                Table.AddColumn(
                AddCol_PRECIO,
                "N_Región", each 
                            let p=[País]
                            in Table.CombineColumns(
                                Table.SelectRows(TblREGION, each [Country] = p ),
                                 {"Region","UE_Y_N"},Combiner.CombineTextByDelimiter("|"),"Para_extraer")[Para_extraer]{0}),
        "N_Región", Splitter.SplitTextByDelimiter("|", QuoteStyle.Csv), {"Region", "UE_Y_N"} ),
    //idem al anterior pero para la TblDESCUENTOS
    AddCol_DCTO=
        Table.SplitColumn(
                Table.AddColumn(
                AddCol_REGION,
                "N_Comercial", each 
                            let c=[Comercial]
                            in Table.CombineColumns(Table.TransformColumnTypes( 
                                Table.SelectRows(TblDESCUENTO, each [Comerciales] = c ),
                                {"Descuento", type text}),
                                 {"Dirección","Descuento"},Combiner.CombineTextByDelimiter("|"),"Para_extraer")[Para_extraer]{0}),
        "N_Comercial", Splitter.SplitTextByDelimiter("|", QuoteStyle.Csv), {"Dirección","Descuento"} ),
    //Agregamos el dato de la 'comisión' desde la TblCOMISION cuando se de la doble coincidencia de Comrcial y Artículo
    AddCol_COMISION=
                Table.AddColumn(
                AddCol_DCTO,
                "N_Comision", each 
                            let a=[Producto], c=[Comercial]
                            in Table.SelectRows(TblCOMISION, each [Cod] = a and [Vendedor]=c)[Comisión]{0})

in
    AddCol_COMISION

En esencia, la parte relevante es donde se emplea Table.SelectRows:
Table.SelectRows(TblPRECIOS, each [Artículo] = a )[Precio]{0}

A pesar de estar trabajando sobre la TblDATOS, podemos referirnos a una tercera tabla ('TblPRECIOS') para recuperar aquellas filas donde se de la condición de que en su columna [Artículo] (OJO!, columna de la tabla TblPRECIOS!!) sea igual al dato 'a' (variable cargada previamente con el dato de la misma fila y la columna [Producto] de la tabla TblDATOS.

La información resultante la incorporamos en una nueva columna de la TblDATOS... lo que se consigue con la función Table.AddColumn:
Table.AddColumn(
                Origen,
                "N_Precio", each 
                            let a=[Producto]
                            in Table.SelectRows(TblPRECIOS, each [Artículo] = a )[Precio]{0})

De cada tabla resultante para cada fila, nos interesa recuperar el dato de la columna [Precio] y el primer y único valor de la fila 1 ([Precio]{0})

Si el cruce de información debe responder a un doble criterio, podemos añadirlo en el argumento de condición de Table.SelectRows:
Table.AddColumn(
                AddCol_DCTO,
                "N_Comision", each 
                            let a=[Producto], c=[Comercial]
                            in Table.SelectRows(TblCOMISION, each [Cod] = a and [Vendedor]=c)[Comisión]{0})

La doble condición, en este caso, se aplica con un criterio lógico 'and':
each [Cod] = a and [Vendedor]=c
donde 'a' y 'c' son variables cargadas previamente con los valores de la TblDATOS de las columnas [Producto] y [Comercial].

Otro caso diferente es cuando queremos recuperar información de más de una columna....
Existen diferentes métodos (los más eficientes suelen ser trabajar con 'Records'), pero en este caso expondré uno distinto empleando concatenaciones (Table.CombineColumns) y divisiones de columnas (Table.SplitColumn).
Table.SplitColumn(
                Table.AddColumn(
                AddCol_REGION,
                "N_Comercial", each 
                            let c=[Comercial]
                            in Table.CombineColumns(Table.TransformColumnTypes( 
                                Table.SelectRows(TblDESCUENTO, each [Comerciales] = c ),
                                {"Descuento", type text}),
                                 {"Dirección","Descuento"},Combiner.CombineTextByDelimiter("|"),"Para_extraer")[Para_extraer]{0}),
        "N_Comercial", Splitter.SplitTextByDelimiter("|", QuoteStyle.Csv), {"Dirección","Descuento"} )

Algo largo... pero fácil de entender.
Desde lo más 'profundo' de la fórmula hacia afuera...
Primero tenemos la selección de filas de la TblDESCUENTO que cumplen la condición de tener igual 'Comerciales' (con Table.SelectRows).
Segundo. Para poder concatenar las columnas deseadas, les cambiamos el tipo de datos a text, con la función Table.TransformColumnTypes
Ese concatenado resultante de las columnas 'Dirección' y 'Descuento' de la TblDESCUENTOS, lo agregamos en una nueva columna en la TblDATOS... usando Table.Addcolumns
Finalmente, aplicamos una división de esta columna recién agregada con Table.SplitColumn

Algo 'retorcidillo'... pero interesante por el uso de distintas funciones M frecuentes en otros contextos...
Desde luego el resultado es idéntico a la primera consulta creada con la herramienta de Combinar consultas.

Algo muy importante que debemos tener presente al usar este sistema es la optimización y/o la agilidad y rapidez de la carga de datos...
Este sistema es sustancialmente más lento que el clásico de 'combinar consultas'!!... pero por contra ofrece un infinito mundo de posibilidades para cruzar distintas tablas, y no únicamente el básico 1-1, 2-2,..., n-n de Table.NestedJoin.

Veamos un ejemplo al respecto...
Supongamos una nueva tabla con unas tarifas determinadas por un rango de fechas (Desde - Hasta) por cada comercial y que únicamente deben aplicar a ES, IT y FR:
Power Query: Table.SelectRows entre Tablas

Como siempre cargaremos esta nueva tabla ('TblTARIFAS') solo como conexión. Y a continuación desde una consulta en blanco escribiremos:
let
    Origen = TblDATOS,

    AddCol_TARIFAS=
                Table.AddColumn(
                Origen,
                "N_Tarifa", each 
                            let f=[Fecha], c=[Comercial]
                            in if ([País]="ES" or [País]="FR" or [País]="IT") then 
                                    Table.SelectRows(TblTARIFAS, each ([Desde] <= f and [Hasta]>=f) and [Comercial]=c)[Tarifa]{0} 
                                else null)
in
    AddCol_TARIFAS

Tras definir y cargar las variable 'f' y 'c' con los datos de la 'TblDATOS', procedemos a aplicar la comparativa (mayor o igual que, menor o igual que) con las filas de la TblTARIFAS dentro del contexto de la función Table.SelectRows (como hemos visto más arriba...).
Condiciones solo aplicadas cuando se cumpla las condiciones lógicas del 'or' indicada con un if... then... else....
Obteniendo:
Power Query: Table.SelectRows entre Tablas


Todas estas opciones y posibles condiciones a incorporar a nuestras consultas resultan muy cómodas de aplicar con este método... y no tanto con otros alternativos, por lo que nuevamente debemos sopesar pros y contras en cada caso particular antes de decidirnos por un método a aplicar ;-)

jueves, 19 de agosto de 2021

Power Query: Tabla Histórica de Registros

Hace unas semanas me plantearon si era posible, con Power Query, tener un histórico de los registros/filas que existieran o hubieran existido alguna vez en nuestra tabla fuente, e igualmente que quedara constancia de las modificaciones ocurridas.

Aunque inicialmente pensé en VBA para Excel, después de pensar algún tiempo, di con una posible solución...
Creo que no es compleja, pero hay que ser meticuloso con los pasos...

Vamos allá!.
Empezaremos con una tabla origen con nombre 'TblORIGEN' (en mi ejemplo dentro del libro de trabajo, en una hoja de cálculo).
Es una tabla muy simple con tres campos: Fecha, País y Unidades
Power Query: Tabla Histórica de Registros

Es imprescindible disponer de un id único para cada registro de nuestra tabla, así que o bien lo añadimos en la TblORIGEN con algún campo calculado o bien manualmente, o, alternativamente, lo incorporamos en nuestra futura consulta de power Query (esta es la opción por la que me he decantado).

Primer paso.
Cargamos normalmente la TblORIGEN, y ya en el Editor de Power Query, agregaremos una nueva columna componiendo un Id o Índice único y constante para cada registro.
En mi ejemplo me he basado en una concatenación de los tres campos de la tabla (valdría cualquier idea siempre que asegure la unicidad!!!)...
En ocasiones añadir terminos horarios (horas, minutos y segundos) ayuda.
El código M de nuestra primera consulta sería entonces:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblORIGEN"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Fecha", type date}, {"País", type text}, {"Unidades", Int64.Type}}),

    //Agregamos una nueva columna como Id único concatenando las tres columnas de la tabla
    ÍndiceAgregado = Table.AddColumn(TipoCambiado, "Índice", each Date.ToText([Fecha]) & [País] & Number.ToText([Unidades]))

in
    ÍndiceAgregado

Cerramos y cargamos el resultado en la hoja de cálculo.
Segundo paso.
La tabla resultante de nuestra consulta (se llamará probablemente 'TblORIGEN_2'), ya en la hoja de cálculo, la copiaremos y pegaremos en otro lugar del libro.
Nos aseguraremos de los dos pasos siguiente:
1- Eliminar la consulta generada al realizar la copia!!
2- Renombrar la tabla por 'TblTemporal'
Power Query: Tabla Histórica de Registros

Tercer paso.
Cargaremos al Editor de Power Query esta tabla 'TblTemporal', a la cual aplicaremos un anexado de la consulta TblORIGEN:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblTemporal"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Fecha", type date}, {"País", type text}, {"Unidades", Int64.Type}, {"Índice", type text}}),

    //Anexamos al resultado de la carga de la TblTemporal, la consulta TblORIGEN (que incluye nuestro Id)
    Anexado=Table.Combine({TipoCambiado, TblORIGEN})

    //veremos que tenemos una duplicidad de registros...
    //que corregiremos a continuación
in
    Anexado

Cerramos y cargamos en una nueva hoja...
Esta tabla se ha llamado 'TblTemporal_2', la cual renombraremos como 'TblFINAL'
Es fácil ver que tenemos duplicados todos los registros.. era de esperar al anexar los mismos registros dos veces...

Esta será nuestra Tabla Histórica final, así que vamos a hacer algún cambio más...

Cuarto paso.
Editamos esta consulta y cambiaremos el Origen actual, de la 'TblTemporal' (existente en la hoja de cálculo) a la 'TblFINAL' (la tabla recién descargada en la hoja de cálculo).
Además, de paso, corregiremos el tema de registros duplicados.
Desde el editor avanzado de Power Query tendremos:
let
    //OJO! cambiamos el origen de 'TblTemporal' por 'TblFINAL' !!
    Origen = Excel.CurrentWorkbook(){[Name="TblFINAL"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Fecha", type date}, {"País", type text}, {"Unidades", Int64.Type}, {"Índice", type text}}),

    //Anexamos al resultado de la carga de la TblTemporal, la consulta TblORIGEN (que incluye nuestro Id)
    Anexado=Table.Combine({TipoCambiado, TblORIGEN}),

    //veremos que tenemos una duplicidad de registros...
    //que corregiremos a continuación
    QuitaDuplicados=Table.Distinct(Anexado)
in
    QuitaDuplicados

Cargamos y cerramos...

Quinto paso.
Eliminamos la tablas intermedias con las que hemos trabajado, dejando exclusivamente:
- la tabla de partida con los datos originales (TblORIGEN)
- la última tabla, que renombramos como 'TblFINAL'

Quedando además solo dos queries de Power Query: TblORIGEN (solo como conexión) y TblTemporal
Power Query: Tabla Histórica de Registros

Sexto paso: Verificación y prueba...
De momento ambas tablas (origen y final) son iguales...veamos que pasa al modificar un dato cualquiera, por ejemplo, cambiar las unidades del segundo registro (14 por 141414):
Power Query: Tabla Histórica de Registros
Perfecto!!, comprobamos como la TblFINAL (nuestra tabla histórica) mantiene el registro original, pero ha incorporado el nuevo!!

Veamos otro caso, eliminaremos de nuestra tabla fuente un registro, el primero que aparece de ES en la fila 9 (Fecha 21/02/2021 País: ES Unidades: 12
Power Query: Tabla Histórica de Registros

Fantástico!, vemos como mantiene el registro eliminado a nuestra disposición.

Una última prueba... añadimos nuevos registros.
Power Query: Tabla Histórica de Registros

Nada mal!!, vemos como a pesar de haber 'forzado' la inserción entre las filas de la tabla fuente, los nuevos registros aparecen anexados al final...

Un trabajo algo laborioso, sobre todo meticuloso, pero que solo hay que hacerlo una única vez. ;-)

martes, 17 de agosto de 2021

Power Query: Comentarios en resultado Consulta

Con frecuencia me comentan que lo que escribo no es para los usuarios 'estándar' de Excel... que escribo sobre aspectos demasiado raros o avanzados :'(
Creo que hoy es uno de esos días ;-)

En alguna ocasión he tenido que implantar un modelo de datos donde el flujo de información exigía que el usuario añadiera notas o comentarios asociados a los registros devueltos de una consulta, es decir, se recogían, cargaban y transformaban datos de las fuentes de datos, para finalmente devolver el resultado de la consulta a la hoja de cálculo...en la cual, el mismo u otro usuario, añadía una nueva columna de comentarios, asociado a registros concretos!!.

Si has usado alguna vez las consultas de Power Query sabrás que, por defecto, cada vez que la consulta se actualiza todo se reubica, pudiendo perder la integridad de los datos.. quedando, en definitiva, una 'base de dato' incongruente e inválida.

Entonces, ¿es posible tal cosa???.
Comprobémoslo!!.

En primer lugar verificaremos la inconsistencia al respecto de las consultas de Power Query.
Así pues cargaremos solo como conexión nuestras dos tablas en el editor de Power Query: 'TblDATOS' y 'TblPRECIOS'
Power Query: Comentarios en resultado Consulta

A continuación relacionaremos y combinaremos ambas tablas para crear una tercera resultante, que tendrá el siguiente código M:
let
    Origen = Table.NestedJoin(TblDATOS, {"País"}, TblPRECIOS, {"País"}, "TblPRECIOS", JoinKind.LeftOuter),
    Se_expandió_TblPRECIOS = Table.ExpandTableColumn(Origen, "TblPRECIOS", {"Precio"}, {"Precio"}),
    AddCol_Total = Table.AddColumn(Se_expandió_TblPRECIOS, "Total", each [Unidades]*[Precio])
in
    AddCol_Total

Es una sencilla consulta que combina ambas tablas por el campo 'País', con Table.NestedJoin, y termina agregando una columna calculada 'Total' como producto de Unidades x Precio.
Esta consulta la llamaré: 'ResultadoUNO' y la cargaré en la hoja de cálculo.
Power Query: Comentarios en resultado Consulta

Es sobre esta tabla 'ResultadoUNO' que tenemos en nuestra hoja de cálculo, donde queremos incorporar una nueva columna de comentarios...(OJO!!, lo añadimos EN LA HOJA DE CÁLCULO, NO EN LA CONSULTA!!)
Tras insertar el nuevo campo, añadimos algún comentario en las celdas...
Power Query: Comentarios en resultado Consulta
Mientras no haya cambios en las fuentes (en especial en la 'TblDATOS'), como que se reordenen las filas, insertemos filas nuevas o eliminemos alguna... los Comentarios respetarán sus ubicaciones, y quedarán asociados a los registros correctos... Podemos actualizar cuantas veces queramos sin problemas (pruébalo).

El problema viene cuando ocurre alguna de esas acciones indicadas:
- Añadimos nuevos registros en el origen (TblDATOS), sobre todo si lo hacemos 'en medio' de la tabla
- Eliminamos registros existentes
- O incluso si reordenamos el origen

En la imagen se puede comprobar que tras ordenar la TblDATOS y actualizar la consulta, los comentarios han permanecido en las celdas concretas donde las escribimos, mientras que las filas se han reubicado según la nueva distribución :OOO
Power Query: Comentarios en resultado Consulta


Obviamente esta situación no responde a nuestras necesidades...

Veamos los pasos a seguir para cumplir nuestra meta.
Partiremos de la situación correcta, i.e., con los comentarios ubicados correctamente y asociados a sus filas.
Primer paso. Verificaremos el nombre de la tabla devuelta por nuestra consulta...
Debería coincidir con el nombre dada a la query (ResultadoUNO en nuestro caso).

Segundo paso. Accederemos a nuestra consulta y la editaremos... Añadiendo la siguiente referencia y carga de esa tabla con comentarios!!.
Desde el editor de Power Query tendríamos:
let
    Origen = Table.NestedJoin(TblDATOS, {"País"}, TblPRECIOS, {"País"}, "TblPRECIOS", JoinKind.LeftOuter),
    Se_expandió_TblPRECIOS = Table.ExpandTableColumn(Origen, "TblPRECIOS", {"Precio"}, {"Precio"}),
    AddCol_Total = Table.AddColumn(Se_expandió_TblPRECIOS, "Total", each [Unidades]*[Precio]),

    //Cargamos la tabla resultado CON LOS COMENTARIOS, que se llama 'ResultadoUNO' (como esta query!)
    TablaConComentarios = Excel.CurrentWorkbook(){[Name="ResultadoUNO"]}[Content],
    //Relacionamos, con Table.NestedJoin, esta consulta (antes de la carga anterior)
    //con la TablaConComentarios... 
    CombinaComentarios=Table.NestedJoin(AddCol_Total, {"Id"}, TablaConComentarios, {"Id"}, "Nueva Columna",JoinKind.LeftOuter),

    //y acabamos expandiendo para recuperar SOLO la columna de comentarios!!
    ExpandeComentarios = Table.ExpandTableColumn(CombinaComentarios, "Nueva Columna", {"Comentarios"}, {"Comentarios"})
in
    ExpandeComentarios

Power Query: Comentarios en resultado Consulta

Tercer paso. Tras la generación del nuevo código incorporado en nuestra consulta, cargamos y cerramos, con lo que volvemos a la hoja de cálculo...
Comprobarás que la primera vez se da una 'extraña circunstancia' (no tanto), y es que se duplica el campo 'Comentario', aparace un Comentario que 'vuelve' desde la consulta además del Comentario original (renombrado como 'Comentario.2').
No te preocupes... elimina el Comentario original, es decir, el campo 'Comentario.2' (además mantiene el formato de color dado, por lo que es fácil de identificar).
Power Query: Comentarios en resultado Consulta


Estamos listos!!!...
Probamos a reordenar la tabla origina 'TblDATOS y actualizamos la consulta:
Power Query: Comentarios en resultado Consulta
:OOO Se mantiene la integridad de las filas!!

Añadamos filas nuevas en la TblDATOS, y de paso rellenaremos algún comentario más...
Power Query: Comentarios en resultado Consulta

OJO!!, al meter nuevas filas, debemos mantener 'integro e indexado' el campo 'Id' (vamos, que no se repitan valores de Id!).
Este campo 'Id' es el que compone y estabiliza nuestro trabajo...

Si tras los cambios previos actualizamos la consulta veremos:
Power Query: Comentarios en resultado Consulta

Fantástico!!... los viejos y nuevos comentarios se quedan donde deben estar!!...

Estupendo ejercicio.
Recalcar que la clave de este funcionamiento es tener una clave como nexo de unión... en mi ejemplo (me lo he puesto fácil) es el campo Id... si no existiera, tendríamos que componerlo (sobre 2,3,.. campos) o de cualquier otra forma.
Espero te resulte útil ;-)

jueves, 12 de agosto de 2021

Power Query: Table.Join vs Table.NestedJoin

Cuando hablamos de relacionar tablas entre sí, dentro del contexto de Power Query, la opción de Combinar consultas es la reina... es fácil de entender y está disponible desde el menú del editor de Power Query.

Quizá no sepas que esta opción implementa en nuestro código M la función Table.NestedJoin: Table.NestedJoin(table1 as table, key1 as any, table2 as any, key2 as any, newColumnName as text, optional joinKind as nullable number, optional keyEqualityComparers as nullable list) as table
Función un poco larga en cuanto argumentos, sobre la que he hablado largo y tendido en distintos artículos del blog.
Pero que básicamente vincula/relaciona dos tablas basándose en una o varias columnas para cruzarlas (con los distintos tipos de Joins existentes), llevando el resultante a una nueva columna...

Otro ejemplo más de esto podría ser el siguiente.
A partir de tres tablas: 'TblVENTAS, TblPRECIO y TblDESCUENTOS'
Power Query: Table.Join vs Table.NestedJoin

Tablas que ya vimos y trabajamos, implementando las claves primarias, en este artículo...
y sobre las que continuaremos trabajando el día de hoy.

Con estas tablas cargadas, y ya desde el editor, bien siguiendo pasos clásicos o bien escribiendo código directamente, llegamos a esta consulta:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblVENTAS"]}[Content],
    Tipo_Cambiado = Table.TransformColumnTypes(Origen,{{"Fechas", type date}, {"País", type text}, {"Comercial", type text}, {"Producto", type text}, {"Unidades", Int64.Type}}),
    //Combinamos con la Tabla de Descuentos
    Consultas_Combinadas = Table.NestedJoin(Tipo_Cambiado, {"País", "Comercial"}, TblDESCUENTOS, {"País", "Comercial"}, "TblDESCUENTOS", JoinKind.LeftOuter),
    //para después extraer los campos de interés
    Se_expandió_TblDESCUENTOS = Table.ExpandTableColumn(Consultas_Combinadas, "TblDESCUENTOS", {"Descuento"}, {"Descuento"}),
    
    //Combinamos con la tercera tabla, la de precios
    Consultas_combinadas1 = Table.NestedJoin(Se_expandió_TblDESCUENTOS, {"Producto"}, TblPRECIO, {"Producto"}, "TblPRECIO", JoinKind.LeftOuter),
    //y finalmente extraemos el Precio
    Se_expandió_TblPRECIO = Table.ExpandTableColumn(Consultas_combinadas1, "TblPRECIO", {"Precio"}, {"Precio"})
in
    Se_expandió_TblPRECIO

Dos ejemplos de combinar usando la función Table.NestedJoin.

Sin pegas ni comentarios adicionales...

Pero existe una buena alternativa a esta función: Table.Join
Table.Join(table1 as table, key1 as any, table2 as table, key2 as any, optional joinKind as nullable number, optional joinAlgorithm as nullable number, optional keyEqualityComparers as nullable list) as table
La cual combina las filas de table1 con las filas de table2 basándose en la igualdad de los valores de las columnas de clave que seleccionamos (como Key1 y Key2).
El uso de esta función provoca un anexado de columnas total, de acuerdo al tipo de Join elegido (los mismos tipos que con Table.NestedJoin!!).
Es decir, no existe la opción de elegir qué columnas 'extraer/expandir'... al contrario, todas las columnas se despliegan, por lo que en un paso siguiente, si lo estimamos, deberemos eliminar las columnas 'sobrantes' (usando Table.RemoveColumns o bien la inversa Table.SelectColumns).

Otro aspecto a tener en cuenta, muy importante, es que Table.Join devolverá un error en caso que en la table1 y la table2 existan columnas con igual nombre...
Lo cual tiene sentido, a priori, ya que no es posible tener dos columnas con el mismo nombre ;-)

Otro argumento diferenciador de Table.Join es el opcional joinAlgortihm con las siguientes opciones:
JoinAlgorithm.Dynamic: elige automáticamente un algoritmo de combinación basado en la inspección de las filas iniciales y los metadatos de ambas tablas.
JoinAlgorithm.PairwiseHash: almacena en búfer las filas de las tablas izquierda y derecha hasta que una de las tablas se almacena en búfer por completo, y luego realiza un LeftHash o RightHash, según la tabla que se haya almacenado en búfer por completo.
Este algoritmo se recomienda solo para tablas pequeñas.
JoinAlgorithm.LeftHash: almacena las filas de la izquierda en una tabla de búsqueda y transmite las filas de la derecha. Para cada fila derecha, las filas izquierdas coincidentes se encuentran a través de la tabla de búsqueda almacenada en búfer.
Se recomienda este algoritmo cuando la tabla de la izquierda es pequeña y se espera que la mayoría de las filas de la tabla de la derecha coincidan con una fila de la izquierda.
JoinAlgorithm.LeftIndex: en lotes, utiliza las claves de la tabla de la izquierda para realizar consultas basadas en predicados en la tabla de la derecha.
Este algoritmo se recomienda cuando la tabla de la derecha es grande, admite el plegado de Table.SelectRows y contiene pocas filas que se espera que coincidan con una fila de la izquierda.
JoinAlgorithm.RightHash: almacena las filas de la derecha en una tabla de búsqueda y transmite las filas de la izquierda. Para cada fila de la izquierda, las filas de la derecha coincidentes se encuentran a través de la tabla de búsqueda almacenada en búfer.
Se recomienda este algoritmo cuando la tabla de la derecha es pequeña y se espera que la mayoría de las filas de la tabla de la izquierda coincidan con una fila de la derecha.
JoinAlgorithm.Rightlndex: en lotes, utiliza las claves de la tabla de la derecha para realizar consultas basadas en predicados en la tabla de la izquierda.
Este algoritmo se recomienda cuando la tabla de la izquierda es grande, admite el plegado de Table.SelectRows y contiene pocas filas que se espera que coincidan con una fila de la derecha.
JoinAlgorithm.SortMerge: realiza una combinación de transmisión basada en el supuesto de que ambas tablas están ordenadas por sus claves de combinación.
OJO!!!!, Importante!!. Si bien es eficiente, devolverá resultados incorrectos si las tablas no están ordenadas como se esperaba.

Mi recomendación personal sería emplear la primera de las opciones: JoinAlgorithm.Dynamic, ya que de esta forma Power Query elegirá por nosotros la mejor opción posible...
Otra opción bastante buena es usar el algortimo: JoinAlgorithm.SortMerge, pero tomando las precauciones de ordenación que indican.

Con todo esto, a modo de ejemplo, escribiremos nuestra siguiente consulta empleando Table.Join:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblVENTAS"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Fechas", type date}, {"País", type text}, {"Comercial", type text}, {"Producto", type text}, {"Unidades", Int64.Type}}),
    
    TblPRECIO_Ordenada=Table.Buffer(
                        Table.RenameColumns(
                            Table.AddKey(
                                Table.Sort(TblPRECIO,{"Producto", Order.Ascending}),
                                {"Producto"},true),
                        {"Producto", "Pto"})),

    TblDCTO_Ordenada=Table.Buffer(
                        Table.RenameColumns(
                            Table.AddKey(
                                Table.Sort(TblDESCUENTOS,{{"País", Order.Ascending},{"Comercial", Order.Ascending}}),
                                {"País","Comercial"},true),
                        {{"Comercial", "Salesman"},{"País","Country"}})),

    CombinadoPRECIO=Table.Join(Table.Sort(TipoCambiado,{{"Producto", Order.Ascending}}),"Producto",
                        TblPRECIO_Ordenada,"Pto",
                        JoinKind.LeftOuter,
                        JoinAlgorithm.SortMerge),

    CombinadoDCTO=Table.Join(Table.Sort(CombinadoPRECIO,{{"País", Order.Ascending},{"Comercial", Order.Ascending}}),{"País","Comercial"},
                        TblDCTO_Ordenada,{"Country","Salesman"},
                        JoinKind.LeftOuter,
                        JoinAlgorithm.SortMerge)

in
    CombinadoDCTO
En este ejemplo hemos realizado las mismas uniones que con la función Table.NestedJoin sobre las tablas de PRECIO (relacionando la columna 'Producto') y DESCUENTOS (relacionando las columnas País y Comercial).
La tabla resultante sería:
Power Query: Table.Join vs Table.NestedJoin

Podemos ver fácilmente como la tabla final resultante de Table.Join, para nuestro ejemplo, es una tabla con las columnas de las tres tablas relacionadas...
Nos devuelve datos correctos ;-)

Interesante el empleo de las funciones M:
Table.Buffer (puedes leer este post al respecto)
Table.AddKey (puedes leer este post al respecto)
Table.RenameColumns: con la que renombramos las columnas de las tablas para evitar el error o conflicto de nombres de columnas repetidos!!.
Table.Sort: función con la que devolvemos Tablas ordenadas por las columnas implicadas en la combinación. Necesario para emplear el algoritmo: .SortMerge

Si bien el uso de este algoritmo es peligroso si omitimos el tema de la ordenación...
Como vemos en el siguiente código 'fallido':
let
    Origen = Excel.CurrentWorkbook(){[Name="TblVENTAS"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Fechas", type date}, {"País", type text}, {"Comercial", type text}, {"Producto", type text}, {"Unidades", Int64.Type}}),
    
    TblPRECIO_SinOrden=Table.Buffer(
                        Table.RenameColumns(
                            Table.AddKey(
                                TblPRECIO,
                                {"Producto"},true),
                        {"Producto", "Pto"})),

    TblDCTO_SinOrden=Table.Buffer(
                        Table.RenameColumns(
                            Table.AddKey(
                                TblDESCUENTOS,
                                {"País","Comercial"},true),
                        {{"Comercial", "Salesman"},{"País","Country"}})),

    CombinadoPRECIO=Table.Join(TipoCambiado,"Producto",
                        TblPRECIO_SinOrden,"Pto",
                        JoinKind.LeftOuter,
                        JoinAlgorithm.SortMerge),

    CombinadoDCTO=Table.Join(CombinadoPRECIO,{"País","Comercial"},
                        TblDCTO_SinOrden,{"Country","Salesman"},
                        JoinKind.LeftOuter,
                        JoinAlgorithm.SortMerge)

in
    CombinadoDCTO

Muy similar, casi idéntico, al previo.. pero omitiendo el uso de Table.Sort.
La tabla resultante 'errónea':
Power Query: Table.Join vs Table.NestedJoin
Tabla final donde podemos comprobar el fallo en los distintos cruces de datos :-(

¿Cuál es la ventaja de emplear Table.Join frente a Table.NestedJoin?.
Básicamente la ventaja es que agiliza los tiempos de carga de nuestras consultas, es más rápida haciendo los cruces de datos.
Por contra exige algo más de precaución al emplearla, según el tipo de algoritmo de combinación que empleemos. Siendo necesario escribirla 'picando a mano', a diferencia de Table.NestedJoin que puede usarse desde los menús estándar.

Ambas funciones requieren pasos posteriores, bien Expandir (Table.ExpandTableColumn las columnas, bien Eliminarlas Table.RemoveColumns)

En todo caso, como siempre, tendremos que probar y comprobar cuál de las dos funciones aporta más ventajas en nuestras consultas... ya que por desgracia, no hay dos situaciones iguales, ni dos comportamientos iguales...
Así es Power Query :O

miércoles, 11 de agosto de 2021

Power Query: Claves Primarias Table.AddKey

Hablaremos hoy de un aspecto interesante cuando trabajamos con grandes bases de datos: las claves primarias.

Si estás habituado a trabajar con sistemas SQL (en realidad con todo tipo de gestores de base de datos) sabrás de la importancia que tiene definir Claves primarias asociadas a las tablas de trabajo.
Notemos que las exigencias necesarias en otros sistemas (indexación campos, integridad, ...) no aplican a nuestro editor de power Query.
En general definir Columnas como 'clave primaria' no aportará nada especial directamente a nuestras consultas, si bien (aquí viene la ventaja) cuando necesitemos combinar o relacionar información entre distintas tablas o fuentes, el haber definido estas claves agilizará y dará rapidez al cruce de datos!!!

Ventajas relevantes cuando necesitemos combinar bases de datos con grandes volúmenes de datos...

Dentro del lenguaje M de Power Query nos encontramos con tres funciones que gestionan este tipo de claves:
Table.Keys(table as table) as list
Esta función identifica y nos retorna una lista con las claves primarias existentes en la tabla indicada.

Table.AddKey(table as table, columns as list, isPrimary as logical) as table
función que agrega una clave a nuestra tabla, teniendo en cuenta una lista de columnas (una o varias) que identifican la clave primaria. El último argumento lógico true/false confirma si queremos que esa lista sea nuestra clave primaria.

Table.ReplaceKeys(table as table, keys as list) as table
Reemplaza las claves primarias existentes por la nueva lista de columnas informadas en forma de Record!!...

Otra función interesante y relacionada con las claves primarias es la función: Table.Group(table as table, key as any, aggregatedColumns as list, optional groupKind as nullable number, optional comparer as nullable function) as table
ya que en el mismo momento que realicemos la agrupación, sobre las columnas indicadas en el segundo argumento, se está definiendo para esa tabla que dichas columnas componen la 'clave primaria'!!.

Veamos algunos ejemplos de estas funciones....
Supongamos tenemos las siguientes tres tablas (TblVENTAS,TblPRECIOS y TblDESCUENTOS), las cuales hemos cargado a nuestro editor de Power Query:
Power Query: Claves Primarias Table.AddKey

En primer lugar, con la TblVENTAS cargada, procederemos a realizar una agrupación por las columnas 'País' y 'Comercial', y sumando (por ejemplo) las 'Unidades'.
El código M de esta agrupación (cuya tabla resultante llamaremos 'TblVENTAS_Agrupado') podría ser:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblVENTAS"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Fechas", type datetime}, {"País", type text}, {"Comercial", type text}, {"Producto", type text}, {"Unidades", Int64.Type}}),

    //Agrupación por las colummnas País y Comercial...
    FilasAgrupadas = Table.Group(TipoCambiado, {"País", "Comercial"}, {{"Suma_Uds", each List.Sum([Unidades]), type nullable number}})
in
    FilasAgrupadas

Esto no es relevante en el día de hoy.
Lo interesante es que con la función Table.Keys comprobaremos y obtendremos un lista de las claves primarias que se han generado al mismo tiempo que la agrupación.

Crearemos, dentro del editor de Power Query, la siguiente función personalizada (que llamaré 'fxCheckKeys'), desde una Consulta en blanco, basada en Table.Keys, que nos permitirá recuperar esa info:
(Tabla as table)=>
let
    Origen = Table.Keys(Tabla),
    RecuperamosRegistro= Table.FromRecords(Origen),
    ExpandeColumna= Table.ExpandListColumn(RecuperamosRegistro, "Columns")
in
    ExpandeColumna

Busco solo obtener una tabla que muestre la/s clave/s existentes...

Si invocamos directamente a nuestra función 'fxCheckKeys' indicando nuestra tabla agrupada:
Power Query: Claves Primarias Table.AddKey

La función nos muestra la siguiente tabla con las claves primarias definidas a partir de la agrupación:
Power Query: Claves Primarias Table.AddKey


Emplearemos ahora la función Table.AddKeys para añadir una clave primaria sobre la 'TblPRECIOS'.
Así pues accederemos desde el editor avanzado en la consulta de 'TblPRECIO' donde añadiremos la siguiente línea:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblPRECIO"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Producto", type text}, {"Precio", type number}}),

    //Añadimos una clave primaria única sobre el campo 'Producto'
    Add_Key=Table.AddKey(TipoCambiado,{"Producto"},true)
in
    Add_Key

Igual que antes podemos invocar a nuestra función personalizada 'fxCheckKeys' y veremos:
Power Query: Claves Primarias Table.AddKey


Y como último ejercicio aplicaremos la función Table.ReplaceKeys sobre la TblVTAS_Agrupado (donde habíamos comprobado esa doble clave: País, Comercial), reemplazando esa clave doble por una sencilla sobre el 'País'.
Por tanto, accederemos a la query 'TblVENTAS_Agrupado' y añadiremos:
let
    Origen = Excel.CurrentWorkbook(){[Name="TblVENTAS"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Fechas", type datetime}, {"País", type text}, {"Comercial", type text}, {"Producto", type text}, {"Unidades", Int64.Type}}),

    //Agrupación por las colummnas País y Comercial...
    FilasAgrupadas = Table.Group(TipoCambiado, {"País", "Comercial"}, {{"Suma_Uds", each List.Sum([Unidades]), type nullable number}}),

    //Reemplazamos las claves existentes por una nueva, que incluye solo el País
    //OJO!!, en este caso debemos informar la clave en forma de Record!!
    CambioKeys=Table.ReplaceKeys(FilasAgrupadas, {[Columns="País", Primary=true]})
in
    CambioKeys

Verifica con la función 'fxCheckKeys' que ahora, para esta tabla agrupada, la clave primaria es solo la columna 'País' ;-)

Una última curiodidad... si queremos reemplazar la clave primaria por un conjunto de columnas, el Record tendría esta forma:
[Columns={"País","Producto"}, Primary=true]

Podemos ver en el siguiente código, similar al anterior, cómo funciona el reemplazamiento de 'claves primarias':
let
    Origen = Excel.CurrentWorkbook(){[Name="TblVENTAS"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"Fechas", type datetime}, {"País", type text}, {"Comercial", type text}, {"Producto", type text}, {"Unidades", Int64.Type}}),

    //Agrupación por las colummnas País y Comercial...
    FilasAgrupadas = Table.Group(TipoCambiado, {"País", "Comercial","Producto"}, {{"Suma_Uds", each List.Sum([Unidades]), type nullable number}}),

    //Reemplazamos las claves existentes por una nueva, que incluye solo el País
    //OJO!!, en este caso debemos informar la clave en forma de Record!!
    //en caso de reemplazar por varias claves el REcord tendría esta forma:
    CambioKeys=Table.ReplaceKeys(FilasAgrupadas, {[Columns={"País","Producto"}, Primary=true]})
in
    CambioKeys


Veremos en siguientes entregas la ventaja de definir estas claves primarias en Power Query.

viernes, 6 de agosto de 2021

Power Query: Última Fecha Condicionada

Me planteaba un usuario una duda respecto a la forma de obtener sobre un listado de fechas la última fecha... Por añadidura explicaré la forma de recuperar de forma condicionada la última fecha de cada mes, tal como se ve en la imagen siguiente:
Power Query: Última Fecha Condicionada

Así pues cargaremos nuestra tabla desde la ficha Datos > grupo Obtener y transformar > Desde Tabla o rango, y desde el editor de consultas de Power Query, seleccionamos nuestra tabla recien cargada y presionaremos Editor avanzado, donde implementaremos el siguiente código M:
let
    Origen = Excel.CurrentWorkbook(){[Name="Tabla1"]}[Content],
    //recuperamos la columna d fechas en modo de lista
    //con el fin de aplicarle la función List.Max que nos retorna la fecha más alta...
    MaxFecha = List.Max(Origen[fechas]),
    
    //Agregamos una nueva columna, donde condicionamos el valor a añadir (la fecha calculada anterior)
    //en el caso que coincida con la fecha de la columna original 'fechas'
    AddColMaxFecha=Table.AddColumn(Origen,"Fecha máxima", each if [fechas]=MaxFecha then MaxFecha else null)
in
    AddColMaxFecha

Power Query: Última Fecha Condicionada

De especial tenemos la necesidad de obtener en forma de lista las fechas de nuestro origen, para posteriormente poder aplicar la función M List.Max

Un cálculo algo más elaborado consistiría en obtener la última fecha para cada mes!!.
Para ello generamos una consulta en blanco donde escribiremos el siguiente código:
let
    //cargamos la tabla origen y modificamos el tipo de datos de las dos columnas...
    Origen = Excel.CurrentWorkbook(){[Name="Tabla1"]}[Content],
    TipoCambiado = Table.TransformColumnTypes(Origen,{{"fechas", type date}, {"eur", Int64.Type}}),
    
    //añadimos una nueva columna con el nombre del mes
    //es importante ya que nos basaremos en este campo para aplicar el criterio posterior de agrupación
    Mes=Table.AddColumn(TipoCambiado,"Mes", each Date.MonthName([fechas])),
    //OJO!!!, si hubiera diferentes años en las fechas de la tabla original tendríamos que añadir el año
    
    //Generamos una agrupación por cada Mes (en letras) obtenido en el paso anterior
    //donde aplicamos el cálculo de Máximo para el campo de 'fechas'
    //además, aprovechamos para definir el tipo de datos devuelto!!!
    AgrupoPorMes=Table.Group(Mes, 
                            {"Mes"}, 
                            {{"MaxFecha", each List.Max([fechas]), type nullable date}, {"DetalleFechas", each _, type table [fechas=nullable date, eur=nullable number, Mes=text]}}),
    //Expandimos y extraemos las columnas...
    Expande_DetalleFechas = Table.ExpandTableColumn(AgrupoPorMes, "DetalleFechas", {"fechas", "eur"}, {"fechas", "eur"}),
    
    //Agregamos una nueva columna que compare la fecha máxima para cada mes obtenida en el paso previo
    //con la fecha original
    Condicional = Table.AddColumn(Expande_DetalleFechas, "Ult_Fecha", each if [MaxFecha] = [fechas] then [MaxFecha] else null),
    
    //quitamos columnas intermedias...
    ColumnasQuitadas = Table.RemoveColumns(Condicional,{"Mes", "MaxFecha"})
in
    ColumnasQuitadas

Power Query: Última Fecha Condicionada


Me parece interesante resaltar la parte de la agrupación por el mes, lo que nos proporciona una 'subtabla' sobre la que poder aplicar la función List.Max sobre los registros con fecha del mes...
Power Query: Última Fecha Condicionada

Fechas que el paso siguiente empleamos para comparar...
Power Query: Última Fecha Condicionada


Un interesante y sobre todo práctico ejercicio con Power Query.