En este nuevo post vamos a ver las novedades que se incluyeron en el framework de consultas a partir de la versión 2012 de Microsoft Dynamics AX.
Para ello, vamos a hablar de las clases QueryHavingFilter y QueryFilter.
QUERYHAVINGFILTER
La clase QueryHavingFilter, como decíamos, ha sido añadida en Microsoft Dynamics AX 2012. Como podemos imaginar, esta clase se encargará añadir filtros dentro de la sentencia having de una select estándar.
Hasta ahora no teníamos forma de realizar este tipo de filtros, ni por sentencias SQL de X++, ni con el objeto Query creado desde el AOT, por lo que si nos encontramos ante la necesidad de utilizarlo se deberá recurrir a la creación de queries mediante este framework.
Imaginemos que necesitamos obtener un listado de los clientes que tienen más de 5 pedidos de venta en el sistema.
La select que deberíamos realizar sería algo así:
1 2 3 |
SELECT CUSTACCOUNT, COUNT(*) FROM SALESTABLE GROUP BY CUSTACCOUNT HAVING COUNT(*) > 5 |
En versiones anteriores, hubiésemos sido capaces de obtener un listado de clientes y el número de pedidos de venta de cada cliente realizando un count sobre el RecId de la tabla y un group by por la cuenta del cliente, pero no hubiésemos podido filtrar por este count.
En AX 2012, gracias a esta nueva clase, podemos obtener este mismo resultado por medio del Query Framework, y para conseguirlo tendríamos que hacer la siguiente consulta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
static void AXZQueryHaving(Args _args) { Query query; QueryBuildDataSource qbds; QueryBuildRange qbr; QueryHavingFilter qhf; QueryRun queryRun; SalesTable salesTable; query = new Query(); qbds = query.addDataSource(tableNum(SalesTable)); qbds.addSelectionField(fieldNum(SalesTable, RecId), SelectionField::Count); qbds.orderMode(OrderMode::GroupBy); qbds.addGroupByField(fieldNum(SalesTable, CustAccount)); qhf = query.addHavingFilter(qbds, fieldStr(SalesTable, RecId), AggregateFunction::Count); qhf.value(">5"); queryRun = new QueryRun(query); while (queryRun.next()) { salesTable = queryRun.get(tableNum(SalesTable)); info(strFmt("Cliente: %1 - Nº Pedidos: %2", salesTable.CustAccount, salesTable.RecId)); } } |
De este modo conseguimos filtrar por el valor que obtenemos de realizar el count(RecId).
QUERYFILTER
La clase QueryFilter, es la segunda novedad de la que vamos a hablar dentro del Query Framework en Dynamics AX 2012. Esta clase nos da la posibilidad de obtener resultados de una unión externa de tablas diferente a lo que podemos obtener mediante sentencias SQL de X++.
La clase QueryFilter se encarga de añadir filtros la cláusula where de una select estándar que utiliza el outer join entre dos tablas. La diferencia que tiene utilizar esta clase con respecto a utilizar la ya conocida QueryBuilRange es que esta segunda añade el filtro dentro de la palabra reservada on de la cláusula join. Por lo que, por medio del QueryFilter, seremos capaces de aplicar filtros una vez que ya se ha realizado el join con sus filtros.
La mejor forma de entender este concepto y la diferencia con el QueryBuilRange es por medio de ejemplos, así que vamos a ello:
Tenemos una consulta que nos da la tabla de Clientes (CustTable) y la tabla de Transacciones (CustTrans), que tienen una relación por la cuenta de cliente (CustAccount).
1 2 |
SELECT * FROM CUSTTABLE OUTER JOIN CUSTTRANS ON CUSTTRANS.CUSTACCOUNT = CUSTTABLE.ACCOUNTNUM |
Esta consulta nos devolverá un listado de todos los clientes, y para aquellos que tengan transacciones, también saldrán todas las transacciones. Por ser un outer join, como decimos, obtendremos todos los clientes, tengan o no transacciones.
Imaginemos ahora, que queremos obtener todos los clientes junto con las transacciones que hayan sido aprobadas únicamente, pero igual que antes, queremos seguir recibiendo aquellos clientes que bien no tienen transacciones o bien estas transacciones todavía no han sido aprobadas. Necesitaríamos generar una select como esta:
1 2 |
SELECT * FROM CUSTTABLE OUTER JOIN CUSTTRANS ON CUSTTRANS.CUSTACCOUNT = CUSTTABLE.ACCOUNTNUM AND CUSTTRANS.APPROVED = 1 |
Esta es la select que obtenemos al incluir el filtro dentro de un QueryBuildRange. Si en lugar de utilizar el QueryBuilRange optásemos por el QueryFilter, la select obtenida sería la siguiente:
1 2 3 |
SELECT * FROM CUSTTABLE OUTER JOIN CUSTTRANS ON CUSTTRANS.CUSTACCOUNT = CUSTTABLE.ACCOUNTNUM WHERE CUSTTRANS.APPROVED = 1 |
Y el resultado de ejecutar esta select sería distinto, ya que el where, se encarga de aplicar los filtros una vez que el outer join ha sido ejecutado con los filtros pertinentes, por lo que, una vez que tengamos el resultset que resulta de realizar la primera parte de la select, es cuando se aplica el filtro de la cláusula where, por lo que finalmente nos quedaríamos únicamente con aquellos clientes que tienen alguna transacción aprobada, y desecharíamos el resto de clientes.
Aquí tenemos un job en el que podemos ver claramente la diferencia entre el resultado obtenido al utilizar un QueryBuildRange o un QueryFilter, simplemente con ejecutarlo comentando una u otra línea podremos verlo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
static void AXZQueryFilterVSQueryBuildRange(Args _args) { Query query; QueryBuildDataSource qbdsCustTable, qbdsCustTrans; QueryRun queryRun; QueryBuildRange qbrApproved; QueryFilter qfApproved; CustTable custTable; CustTrans custTrans; query = new query(); qbdsCustTable = query.addDataSource(tableNum(CustTable)); qbdsCustTrans = qbdsCustTable.addDataSource(tableNum(CustTrans)); qbdsCustTrans.relations(true); qbdsCustTrans.joinMode(JoinMode::OuterJoin); qbdsCustTrans.addLink(fieldNum(CustTable, AccountNum), fieldNum(CustTrans, AccountNum)); // QueryBuildRange qbrApproved = qbdsCustTrans.addRange(fieldNum(CustTrans, Approved)); qbrApproved.value(queryValue(NoYes::Yes)); // QueryFilter qfApproved = query.addQueryFilter(qbdsCustTrans, fieldStr(CustTrans, Approved)); qfApproved.value(queryValue(NoYes::Yes)); queryRun = new queryRun(query); while (queryRun.next()) { custTable = queryRun.get(tableNum(CustTable)); custTrans = queryRun.get(tableNum(CustTrans)); info (strFmt("Cliente: %1, Asiento: %2", custTable.AccountNum, custTrans.Voucher)); } info (strFmt("Total: %1 registros", SysQuery::countLoops(queryRun))); } |