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

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.