martes, 30 de marzo de 2021

Función CUBO sobre rango de criterios

La semana pasada publiqué un post donde resolvía un problema interesante con la función SUMAPRODUCTO, en aquel ejercicio veíamos como replicar el comportamiento de las funciones BD trabajando sobre un rango de criterios variables!!.
Hoy veremos cómo aplicando las funciones CUBO y expresiones MDX podremos repetir el ejercicio.

En particular necesitaremos emplear la función MDX Union (vista en el post previo), en alguna de sus formas alternativas...
Además nos aprovecharemos de una de las nuevas funciones (disponibles para Microsoft 365) liberadas hace poco tiempo:
MATRIZATEXTO(matriz; [formato])
función que devuelve una matriz de valores de texto de cualquier rango especificado.
Pasa los valores de texto sin modificarlos y convierte los valores no textuales en texto.

Partiremos de nuestra base de datos ('tabla1') cargada en el Modelo de datos (Power Pivot); con unas medidas creadas en dicho Modelo:
Sum_Ventas:=SUM([Ventas])
Cuenta_Ventas:=COUNTA([Ventas])

Revisa los artículos previos a este, donde se detallan los pasos y acciones seguidas.
Función CUBO sobre rango de criterios


En la imagen vemos todos los cálculos realizados con la función SUMAPRODUCTO del post anterior; y además el desarrollo realizado con las funciones CUBO.
OJO!!. Actualización: Para mejorar la fórmula y evitar duplicidades con criterios no excluyentes, he cambiado la referencia de .Children por .[All]

En primer lugar en la celda P5, con la función CONJUNTOCUBO deberemos escribir cuál es el conjunto requerido...
Si expresaramos literalmente el ejemplo de la imagen, esto es, que sea:
registros que coincidan simultáneamente con el 'Comercial = ANA' y además 'País= ES'
más aquellos registros con el 'Comercial = EVA' y 'Producto = P4'
y más aquellos registros con el 'Comercial = EVA' y 'Producto = P2'

Si tuvieramos que desarrollar la expresión en MDX podría tener esta forma:
"{[Tabla1].[Comercial].[ANA]}*{[Tabla1].[País].[ES]}*{[Tabla1].[Producto].[All]},
{[Tabla1].[Comercial].[EVA]}*{[Tabla1].[País].[All]}*{[Tabla1].[Producto].[P4]},
{[Tabla1].[Comercial].[EVA]}*{[Tabla1].[País].[All]}*{[Tabla1].[Producto].[P2]}"
forma alternativa de uso de la función MDX Union.
El problema es que tal cual no sería viable por que excederíamos el límite de 255 caracteres en un argumento de función!!! :O
Por lo que podríamos mejorar un poco la expresión hasta:
"{{{[Tabla1].[Comercial].[ANA]}*{[Tabla1].[País].[ES]}*{[Tabla1].[Producto].[All]}},
{{[Tabla1].[Comercial].[EVA]}*{[Tabla1].[País].[All]}*{{[Tabla1].[Producto].[P2]},{[Tabla1].[Producto].[P4]}}}}"
más concreta y reducida para este caso en particular.

Claro está, si queremos que el rango de criterios sea variable, nuestra expresión de conjunto en MDX, no puede ser tan rígida como la anteior, válida solo para esa distribución inicial... por lo que deberemos buscar una forma para retornar de forma dinámica una expresión de conjunto en MDX.
Nota que la función Union, en esta forma, requiere el asterisco (* multiplicación) como operador lógico Y, y la coma (,) como operador lógico O.
Esto lo conseguiremos con la función MATRIZATEXTO
Veamos los pasos...
Función CUBO sobre rango de criterios

Comprobamos como nos devuelve una cadena de texto con estructura de matriz a partir de los valores del rango (OJO con los separadores de filas y columnas de tu sistema!!!, en mi caso la \ para columnas, y el ; para las filas)
La 'matriz' devuelva es: {"ANA"\"ES"\;"EVA"\\"P4";"EVA"\\"P2"}
tenemos claramente identificados los elementos por filas y por columnas.

Por lo que el siguiente paso consistiría en asociar a cada elemento su cadena de texto requerida para la expresión en MDX; esto es, identificando la tabla ,el campo y el valor, con sus corchetes necesarios.
Así pues, generaremos un rango de criterios 'virtual' con una función SI condicional, que cambie los elementos originales por las cadenas de textos necesarios... en nuestro ejemplo:
=SI($B$3:$D$5="";
"{[Tabla1].["&B$2:$D$2&"].[All]}";
"{"&SI($B$3:$D$5<>0;
"[Tabla1].["&B$2:D$2&"].["&$B$3:$D$5&"]";
"[Tabla1].["&B$2:$D$2&"].[All]")&"}")
Función CUBO sobre rango de criterios


Observamos que nuestro condicional se encarga de celda a celda montar las referencias en MDX necesarias, a base de concatenados de los encabezados con los elementos individuales de cada fila y /o columna.

Ahora sí, sobre esta matriz virtual, aplicamos la función MATRIZATEXTO tendríamos la siguiente cadena:
{"{[Tabla1].[Comercial].[ANA]}"\"{[Tabla1].[País].[ES]}"\"{[Tabla1].[Producto].[All]}";"{[Tabla1].[Comercial].[EVA]}"\"{[Tabla1].[País].[All]}"\"{[Tabla1].[Producto].[P4]}";"{[Tabla1].[Comercial].[EVA]}"\"{[Tabla1].[País].[All]}"\"{[Tabla1].[Producto].[P2]}"}
Función CUBO sobre rango de criterios
Estamos cerca ya...
Si echamos un vistazo a la última cadena de texto obtenida, facilmente se observa la similitud con la expresión final buscada... solo hay que sustituir las \ por *, y los ; por , (insisto aquí, OJO con los separadores de tu sistema!!).
Por tanto, el último paso consistiría en reemplazar o SUSTITUIR dichos caracteres:
=SUSTITUIR(SUSTITUIR(SUSTITUIR(B13;"\";"*");";";",");"""";"")
Función CUBO sobre rango de criterios

Listo!!, objetivo conseguido!!. Tenemos formulado, a partir del rango de criterios, un generador dinámico de expresiones MDX para crear un 'Conjunto'.

Con todo esto ya podemos volver a nuestra hoja de cálculo, e insertar en P5 la fórmula:
=CONJUNTOCUBO("ThisWorkbookDataModel";
SUSTITUIR(SUSTITUIR(SUSTITUIR(
MATRIZATEXTO(
SI($L$2:$N$4="";
"{[Tabla1].["&L$1:$N$1&"].[All]}";
"{"&SI($L$2:$N$4<>0;
"[Tabla1].["&L$1:N$1&"].["&$L$2:$N$4&"]";
"[Tabla1].["&L$1:$N$1&"].[All]")&"}");1);"\";"*");";";",");"""";"");"Criterios")
Función CUBO sobre rango de criterios

Con el conjunto creado, podemos hacer uso de él en las celdas P6 y P7, con la función VALORCUBO y llamando a las medidas de nuestro Modelo de datos:
En P6 tendríamos el dato de cuántos registros verifican o existen en el conjunto creado:
=VALORCUBO("ThisWorkbookDataModel";"[Measures].[Cuenta_Ventas]";P5)
Mientras que en P7 calcularíamos los acumulados de las ventas de dicho conjunto:
=VALORCUBO("ThisWorkbookDataModel";"[Measures].[Sum_Ventas]";P5)

podemos comparar rapidamente los resultados con los datos obtenidos con las fórmulas SUMAPRODUCTO, SUMAR.SI.CONJUNTO o BDSUMA/BDCONTARA... Fantástico resultado!!.
De hecho incluso más dinámico que con las funciones clásicas (SUMAPRODUCTO o SUMAR.SI.CONJUNTO!!)

Pero aún nos queda desplegar el cálculo para el periodo de estudio, entre los años 2013 y 2019.
Así en la celda P8 creamos un nuevo conjunto:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"[Calendar].[Date Hierarchy].&[2013]:[Calendar].[Date Hierarchy].&[2019]";
"Años-13_19")
Expresión que delimita el intervalo entre un dato y otro (usando los dos puntos como operador).
Función CUBO sobre rango de criterios

A partir de este último conjunto, desplegamos los elementos en O10 con la fórmula:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";P8;SECUENCIA(RECUENTOCONJUNTOCUBO(P8)))
Función CUBO sobre rango de criterios


Para acabar finalmente nuestro ejercicio, incluyendo en P10 la fórmula:
=VALORCUBO("ThisWorkbookDataModel";"[Measures].[Sum_Ventas]";$P$5;O10)
y arrastrar hasta P16.

Nuevamente, las funciones CUBO, combinada en este caso con la función MATRIZATEXTO, nos ofrece una posibilidad de cálculo sobre bases de datos cargadas en nuestros Modelos de datos increibles... más allá de simples tablas dinámicas.

jueves, 25 de marzo de 2021

Más de MDX y funciones CUBO

Como continuación de un artículo previo donde hablábamos del lenguaje MDX en las funciones CUBO, hoy expondré algunas otras funciones en MDX útiles en nuestro día a día.
Más de MDX y funciones CUBO

Estas funciones son: Tail, Subset y Union
Funciones MDX interesantes que conviene tener presente...

Empezaremos con TAIL(Expresión_de_Conjunto [ ,Num_elementos ] )
Esta función nos devuelve un subconjunto de elementos, los indicados en el argumento 'Num_Elementos' (o uno si se omite), de entre todos los elementos del conjunto marcada en el primero argumento... pero comenzando a contar desde el último!!.

Por ejemplo, de la base de datos trabajada y cargada en el Modelo de datos vista en artículos previos (ver aquí), y con los siguientes datos acumulados, que a modo de demostración vemos en la siguiente tabla dinámica, podríamos obtener los tres últimos paises con mayores ventas.
En este caso combinamos la función en MDX TAIL con otra ya vista ORDER.
Así en R7 definimos nuestro subconjunto:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"Tail (Order
([Tabla1].[País].[All].Children, [Measures].[Sum_Ventas], DESC )
,3 )";"Tail_Top3")
Para luego en R8 obtener el listado de miembros de dicho subconjunto:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";$R$7;SECUENCIA(RECUENTOCONJUNTOCUBO($R$7)))
Más de MDX y funciones CUBO

Comprobamos como efectivamente, el subconjunto obtenido, responde a los tres últimos paises de acuerdo a las ventas acumuladas obtenidas...
Es similar a la función BottomCount(Expresión_de_Conjunto, Cuenta [,Numeric_Expression])
, pero Tail recupera el subconjunto según el orden indicado, mientras que BottomCount lo devuelve en sentido inverso, i.e., el primer elemento sería realmente el último, el segundo el penúltimo, y sucesivamente.
Un ejemplo en W7 escribimos:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"BottomCount([Tabla1].[País].Children,3,([measures].[Sum_Ventas]))";
"Bottom3")
Lo vemos en la imagen siguiente:
Más de MDX y funciones CUBO


La siguiente función MDX importante es: Subset(Expresión_de_Conjunto, Elemento_Inicial [ ,Num_Elem_Extraer ] )
Función interesante que funciona de manera similar a EXTRAE en la hoja de cálculo... donde a partir de un conjunto indicado, recuperamos los elementos desde una posición inicial (OJO!! elementos indizados en base cero!!) extraemos un número de elementos dados.
Un ejemplo en R15:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"Subset (
Order ([Tabla1].[País].[All].Children, [Measures].[Sum_Ventas], DESC )
,0 ,3 )";"SubSet-3")
donde obtenemos los tres primeros elementos: posiciones 0,1 y 2
o también en W15:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"Subset (
Order ([Tabla1].[País].[All].Children, [Measures].[Sum_Ventas], DESC )
,1 ,2 )";"SubSet-2-3")
donde recuperamos las posiciones 1 y 2, esto es, el segundo y tercero...
Más de MDX y funciones CUBO

Acabaremos hoy con la función MDX Union(Expresión_de_Conjunto1, Expresión_de_Conjunto2 [,...n][, ALL])
esta función devuelve un nuevo conjunto resultante de la unión/anexado de diferentes conjuntos.
Interesante conocer que hay formas alternativas de obtener el mismo resultado a la función Union:
{Expresión_de_Conjunto1} + {Expresión_de_Conjunto2} [+...{Expresión_de_Conjunton}] o alternativamente
{Expresión_de_Conjunto1 , Expresión_de_Conjunto2 [,...Expresión_de_Conjunton]}

OJO!! con las llaves, y dónde se colocan en estas expresiones!!!.

En los siguientes ejemplos vemos el resultado obtenido:
En R25 escribimos:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"Union( [Tabla1].[Año].&[2014],[Tabla1].[Año].&[2016] )";
"Union 2014-2016")
O en T25:
=CONJUNTOCUBO("ThisWorkbookDataModel";
" {[Tabla1].[Año].&[2014]} +{ [Tabla1].[Año].&[2016]} ";
"Union 2014-2016")
O en U25:
=CONJUNTOCUBO("ThisWorkbookDataModel";
" {[Tabla1].[Año].&[2014], [Tabla1].[Año].&[2016]} ";
"Union 2014-2016")
comprobamos en todos los casos en R29, T29 y U29:
=VALORCUBO("ThisWorkbookDataModel";"[Measures].[Sum_Ventas]";R25)
que el resultado coincide con el desplegado en la tabla dinámica (a efectos de verificación!).
Más de MDX y funciones CUBO

martes, 23 de marzo de 2021

SUMAPRODUCTO sobre rango de criterios

Hace unas semanas me dió por pensar si era posible replicar el comportamiento de las funciones de BASE DE DATOS (funciones BD) con otras funciones como SUMAPRODUCTO y este fue el resultado.
SUMAPRODUCTO sobre rango de criterios

La idea es que a partir de la base de datos/tabla llamada 'TblVTAS' y de un rango con criterios múltiples, donde juegan los operadores lógicos Y-O, poder obtener un dato acumulado de aquellos registros que verifiquen dichos criterios...

Si aplicamos una función BDSUMA o BDCONTARA, en las celdas L7 y L8:
=BDCONTARA(TblVTAS[#Todo];"Comercial";$K$1:$M$4)
=BDSUMA(TblVTAS[#Todo];"Comercial";$K$1:$M$4)
SUMAPRODUCTO sobre rango de criterios

Comprobamos que el resultado es perfectamente válido.
Pero, ¿y si queremos obtener el dato para una lista de años?, esto es, ¿si queremos que además de cumplir dichos criterios tengamos una nueva variable por el año?... En ese caso las funciones BD no nos pueden ayudar :O

Y aquí entran en juego funciones como SUMAPRODUCTO con un uso especial...
En primer lugar replicaremos el cálculo exacto de los criterios del rango.
Así en M11 escribiremos:
=SUMAPRODUCTO(TblVTAS[Ventas]*
(SI(TRANSPONER($K$2:$K$4)="";1;(TblVTAS[Comercial]=TRANSPONER($K$2:$K$4)))
*SI(TRANSPONER($L$2:$L$4)="";1;(TblVTAS[País]=TRANSPONER($L$2:$L$4)))
*SI(TRANSPONER($M$2:$M$4)="";1;(TblVTAS[Producto]=TRANSPONER($M$2:$M$4)))))


Con la primera alternativa empleando SUMAPRODUCTO conseguimos obtener tres vectores de VERDADEROS o FALSOS (1 y 0) que verificarían los criterios (uno por cada fila del 'Rango de criterios'), para finalmente multiplicarlos por el vector del campo de las 'Ventas'.
SUMAPRODUCTO sobre rango de criterios
Muy importante la acción de TRANSPONER para que la operación se realice adecuadamente!!.

Claro está que llegamos al mismo resultado visto en la celda L8 empleando BDSUMA... todo un logro ;-)

El paso siguiente consiste en incorporar un nuevo criterio de 'Año' a la ecuación.
Recordemos, se deben cumplir las condiciones del 'Rango de criterios', y además que coincida con cada año de la lista dada:
SUMAPRODUCTO sobre rango de criterios

Comprobamos, al ver la fórmula en M15:M21, que la esencia es la descrita previamente en la celda M11.
Escribimos en M15 y luego arrastramos:
=SUMAPRODUCTO(TblVTAS[Ventas]*(TblVTAS[Año]=$K15)*
(SI(TRANSPONER($K$2:$K$4)="";1;(TblVTAS[Comercial]=TRANSPONER($K$2:$K$4)))
*SI(TRANSPONER($L$2:$L$4)="";1;(TblVTAS[País]=TRANSPONER($L$2:$L$4)))
*SI(TRANSPONER($M$2:$M$4)="";1;(TblVTAS[Producto]=TRANSPONER($M$2:$M$4)))))


La diferencia es que incluimos en el producto de criterios de SUMAPRODUCTO el campo 'Año'...
Meta alcanzada!!. Sobre los criterios de nuestro rango hemos conseguido adicionar uno nuevo sobre los años de nuestra lista!!.

Una fórmula alternativa, más fiable y segura, especialmente para aquellos criterios que generan subconjuntos de registros coincidentes, sería:
=SUMAPRODUCTO(TblVTAS[Ventas]*(TblVTAS[Año]=$K15)*
(MMULT((SI(TRANSPONER($K$2:$K$4)="";1;(TblVTAS[Comercial]=TRANSPONER($K$2:$K$4)))
*SI(TRANSPONER($L$2:$L$4)="";1;(TblVTAS[País]=TRANSPONER($L$2:$L$4)))
*SI(TRANSPONER($M$2:$M$4)="";1;(TblVTAS[Producto]=TRANSPONER($M$2:$M$4))));SECUENCIA(COLUMNAS((SI(TRANSPONER($K$2:$K$4)="";1;(TblVTAS[Comercial]=TRANSPONER($K$2:$K$4)))
*SI(TRANSPONER($L$2:$L$4)="";1;(TblVTAS[País]=TRANSPONER($L$2:$L$4)))
*SI(TRANSPONER($M$2:$M$4)="";1;(TblVTAS[Producto]=TRANSPONER($M$2:$M$4)))));1;1;0))>0))


Lo importante de esta última fórmula es el uso de MMULT para obtener los parciales por fila de la matriz de condiciones:
=MMULT(matriz;SECUENCIA(COLUMNAS(matriz);1;1;0))
nos permite saber sencillamente, evitando duplicidades de cálculo, si fila por fila cumple una o varias de las condiciones/criterios dados...

Agradecer a Tim Heng y Liam Bastick de sumproduct.com las ideas aportadas.

jueves, 18 de marzo de 2021

Funciones CUBO y MDX.

Seguiremos a partir del post previo donde profundizábamos en algunas funciones MDX dentro de nuestras funciones CUBO.
Hoy iremos un paso más allá y aplicaremos expresiones MDX en estas fantásticas funciones CUBO.
Veremos expresiones como FILTER, TOPCOUNT, EXISTS o EXCEPT, expresiones básicas o fundamentales para nuestro trabajo...
Funciones CUBO y MDX
Desde nuestra base de datos cargada en el artículo anterior, con las relaciones entre tablas y medidas creadas, comenzaremos nuestro ejemplo.

En un primer ejemplo aplicaremos un filtro para recuperar miembros de 'País' cuyas ventas acumuladas superen los 1.700 euros
Así en C3 contruimos nuestro conjunto con la expresión Filter:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"Filter( [Tabla1].[País].Children, [Measures].[Sum_Ventas]>1700)";
"filtro>1700")

Si mostramos los miembros de nuesrto conjunto en C4:
/ =MIEMBRORANGOCUBO("ThisWorkbookDataModel";$C$3;SECUENCIA(RECUENTOCONJUNTOCUBO($C$3)))
veremos lo esperado, solo aquellos paises cuyas ventas sumen más de 1.700; i.e., como si en una tabla dinámica aplicáramos un filtro de valor sobre las ventas...

Incorporamos el valor de las ventas acumuladas con VALORCUBO en D$ y siguientes:
=VALORCUBO("ThisWorkbookDataModel";$E$15;$C4)
Si subimos el nivel de ventas a 3.700 comprobamos el efecto:
Funciones CUBO y MDX


La sintaxis de Filter:
Filter(Set_Expression, Logical_Expression )
Nos pide en primer lugar que identifiquemos el conjunto de datos a obtener, según se cumpla la condición lógica del segunda argumento.

Veamos otra expresión interesante: TopCount.
Queremos obtener información de los dos paises con mayores ventas en el año 2025
Entonces montamos nuestro conjunto en C11:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"TopCount([Tabla1].[País].Children,2,([measures].[Sum_Ventas],[tabla1].[año].[2025]))";
"Top2_2025")
Funciones CUBO y MDX

Por otra parte sacamos un MIEMBROGRUPO para la medida de la Suma de ventas (por variar... jeje). En D10 añadimos:
=MIEMBROCUBO("ThisWorkbookDataModel";"[Measures].[Sum_Ventas]")
Y para listar los dos elementos obtenidos del conjunto montado con TopCount en C12 añadimos:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";C11;SECUENCIA(2))

Mientras que recuperamos las ventas acumuladas correspondientes con VALORCUBO en D12:
=VALORCUBO("ThisWorkbookDataModel";$D$10;$C12;"[Tabla1].[Año].&[2025]")

La sintaxis de TopCount:
TopCount(Set_Expression,Count [ ,Numeric_Expression ] )
Donde a partir del conjunto indicado, indicamos cuántos miembros por 'arriba' queremos extraer, según la expresión numérica (simple o compuesta) deseada.
Sobre nuestra expresión anterior:
TopCount([Tabla1].[País].Children,2,([measures].[Sum_Ventas],[tabla1].[año].[2025]))
indicamos que deseamos trabajar con los 'hijos' de País, con los dos más altos, que respondan al cruce de datos del año 2025 en cuanto a su suma de ventas....

La siguiente expresión es Exists, cuya sintáxis sería:
Exists( Set_Expression1 , Set_Expression2 [, MeasureGroupName] )
donde se definen un primer conjunto de miembros que exista en el segundo de ellos...
Veamos como ejemplo cómo obtener el listado de paises que existan en el año 2025
Funciones CUBO y MDX

En la celda C17 añadimos nuestro conjunto
=CONJUNTOCUBO("ThisWorkbookDataModel";
"EXISTS([Tabla1].[País].Children, [Tabla1].[Año].["&B17&"])";
"pais-"&B17)
En C19 contamos el número de elementos del conjunto:
=RECUENTOCONJUNTOCUBO(C17)
Y en D17 y siguiente listamos dichos elementos:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";$C$17;SECUENCIA($C$19))
y finalmente recuperamos las ventas correspondientes en E17:E19
=VALORCUBO("ThisWorkbookDataModel";$E$15;D17;"[Tabla1].[Año].&["&$B$17&"]")

Otra expresión que conviene analizar es Except... Cuya sintaxis nos indica:
Except(Set_Expression1, Set_Expression2 [, ALL ] )
donde de la primera expresión o conjunto excluimos los del segundo... El tercer argumento ALL mantiene posibles duplicados del primer conjunto.
Nuestro ejemplo, trabajado previamente de manera similar en un artículo del blog previo, nos extraerá los Comerciales distintos a LUIS... en nuestro ejercicio de tres comerciales: ANA, EVA, LUIS, el conjunto será ANA + EVA
Funciones CUBO y MDX

El resto de conjuntos y valores lo puedes encontrar en el artículo previo.

Otra expresión interesante, muy similar a Exists es NonEmpty:
NONEMPTY(set_expression1 [,set_expression2])
donde obtenemos miembros del primer conjunto con datos (no vacíos) en el segundo...
La diferencia con Exists es la prevalencia de duplicados... aunque son bastantes similares salvo que usemos el tercer argumento de Exists.
Funciones CUBO y MDX

El conjunto formado en G17 es:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"NonEmpty([Tabla1].[País].Children, [Tabla1].[Año].["&B17&"])";
"pais-"&B17)
A partir del cual podemos obtener igualmente sus componentes con MIEMBRORANGOCUBO o datos de ventas con VALORCUBO.

Para concluir con esta introducción a MDX veremos un par de funciones mas: TopSum es BottomSum.
Funciones interesantes cuanto menos...
Sus sintaxis:
TopSum(Set_Expression, Value, Numeric_Expression)
La función calcula la suma de una medida (Numeric_Expresion) evaluda sobre el conjunto dado (Set_Expression) ordenado en descendente (de mayor a menor).
La función nos devolverá los elementos con los valores más altos cuyo total acumulado sea al menos el valor especificado (Value).
y
BottomSum(Set_Expression, Value, Numeric_Expression)
Esta función calcula la suma de una medida (Numeric_Expresion) evaluda sobre el conjunto dado (Set_Expression) ordenado en ascendente (de menor a mayor).
La función nos devolverá los elementos con los valores más bajos cuyo total acumulado sea al menos el valor especificado (Value).

Raro de entender, verdad?.
Veamoslo con un ejemplo.
Funciones CUBO y MDX

Empezamos montando un conjunto con TopSum en la celda J7:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"TopSum( [Tabla1].[País].Children, 9868,[Measures].[Sum_Ventas])";
"TopSum 9868")

Al extraer sus miembros en J8:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";$J$7;SECUENCIA(RECUENTOCONJUNTOCUBO($J$7)))
Y compararlos con la tabla dinámica de más abajo (ver imagen), comprobamos que los 'paises' listados corresponden a aquellos cuya suma acumulada (ordenados en sentido descendente) supera los 9.868... en este caso, los tres paises ES, FR, DE con mayor importe de ventas.

De forma similar con BottomSum en la celda N7:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"BottomSum( {[Tabla1].[País].Children}, 7453,[Measures].[Sum_Ventas])";
"BottomSum 7453")

Al extraer sus miembros en N8:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";$N$7;SECUENCIA(RECUENTOCONJUNTOCUBO($N$7)))
De forma similar, al compararlos con la tabla dinámica de más abajo (ver imagen), comprobamos que los 'paises' listados corresponden a aquellos cuya suma acumulada (ordenados en sentido ascendente) supera los 7.453... en este caso, los dos paises PT, DE con menor importe de ventas..

martes, 16 de marzo de 2021

Funciones de CUBO y el Modelo de Datos

En entradas anteriores del blog publiqué sobre el uso de las funciones CUBO, pero sin profundizar en el verdadero potencial que encierran...
Hoy nos adentraremos en el fascinante mundo de las funciones de CUBO y sus expresiones MDX (MultiDimensional eXpressions)

Correcto... lo primero que debes saber es que estas funciones CUBO atacan al Modelo de datos a través de expresiones en MDX (una especie de SQL adaptado)...
Por lo que, en efecto, otro lenguaje entra a escena además de los ya conocidos: DAX, lenguaje M, etc...

En el post de hoy mencionaré las expresiones más habituales y que nos facilitarán el trabajo de una forma insospechada!!.

Para explorar este asombroso mundo partiremos de una sencilla base de datos agregada al Modelo de datos.
Funciones de CUBO y el Modelo de Datos

Dentro del Modelo de datos, además hemos incorporado una tabla de fechas (Editor de Power Pivot > ficha Diseñar > grupo Calendarios > botón Tabla de fechas > Nueva), y que relacionaremos con la base de datos anterior: Fecas - Date
Funciones de CUBO y el Modelo de Datos

Adicionalmente crearemos en el Área de cálculo la siguiente medida (muy simple):
Sum_Ventas:=SUM([Ventas])
Funciones de CUBO y el Modelo de Datos

Listos para volver a nuestra hoja de cálculo y trabajar 'con cubo y pala' ;-)

Las dos primeras expresiones que debes conocer son: .Members y .Children.
En ambos casos listarán los miembros/elementos de la dimensión donde nos movamos... la diferencia es que:
.Members muestra TODOS los miembros (incluido el 'All') ordenados.
mientras que .Children extrae solo los 'hijos' dependientes igualmente ordenados.

Veamos un ejemplo... Crearemos un par de conjuntos, usando la función CONJUNTOCUBO en las celdas J4 y M4.
En J4 insertamos:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"[Tabla1].[País].Members";
"paises")

y en M4:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"[Tabla1].[País].Children";
"países")

Funciones de CUBO y el Modelo de Datos

Para verificar cuales osn los miembros que componen cada Conjunto creado podemos, en primer lugar, obtener un conteo de éstos, con la función RECUENTOCONJUNTOCUBO aplicado sobre nuestras celdas J4 y M4 (donde habíamos definido los conjuntos).
Y por otra parte, listar los miembros con la función MIEMBRORANGOCUBO.
En J7 podemos escribir:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";J4;SECUENCIA(RECUENTOCONJUNTOCUBO(J4)))
donde vemos que los miembros listados son: 'All', 'DE', 'ES', 'FR', 'PT'
En M7 podemos escribir:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";M4;SECUENCIA(RECUENTOCONJUNTOCUBO(M4)))
donde vemos que los miembros listados son: 'DE', 'ES', 'FR', 'PT'

Un uso estándar de este listado de miembros podría ser aplicar sobre ellos la función VALORCUBO.
En K7 podemos escribir:
=VALORCUBO("ThisWorkbookDataModel";"[Measures].[Sum_Ventas]";$J7)
donde vemos que para cada miembro, obtemos el resultado de nuestra medida 'Sum_Ventas'... obviamente para 'All' tenemos el total acumulado de 'todos' los miembros.

Otro ejemplo que podemos mostrar es cómo conseguir datos filtrados por fechas, por ejemplo.
Funciones de CUBO y el Modelo de Datos

Supongamos que queremos obtener los datos de ciertos años, entre el 2013 y 2017...
Para ello crearemos un CONJUNTO y en J16 escribiríamos:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"[Calendar].[Date Hierarchy].&[2013]:[Calendar].[Date Hierarchy].&[2017]";
"Fechas-13-17")

Podemos averiguar el número de miembros únicos en ese intervalo de tiempo... que debes fijarte se ha expresado como:
inicio : fin (ojo que equivaldría a >=inicio y <=fin).
En K16 añadimos:
=VALORCUBO("ThisWorkbookDataModel";"[Measures].[Sum_Ventas]";J16)
que nos devuelve el acumulado de las Ventas (obtenido de nuestra 'medida' del Modelo de datos) para los miembros del CONJUNTO recién creado (6.845,18 para nuestro ejemplo).
para comprobarlo, por si aún no te fías de la seguridad de estos cálculos, podemos construir una tabla dinámica como la siguiente y aplicar los filtros correspondientes:
Funciones de CUBO y el Modelo de Datos

Otro cálculo muy interesante lo podemos realizar empleando la expresión .Parent, que devuelve el 'padre' (lo contrario de '.Children') sobre un miembro en concreto.
Por ejemplo en la celda L16 escribimos:
=VALORCUBO("ThisWorkbookDataModel";
"[Measures].[Sum_Ventas]";
"[Calendar].[Date Hierarchy].&[2010].Parent")

que nos devuelve el acumulado de ventas para TODOS los años... esto es, a pesar de indicar un miembro en concreto, una fecha en particular: 2010, al indicar que deseamos su .Parent, recuperamos la totalidad de los años (17.320,79).

Un paso más allá consistiría en combinar este conjunto de años 2013-2017 con otro conjunto de comerciales: ANA+EVA.
Así creamos un nuevo conjunto de dos miembros, en la celda J21 añadimos:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"{[Tabla1].[Comercial].&[ANA],[Tabla1].[Comercial].&[EVA]}";
"ana+eva")

fijémosnos en que forma hemos indicado uno y otro... entre llaves y separados por coma (como una matriz de elementos).
Igualmente podríamos haber escrito en un par de celdas los nombres ANA y EVA (en J24:J25). Y haber creado el siguiente conjunto referido a esas celdas... =CONJUNTOCUBO("ThisWorkbookDataModel";
"[Tabla1].[Comercial].["&$J$24:$J$25&"]";
"ana+eva")

En cualquiera de ambos casos, cruzando los miembros 'Años' de .Parent con el conjuntos en J21 de ambos comerciales (ANA + EVA) obtendríamos un resultado ajustado (10.484,80).
Como se comprueba con nuestra tabla dinámica (de momento sigo demostrando la fiablidad de esta formulación):
Funciones de CUBO y el Modelo de Datos


Terminaré el post de hoy con algunas otras funciones MDX con las que podemos trabajar dentro del contexto de nuestras funciones CUBO:
.LastChild
.FirstChild
.Lead
.Lag
.NextMember
.PrevMember
Son facilmente comprensibles...
.LastChild nos devuelve el último miembro, el último 'hijo' del conjunto.
.FirstChild nos devuelve el primer miembro, o 'hijo' del conjunto.

.NextMember nos devuelve el siguiente miembro desde el especificado.
.PrevMember nos devuelve el miembro previo o anterior del conjunto.
.Lead y .Lag :
Recuperamos la posición de un miembro dentro de un nivel.. sabiendo que la numeración está en base cero.
Si especificamos .Lead(0) la función nos devolvería el miembre específicado.
Si a la función .Lead le aplciamos un número o posición negativa devuelve el miembro anterior.
Por tanto .Lead(1) es equivalente a la función .NextMember; y .Lead(-1) equivale a .PrevMember
Las funciones .Lead y .lag son similares, excepto porque .lag va en dirección contraria que .Lead. Es decir, .Lead(n) equivale .Lag(-n).

Unos ejemplos sencillos que nos permiten ver su funcionamiento:
Funciones de CUBO y el Modelo de Datos

Las fórmulas empleadas en el rango O2:O7 son:
En O2 =MIEMBROCUBO("ThisWorkbookDataModel";"[Tabla1].[Año].LastChild")
En O3 =MIEMBROCUBO("ThisWorkbookDataModel";"[Tabla1].[Año].FirstChild")
En O4 =MIEMBROCUBO("ThisWorkbookDataModel";"[Tabla1].[Año].[2015].Lead(-1)")
En O5 =MIEMBROCUBO("ThisWorkbookDataModel";"[Tabla1].[Año].[2015].Lag(-1)")
En O6 =MIEMBROCUBO("ThisWorkbookDataModel";"[Tabla1].[Año].[2015].NextMember")
En O7 =MIEMBROCUBO("ThisWorkbookDataModel";"[Tabla1].[Año].[2015].PrevMember")
Funciones muy útiles para poder referirnos a distintos miembros del conjunto...
Por ejemplo, si queremos comparar un periodo con el anterior.
Funciones de CUBO y el Modelo de Datos

Veamos la distribución de nuestras fórmulas en el minireport.
En la celda N12 escribimos:
=CONJUNTOCUBO("ThisWorkbookDataModel";
"[Calendar].[Date Hierarchy].&[2013]:[Calendar].[Date Hierarchy].&[2017]";
"Fechas-13-17")

que estará compuesto, como ya vimos por los miembros entre los ejercicios 2013 y 2017.
En N18 listamoms dichos miembros de nuestro conjunto con la fórmula:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";
$N$12;
SECUENCIA(RECUENTOCONJUNTOCUBO($N$12)))


Por otra parte definimos un nuevo conjunto, también visto, con los comerciales ANA y EVA, así en O11:
=CONJUNTOCUBO("ThisWorkbookDataModel"; "{[Tabla1].[Comercial].&[ANA],[Tabla1].[Comercial].&[EVA]}"; "ana+eva")
Escribimos nuestros encabezados como textos planos en O12:Q12
Actual // Y-1 // Todos comerciales

Y para retornar los importes de las ventas de cada año acumulados para ANA y EVA añadiremos en O13
=VALORCUBO("ThisWorkbookDataModel";"[Measures].[Sum_Ventas]";N13;$O$11)
función con la recuperamos los valores de nuestro cruce de cubos...

Para recuperar las ventas acumuladas del periodo previo usaremos la función MDX .Lag(1) en P13 y siguientes:
=VALORCUBO("ThisWorkbookDataModel";"[Measures].[Sum_Ventas]";"[Calendar].[Date Hierarchy].&["&$N13&"].Lag(1)";$O$11)
esto es, ventas del año previo acumulados de los dos comerciales ANA y EVA.

Finalemente, para cada año del periodo de estudio 2013-2017, obtendremos el dato de ventas acumulado de TODOS los comerciales... En Q13 añadimos:
=VALORCUBO("ThisWorkbookDataModel"; "[Measures].[Sum_Ventas]"; N13; "[Tabla1].[Comercial].&["&MIEMBRORANGOCUBO("ThisWorkbookDataModel";$O$11;1)&"].Parent")
Donde hacemos una llamada a la totalidad de los comerciales con .Parent, a partir de un elemento (del primer miembro) del conjunto de comerciales...
Podemos comprobarlo en la siguiente tabla dinámica:
Funciones de CUBO y el Modelo de Datos


Continuaré en entradas del blog siguientes ahondando en este mundo de las funciones CUBO y este lenguaje MDX.

jueves, 11 de marzo de 2021

Power Pivot: Medidas y Funciones CUBO

Como continuación del ejercicio anterior (ver aquí), concluiremos y llegaremos al mismo resultado combinando las herramientas de Power Pivot con las funciones CUBO...
Power Pivot: Medidas y Funciones CUBO

Para este ejercicio necesitamos agregar a nuestro Modelo de datos el listado de Comerciales con los que trabajar: 'TblAgencias'.
Además una vez cargado le añadiremos una sencilla Columna calculada que incorpore un valor =1
Power Pivot: Medidas y Funciones CUBO

Esto facilitará el cálculo posterior...

Y a continuación, dentro de nuestro Modelo de datos en Power Pivot, desde la vista de diagrama, crearemos la relación entre ambas tablas, usando el campo Cod comercial como nexo.
Power Pivot: Medidas y Funciones CUBO

Por último crearemos una medida nueva:
SumaVTAS:=sumx(TblDATOS;TblDATOS[Ventas]*RELATED(TblAgencias[Add_TF]))
Power Pivot: Medidas y Funciones CUBO


Listos... volvemos a nuestra hoja de cálculo para construir nuestro cuadro de reporte.
Para lo cual repetiremos los pasos vistos en el post anterior.
Para los encabezados escribimos primero en J18:
=CONJUNTOCUBO("ThisWorkbookDataModel";"[TblDATOS].[Periodo].children";"Periodos";5)
donde generamos un listado 'virtual' de elementos únicos extraidos del campo 'Periodo'.

En I19 repetimos la operación para el campo 'Tipos':
=CONJUNTOCUBO("ThisWorkbookDataModel";"[TblDATOS].[Tipo].children";"Tipos")
obteniendo un listado 'virtual/interno' de elementos únicos extraidos del campo 'Tipo'.

Con los conjuntos generados, procedemos a extraerlos con la función MIEMBRORANGOCUBO.
En J19, apoyándonos en el CONJUNTO indicado en el paso anterior (celda J18) escribimos:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";$J$18;SECUENCIA(1;12))
que retornará todos los elementos existentes (hasta 12, por ser el valor indicado con SECUENCIA) del conjunto de elementos de 'Periodo'.
De igual forma en I20 (apoyándonos en el conjunto previo de la celda I19) escribimos:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";$I$19;SECUENCIA(3))
que devolverá los elementos únicos del campo 'Tipo', cargados en el CONJUNTO descrito en la celda I8.

Nos toca definir un último conjunto, para los Clientes seleccionados...

El último conjunto en la celda I18:
=CONJUNTOCUBO("ThisWorkbookDataModel";"[TblDATOS].[Cliente].[All].["&TblGRUPO[Cliente]&"]";"Cliente")
Obteniendo el conjunto de Clientes deseado: A y B.

Con los conjuntos de criterios montados, y los miembros de los conjuntos de Tipo y Periodo desplegados, podemos recuperar valores cruzados y acumulados para todas esas condiciones... Así en J20:U22 escribimos:
=VALORCUBO("ThisWorkbookDataModel";$I20;J$19;$I$18;"[Measures].[SumaVTAS]")
Donde recuperamos el dato de nuestra medida SumaVTAS teniendo en cuenta todos los conjuntos anteriores, esto es, todos los cruces de datos anteriores y fijados en la hoja de cálculo (bien por CONJUNTOS, o bien por detalle de elementos de CONJUNTOS) en las celdas: $I20;J$19;$I$18

En este caso, la relación creada en el Modelo de datos, nos ha evitado crear un CONJUNTO en la hoja... pero como era de esperar, con igual resultado!!.

martes, 9 de marzo de 2021

Acumulado por múltiple condiciones cruzadas

Al hilo del post anterior (ver aquí) se me planteó la posibilidad de aplicar un doble/múltiple criterio sobre distintos campos.
Acumulado por múltiple condiciones cruzadas

Sobre la imagen anterior vemos nuestra 'base de datos' (TblDATOS), y en la parte superior las condciones a aplicar sobre los campos 'Cliente' y 'Cod comercial'.
Sabiendo que las posibles correspondencias entre uno y otro son:
10 A
11 A
12 A
13 B
20 B
21 B
22 C

De tal forma que el objetivo es acumular las 'Ventas' por 'Tipo' y 'Periodo' según los Clientes y Comerciales seleccionados.
En el ejemplo de la imagen hemos seleccionado los clientes A y B, y de otra parte los Comerciales 13,22 y 10... por tanto, deberemos acumular para cada 'Tipo' y 'Periodo' los siguientes registros:
10 y A
más los siguientes
13 y B
dejando fuera el Comercial 22 ya que no coincide con ningún cliente seleccionado (A y B).
De alguna manera es como si generaramos una tabla dinámica con estos filtros:
Acumulado por múltiple condiciones cruzadas

Comprobamos que el resultado de esos cruces, en total, es de 3.979.

Pero, ¿y con fórmulas?.
Lamentablemente no es posible usando la combinación matricial:
=SUMA(SUMAR.SI.CONJUNTO(....))
ya que de esta manera se acumularía con condiciones lineales ¿¿qué, cómo...perdón??. Quiero decir acumularía, para nuestro ejemplo (fíjate en cómo están listadas las condiciones):
A y 13
más
B y 22
lo que no retornaría nada!!.

Nos quedaría BDSUMA con el rango auxliar de criterios que sí admitiría nuestras condiciones!!

Pero no es esta la solución que busco hoy... ;-)
Hoy aplicaremos las funciones CUBO (puedes leer algo más aquí).
Asi que lo primero que haremos será Cargar nuestra TblDATOS al Modelo de datos (a Power Pivot).
Desde la Ficha Power Pivot > grupo Tablas > botón Agregar al Modelo de datos. Lo que nos llevará la información al editor de Power Pivot.
Ya en nuestro editor, desde al área de cálculo de la tabla, añadiremos una medida (muy sencilla) que sume el campo de unidades:
S_Ventas:=SUM(TblDATOS[Ventas])
Acumulado por múltiple condiciones cruzadas

Ya con la medida creada, lo que nos facilitará el trabajo para acumular ventas, generamos nuestra tabla de reporte siguiente:
Acumulado por múltiple condiciones cruzadas

Con las siguientes formulaciones.
Para los encabezados escribimos primero en J7:
=CONJUNTOCUBO("ThisWorkbookDataModel";"[TblDATOS].[Periodo].children";"Periodos";5)
donde generamos un listado 'virtual' de elementos únicos extraidos del campo 'Periodo'.

En I8 repetimos la operación para el campo 'Tipos':
=CONJUNTOCUBO("ThisWorkbookDataModel";"[TblDATOS].[Tipo].children";"Tipos")
obteniendo un listado 'virtual/interno' de elementos únicos extraidos del campo 'Tipo'.

Con los conjuntos generados, procedemos a extraerlos con la función MIEMBRORANGOCUBO.
En J8, apoyándonos en el CONJUNTO indicado en el paso anterior (celda J7) escribimos:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";$J$7;SECUENCIA(1;12))
que retornará todos los elementos existentes (hasta 12, por ser el valor indicado con SECUENCIA) del conjunto de elementos de 'Periodo'.
De igual forma en I9 (apoyándonos en el conjunto previo de la celda I8) escribimos:
=MIEMBRORANGOCUBO("ThisWorkbookDataModel";$I$8;SECUENCIA(3))
que devolverá los elementos únicos del campo 'Tipo', cargados en el CONJUNTO descrito en la celda I8.

Nos toca definir dos últimos conjuntos, uno para los Clientes y otro para los Comerciales...
En la celda I6 creamos el conjunto:
=CONJUNTOCUBO("ThisWorkbookDataModel";"[TblDATOS].[Cod Comercial].[All].["&TblAgencias[Cod comercial]&"]";"Comercial")
Donde montamos un conjunto con los distintos Comerciales listado en D2:D4 (la tabla de Comerciales con los que operar).

El segundo y último conjunto en la celda I7:
=CONJUNTOCUBO("ThisWorkbookDataModel";"[TblDATOS].[Cliente].[All].["&TblGRUPO[Cliente]&"]";"Cliente")
Obteniendo el conjunto de Clientes deseado: A y B.

Con los conjuntos de criterios montados, y los miembros de los conjuntos de Tipo y Periodo desplegados, podemos recuperar valores cruzados y acumulados para todas esas condiciones... Así en J9:U11 escribimos:
=VALORCUBO("ThisWorkbookDataModel";$I9;J$8;$I$7;$I$6;"[Measures].[S_Ventas]")
Donde recuperamos el dato de nuestra medida S_Ventas teniendo en cuenta todos los conjuntos anteriores, esto es, todos los cruces de datos anteriores y fijados en la hoja de cálculo (bien por CONJUNTOS, o bien por detalle de elementos de CONJUNTOS) en las celdas: ;$I9;J$8;$I$7;$I$6

El resultado es el esperado!!!. Compáralo con la tabla dinámica anterior y verificaremos que es idéntico ;-) Éxito!!

jueves, 4 de marzo de 2021

Suma Acumulada excluyente según lista

Hoy toca compartir algo más sencillo, pero igual de práctico. ;-)
Se planteaba en un grupo de Facebook, por un miembro de éste, la forma en la que obtener una suma acumulada excluyendo aquellos elementos listados en otra lista
Suma Acumulada excluyente según lista

La idea por tanto es acumular todos aquellos artículos de la TblDATOS que NO estén en la tabla de elementos 'TblFILTRADO'... lo que se consigue introduciendo en la celda G2 la fórmula:
=SUMAPRODUCTO(ESERROR(COINCIDIR(TblDATOS[Art];TblFILTRADO[filtrado];0))*(TblDATOS[Uds]))
Donde el parámetro clave es la función COINCIDIR:
COINCIDIR(TblDATOS[Art];TblFILTRADO[filtrado];0)
que para el ejemplo de la imagen devuelve una matriz:
{#N/D;1;#N/D;2;3;#N/D;#N/D;#N/D}
que identifica las posiciones de los elementos a excluir!! (que aparecen en nuestra segunda tabla TblFILTRADO).
Ya que en realidad estos son los que queremos apartar, y los que necesitamos acumular son precisamente los que han devuelto un error (esto es, los no encontrados), aplicamos la función ESERROR que transforma nuestra matriz previa en:
{VERDADERO;FALSO;VERDADERO;FALSO;FALSO;VERDADERO;VERDADERO;VERDADERO}
justo lo que requerimos para multiplicar uno-a-uno por el campo de unidades de la tabla 'TblDATOS'.
Aquí SUMAPRODUCTO hace su trabajo correctamente... operando matricialmente primero, para finalmente acumular los elementos resultantes...

De forma similar podemos obtener la suma acumulada de aquellos elementos listados; es decir, sumar solo aquellos elementos de nuestra lista...
Una primera forma, aprovechando la anterior fórmula, sería en I2:
=SUMAPRODUCTO(NO(ESERROR(COINCIDIR(TblDATOS[Art];TblFILTRADO[filtrado];0)))*(TblDATOS[Uds]))
misma explicación que para el caso anterior... pero añadiendo la función NO que invierte el sentido de la respuesta, y en consecuencia nuestra matriz de verdaderos y falsos, que ahora quedaría:
{FALSO;VERDADERO;FALSO;VERDADERO;VERDADERO;FALSO;FALSO;FALSO}
y al multiplicar por las 'unidades' de nuevo SUMAPRODUCTO devuelve el dato correcto.

Alternativamente podríamos haber aplicado otra forma clásica, en I3:
=SUMA(SUMAR.SI.CONJUNTO(TblDATOS[Uds];TblDATOS[Art];TblFILTRADO[filtrado]))
en este caso obligatoria ejecutar matricialmente (presionando Ctrl+Mayusc+Enter).
Con igual resultado...

martes, 2 de marzo de 2021

Power Query: Importar todas las hojas de un libro

Un caso que se planteaba semanas atrás trataba sobre la forma en que se podía importar todas las hojas de un mismo libro que contenía, cada una de ellas, la particularidad de tener encabezados distintos (aunque con iguales conceptos).

Así pues nuestro comienzo es un fichero origen llamado: 'PQ_Source_file.xlsx', en este caso con cuatro hojas (ES,FR, DE e IT).
Power Query: Importar todas las hojas de un libro

Todas ellas con el encabezado en la fila 6 y columnas con datos variables (de la A a la G), pero solo nos interesan las columnas A:D, cuyos conceptos son:
Columna A: Fecha
Columna B: Descripción
Columna C: Cuenta
Columna D: Importe
Con estos detalles de partida claros, comenzaremos nuestro proceso de creación de una consulta en Power Query.
En un libro accederemos a la ficha Datos > grupo Obtener y Transformar > desplegable Obtener datos > Desde un archivo > Desde un archivo; y en la ventana diálogo indicaremos la ruta de nuestro fichero fuente ('PQ_Source_file.xlsx').
Power Query: Importar todas las hojas de un libro
Normalmente en esta ventana presionamos una sola de las hojas... pero en esta ocasión seleccionaremos la 'carpetita' con el nombre del fichero, y presionaremos Transformar datos.
Lo que abrirá el editor de consultas de Power Query.
Power Query: Importar todas las hojas de un libro

De momento lo dejamos como está, y abriremos una Consulta en blanco (desde el menú del editor de consultas, vamos a la ficha Inicio > grupo Nueva consulta > desplegable Nuevo origen > Otros orígenes > Consulta en blanco).
Activaremos el editor avanzado y escribiremos:
(hoja as text) as table =>
let
    Origen = Excel.Workbook(File.Contents("F:\excelforo\PQ_Source_file.xlsx"), null, true),
    //Recuperamos la primera hoja del libro!!
    Hoja1 =  Origen{[Item=hoja,Kind="Sheet"]}[Data],

    //Eliminamos las seis filas.. incluyendo los 'posibles' encabezados!!!
    #"Filas superiores quitadas" = Table.Skip(Hoja1,6),
    
    //Nos quedamos con las cuatro primeras columnas.. eliminando cualquier otra
    #"Otras columnas quitadas" = Table.SelectColumns(#"Filas superiores quitadas",{"Column1", "Column2", "Column3", "Column4"}),

    //Renombramos columnas 
    //Recuperamos una Lista los Nombres de las columnas actuales
    NombresColsOriginales = Table.ColumnNames(#"Otras columnas quitadas"),
    //Definimos una Lista con los Futuros Nombres de las cuatro columnas
    NombresColsNuevos={"Date","Description","Account","Amount"},
    
    //Componemos una Lista 'doble' con las dos anteriores usando List.Zip
    RenombramosListado = List.Zip({NombresColsOriginales,NombresColsNuevos}),
    //Finalmente con Table.RenameColumns asignamos los nombres nuevos...
    RenombradoColumnas = Table.RenameColumns(#"Otras columnas quitadas", RenombramosListado)

in
    RenombradoColumnas

Power Query: Importar todas las hojas de un libro
Es el código de una función personalizada en lenguaje M que procesa una hoja (cuyo nombre se ha parametrizado) de nuestro libro fuente ('PQ_Source_file.xlsx');
donde además eliminamos las primeras filas de cada hoja;
eliminamos columnas 'sobrantes';
y renombramos nuestras cuatro columnas con estos cuatro nombres ("Date","Description","Account","Amount").
Esta técnica ya se explicó en un artículo anterior del blog (leer aquí).

A esta función la he llamado: 'q1stTransform'.

Ahora volveremos a nuestra primera consulta, donde veíamos todas las hojas existentes de nuestro libro fuente...
Podemos usar el asistente para quedarnos solo con la columna 'Name' que contiene el nombre de las hojas... (es lo que necesitamos para alimentar nuestra función personalizada!).
Y a continuación agregar una columna personalizada, donde alimentemos la función 'q1stTransform'
Power Query: Importar todas las hojas de un libro

Donde tendríamos una nueva columna conteniendo Tables con la info de cada hoja.
Power Query: Importar todas las hojas de un libro
Terminaríamos expandiendo los 'Datos' o alternativamente emplear la función M: Table.Combine
El código completo sería:
let
    Origen = Excel.Workbook(File.Contents("F:\excelforo\PQ_Source_file.xlsx"), null, true),
    //Nos quedamos solo con la columna Name
    #"Otras columnas quitadas" = Table.SelectColumns(Origen,{"Name"}),
    //agregamos en una columna nueva la función creada: q1stTransform
    #"Personalizada agregada" = Table.AddColumn(#"Otras columnas quitadas", "Datos", each q1stTransform([Name])),
    //y expandimos Datos
    #"Se expandió Datos" = Table.Combine(#"Personalizada agregada"[Datos])
in
    #"Se expandió Datos"

Power Query: Importar todas las hojas de un libro


Listo. Podemos comprobar el resultado...
Power Query: Importar todas las hojas de un libro

Una única tabla con la información de las cuatro columnas de todas las hojas de nuestro fichero fuente... a pesar de tener encabezados diferentes!!!.
Todo un éxito ;-)