segunda-feira, 1 de junho de 2015

Base TRN, Primeiro Round!


A discussão aqui seria se o Genexus deveria mudar e evoluir ao longo do tempo sua forma de programar ou se mantém sua tradição, ou ainda se deve mudar em um ritmo mais acelerado. Muitos ficam incomodados quando algo radical ocorre, enquanto que outros ficam quando algo radical não ocorre, enfim, a comunidade pede melhorias mas ao mesmo tempo se assusta com as entregas. Particularmente acredito que nada como uma 'evoluidazinha' de vez em quando, mas também me preocupo quando algumas pedem ajustes, senão nos programas existentes, mas pelo menos na maneira que pensamos.

O fato concreto é que Genexus Evolution 3 apresenta alguns recursos novos na linguagem de programação que chamam bastante a atenção, muitas anunciadas a anos nos eventos. BASE TRN é um mecanismo que vai te ajudar a pensar Genexus melhor, mais simples.

CLAUSULA BASE TRN

Comecemos pelo comando for each que agora inclui uma nova cláusula chamada <base trn>, que possibilita indicar qual a transação (ou nível de transação) que será associada ao comando, ou seja que permite definir a tabela base.

Temos nesta nova cláusula um recurso que pode perfeitamente substituir a atual DEFINED BY, permitindo que se aponte para outras tabelas que além de representar melhor o relacionamento gerado entre as tabelas, pode também acelerar o processo de especificação do programa, pois o Genexus não precisa 'calcular' a tabela base, você mesmo indica.

Falando em escolher a tabela base, temos uma situação interessante quando programamos uma relação M-N, como por exemplo, a transação de dois níveis apresentada abaixo (NotaFiscal e Produto), já bastante conhecida e explorada, o que temos é a presença de ProdutoId e ProdutoDescricao nos níveis NotaFiscal.Produto e também na transação original Produto.


O fato é que neste simples exemplo temos duas 'tabelas' que eventualmente terão o atributo ProdutoDescricao, Hummmmm? não entendi, acho que você se enganou! Calma meu amigo, vamos devagarzinho aqui.  A normalização do modelo, que o Genexus vai promover, gerará duas tabelas distintas uma Produto com o atributo ProdutoDescricao gravado fisicamente, e outra chamada NotaFiscalProduto com o atributo ProdutoDescricao alcançável pela tabela estendida, portanto, não se esqueça do poder e da força desta 'tabela'.

FOR EACH ANINHADO

Desta forma se programarmos um  for each do tipo Join (aninhado) poderemos ter a surpresa de ver o Genexus apontar para a tabela Produto, que eventualmente não representa a melhor relação possível.  Seguindo este exemplo, a programação a seguir apontará para a tabela NotaFiscal no primeiro for each e para Produto no for each aninhado, ou seja, teremos ai um péssimo Produto Cartesiano, situação que pode ser comparada a um erro.

(1) for each
        &NotaFiscalId = NotaFiscalId
        for each
            &ProdutoDescricao = ProdutoDescricao
        endfor       
    endfor


Um detalhe interessante desta programação é que o Genexus gera duas queries, uma para selecionar Nota Fiscal e outra para Produto.

(2)
SELECT [NotaFiscalId] FROM [NotaFiscal] WITH (NOLOCK) ORDER BY [NotaFiscalId] 
SELECT [NotaFiscalId], [ProdutoId] FROM [NotaFiscalProduto] WITH (NOLOCK) WHERE [NotaFiscalId] = @NotaFiscalId ORDER BY [NotaFiscalId]

Apesar de estarmos utilizando este modelo em quase 90% dos programas até hoje, o resultado desta programação, sob o ponto de vista do Genexus EV3 deixa de ser interessante, pois é possível alcançar a mesma resposta com apenas uma querie.

BASE TRN

E que quiséssemos apontar para a tabela mais próxima de NotaFiscal, ou seja, para NotaFiscalProduto, temos com a <base trn>, podemos apontar para o nível desejado na transação, bastando incluir no for each o nível da transação que desejamos alcançar, muito simples hein.

(3) for each
        &NotaFiscalId = NotaFiscalId
        for each NotaFiscal.Produto
            &ProdutoDescricao = ProdutoDescricao
        endfor       
    endfor


A query gerada nesta operação e exatamente a mesma dos for eachs aninhados, exemplo (1).

(4)
SELECT [NotaFiscalId] FROM [NotaFiscal] WITH (NOLOCK) ORDER BY [NotaFiscalId] 

SELECT [NotaFiscalId], [ProdutoId] FROM [NotaFiscalProduto] WITH (NOLOCK) WHERE [NotaFiscalId] = @NotaFiscalId ORDER BY [NotaFiscalId]

Um dos detalhes deste novo modelo é a possibilidade de apontar para outras transações do modelo.

(5) for each
&NotaFiscalId = NotaFiscalId
for each Produto
&produtoid = produtoId
load
endfor
    endfor

Evidentemente, o resultado é um produto cartesiano, mas ainda sem mudanças significativas nas queries.

(6)
SELECT [NotaFiscalId] FROM [NotaFiscal] WITH (NOLOCK) ORDER BY [NotaFiscalId] 
SELECT [ProdutoId] FROM [Produto] WITH (NOLOCK) ORDER BY [ProdutoId]

Em outras palavras, quando temos relações M-N sempre teremos duas tabelas que podem ser escolhidas, e em algumas situações não contamos com um atributo secundário para apontar, a nova cláusula oferece uma possibilidade digamos, mais elegante.

ECONOMIZANDO SQL COM BASE TRN

Um detalhe interessante na programação da cláusula <base trn>, é a possibilidade de gerarmos uma lista de transações, gerando, quando for o caso, queries com INNER JOIN.  A vantagem deste modelo é a ausência de for eachs aninhados, mas com resultados semelhantes.

PRODUTO CARTESIANO
A geração de produto cartesiano ocorre quando relacionamos duas tabelas distintas que não se conectam por um filtro implícito, como por exemplo o código apresentado no exemplo (1), em que programamos dois for eachs aninhados para obter um produto cartesiano. O interessante é que com <base trn>, alcançamos o mesmo resultado com um único for each.

(7) for each Produto, NotaFiscal
       msg(NotaFiscalData.ToString()+''+ProdutoDescricao)
    endfor

O melhor disso é que este gera apenas um único SELECT, ou seja, mais rápido e eficiente, portanto, muito melhor que o modelo programado no exemplo (1).

(8)
SELECT T1.[ProdutoId], T2.[NotaFiscalId], T1.[ProdutoDescricao] FROM [Produto] T1 WITH (NOLOCK),  [NotaFiscal] T2 WITH (NOLOCK) ORDER BY T1.[ProdutoId] 

JOIN COM UM FOR EACH
Uma pequena mudança na ordem das transações nesta lista se produz um efeito totalmente diferente, ainda com um único SELECT chega-se a um INNER JOIN nas duas ou mais tabelas envolvidas.

(9) for each NotaFiscal, NotaFiscal.Produto
        &NotaFiscalId        = NotaFiscalId
        &ProdutoDescricao    = ProdutoDescricao
        load

    endfor

SELECT gerado neste caso é similar a pesquisa em tabela estendida, ou seja, um INNER JOIN com as três tabelas envolvidas.

(10)
SELECT T2.[ProdutoId], T1.[NotaFiscalId], T3.[ProdutoDescricao] FROM (([NotaFiscal] T1 WITH (NOLOCK) INNER JOIN [NotaFiscalProduto] T2 WITH (NOLOCK) ON T2.[NotaFiscalId] = T1.[NotaFiscalId]) INNER JOIN [Produto] T3 WITH (NOLOCK) ON T3.[ProdutoId] = T2.[ProdutoId]) ORDER BY T1.[NotaFiscalId]

O resultado da execução deste for each é a mesma executada com dois for eachs aninhados (exemplo 1), porem com melhor performance.

PROGRAMAÇÃO TRADICIONAL
Sem o <base trn>, mas buscando o mesmo apresentado no exemplo (9) , é a programação com um único for each. Neste caso o que se programa é a tabela base NotaFiscal e tabela estendida.

(11) for each
        &NotaFiscalId        = NotaFiscalId
        &ProdutoDescricao    = ProdutoDescricao
        load
    endfor


A querie resultante neste modelo.

(12) 
SELECT [NotaFiscalId], [ProdutoId] FROM [NotaFiscalProduto] WITH (NOLOCK) ORDER BY [NotaFiscalId], [ProdutoId]

Resumo da História

Nas versões anteriores do Genexus tinhamos poucas possibilidades para gerar SELECT nas tabelas do BD, com a versão EV3 e as <base trn> temos situações que seriam resolvidas de formas distintas. Observe, que nesta pequena situação chegamos a seis modos distintos para recuperar informação entre Produto e NotaFiscal.

Outro detalhe muito importante é o especificador do Genexus, lembrando que cada programa a ser gerado em Java, C# ou Ruby, deve passar por ele que defina quais tabelas devem ser alcançadas e a forma de acesso.  O fato é que quanto mais tabelas temos na kb mais lento será o especificador para identificar uma tabela base e estendida que alcance tudo que se deseja no for each<base trn>, é uma forma de turbinar o processo, pois o próprio desenvolvedor define o que o especificador deve fazer.

O ponto mais negativo é que programas em versões anteriores poderão sofrer um pouco nesta nova definição, pois na sintaxe anterior o que tinhamos era for each [atributo order], e agora for each [transacao], portanto, é necessária uma pequena revisão.

Um detalhe para a Artech: o objeto Data Selector não foi influenciado pela <base trn>, creio que precisa.