AX Dev Warehouse

Um blog dedicado ao desenvolvimento de soluções em Microsoft Dynamics AX
posts - 41, comments - 18, trackbacks - 0

Acessando dados via X++

Uma das grandes vantagens do X++ como linguagem para um ERP são suas funcionalidades de acesso a dados integradas a própria linguagem. Isto simplifica bastante o acesso à camada de dados. Mesmo assim existem muitas formas de alcançar os mesmos objetivos, umas mais adequadas que outras. Neste post vou analisar algumas destas alternativas.

A estrutura mais utilizada de acesso aos dados de uma tabela é o while select. N exemplo abaixo seleciono e processo todos os clientes, contidos na tabela CustTable, que pertencem ao grupo “INTER-PR”.

    CustTable customer;
    ;

    while select customer
        where customer.CustGroup == "INTER-PR"
    {
        print strfmt("Id: %1 - Name: %2", customer.AccountNum, customer.Name);
    }

    pause;

 

Estendendo este código posso trazer as vendas abertas para estes clientes. O exemplo abaixo mostra isto, porém de uma forma pouco performática. Para cada cliente você faz um novo select na tabela de pedidos. Para uma base de 500 clientes, são 501 acessos ao banco de dados.

    CustTable customer;
    SalesTable sales;
    ;

    while select customer
        where customer.CustGroup == "INTER-PR"
    {
        print strfmt("Id: %1 - Name: %2", customer.AccountNum, customer.Name);

        while select sales
            where sales.CustAccount == customer.AccountNum
        {

            print strfmt("  Sales: %1 - amount to be invoiced: %2", sales.SalesId, sales.amountOrderedNotInvoiced());
        }
    }

    pause;

 

O código abaixo mostra uma solução usando um join entre as duas tabelas. O resultado é o mesmo, mas o acesso ao banco de dados é feito de uma vez. O que torna a solução mais performática.

    CustTable customer;
    SalesTable sales;
    CustAccount currentCustomer;
    ;

    while select customer
        where customer.CustGroup == "INTER-PR"
        join sales
            where customer.AccountNum == sales.CustAccount
    {
        if (customer.AccountNum != currentCustomer)
        {
            print strfmt("Id: %1 - Name: %2", customer.AccountNum, customer.Name);
            currentCustomer = customer.AccountNum;
        }

        print strfmt("  Sales: %1 - amount to be invoiced: %2", sales.SalesId, sales.amountOrderedNotInvoiced());
    }

    pause;

 

Outro problema muito comum é listar todos os clientes que têm ordens de venda em aberto. Uma solução que pode parecer razoável é esta abaixo. Para cada cliente verifico se existe uma ordem aberta para ele.

    CustTable customer;
    ;

    print "Clients with open orders:";
    while select customer
    {
        if (SalesTable::existCustOpenOrder(customer.AccountNum))
        {
            print strfmt("  Id: %1 - Name: %2", customer.AccountNum, customer.Name);
        }
    }

    pause;

 

Existem dois pontos fracos nesta solução: O primeiro select traz todos os clientes, sem filtrar nada. Depois disto, para cada cliente, um select é realizado na tabela de ordens de venda. Novamente um join é o mais adequado para este caso. Porém, neste caso, o ideal é fazer um tipo especial de join, que o exists join.

    CustTable customer;
    SalesTable sales;
    ;

    print "Clients with open orders:";
    while select customer
        exists join sales
        where customer.AccountNum == sales.CustAccount
    {
        print strfmt("  Id: %1 - Name: %2", customer.AccountNum, customer.Name);
    }

    pause;

 

Neste caso, o exists join tem uma grande vantagem sobre um join comum: Não traz todas as ordens de vendas de cada cliente. Com isto o resultado do select é menor (leia-se: quantas linhas retornam). Além disto, a instância da tabela de ordens de compras não é carregada, economizando memória.

Este tipo de join tem seu inverso, que é o notexists. O comando também é conhecido como “minus” em SQL, pois trazem as linhas da tabela de cliente MENOS os clientes que estão na tabela de ordens de venda. Este é o resultado do código abaixo.

    CustTable customer;
    SalesTable sales;
    ;

    print "Clients without open orders:";
    while select customer
        notexists join sales
        where customer.AccountNum == sales.CustAccount
    {
        print strfmt("  Id: %1 - Name: %2", customer.AccountNum, customer.Name);
    }

    pause;

 

Utilizando o notexists temos que tomar o seguinte cuidado. As cláusulas where referentes à tabela de clientes devem ser escritas logo abaixo do select. Já as cláusulas referentes ao join entre as tabelas ficam no final do comando. Se eu quiser, por exemplo, filtrar os clientes que estão em São Paulo, tenho a query abaixo. Dois where no mesmo select não é comum em SQL, mas a leitura é bem intuitiva: Quero buscar os clientes que estão em São Paulo (primeiro select) e que não têm ordens de venda (o join).

    CustTable customer;
    SalesTable sales;
    ;

    print "Clients without open orders:";
    while select customer
        where customer.State == "SP" //Neste where ficam as cláusulas referentes a customer
        notexists join sales
        where customer.AccountNum == sales.CustAccount //Neste ficam as cláusulas do join
    {
        print strfmt("  Id: %1 - Name: %2", customer.AccountNum, customer.Name);
    }

    pause;

 

Se você tentar colocar a cláusula do cliente no final, junto com as outras, verá que o resultado é o inverso, a query trará os cliente que não estão em São Paulo, pois a cláusula está relacionada ao notexists.

Como conclusão, seguem algumas dicas para identificar qual a melhor solução:

  • Se dentro do select while você está fazendo testes com campos da tabela ou buscando informações de outras tabelas, também usando informações da tabela, verifique se um join não pode resolver este problema.
  • Se você está fazendo um join, mas a segunda tabela só é utilizada como filtro para o select, verifique se é possível utilizar o comando exists ou notexists

Print | posted on Tuesday, October 16, 2007 12:53 PM | Filed Under [ X++ ]

Powered by: