Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
bjetivo
Status do Concluído
Abordar o desenvolvimento de queries utilizando o framework Protheus. documento
Data 25/03/2015
O DBAcess (antigo TopConnect) Versão 1.0
O DBAccess é um produto que permite a conexão do ERP Protheus aos diversos bancos de
Versão 1.0
dados SQL suportados.
anterior
O DBAcess possui várias funções. Destacamos as mais importantes:
Autores Sérgio Luís de
Comunicação com o banco de dados. Alcântara
Gerenciamento das conexões de estações Protheus x banco da dados. Silveira
Tradução dos comandos e funções enviados pela RDD (ISAM) em linguagem SQL.
Envio e tratamento dos comandos SQL nativos ao banco de dados.
Gerenciamento de travas (locks) através do lock manager.
O DBAccess permite aos desenvolvedores trabalhar com banco de dados SQL usando uma
metodologia ISAM, exatamente como se estivessem usando um banco ISAM nativo.
No entanto, devido às diferenças intrínsecas ao dois modelos, esta forma de acesso é muito
lenta, além de onerar demasiadamente o banco de dados, a rede e o próprio DBAccess.
A recomendação é utilizar sempre que possível a linguagem SQL para acesso aos dados quando
se utiliza DBAccess.
A leitura de um conjunto de resultados (result set) obtido através de uma query é muitíssimo mais
rápida que a varredura na tabela real usando ISAM.
Além disso, a linguagem SQL permite o uso de funções de agregação, entre outros, que otimizam
ainda mais o desempenho.
Listamos abaixo algumas características exigidas para a manipulação de tabelas pelo DBAccess.
Campos especiais: as tabelas gerenciadas pelo DBAccess devem possuir campos de controle.
Eles são criados automaticamente quando da criação das tabelas. São eles:
Campo Descrição
Os campos tipo memo são armazenados como BLOB (binary large object). O nome
do tipo de campo pode variar conforme o banco de dados. No MS Sql Server o campo
é armazenado como image. Os campos MEMO vituais estilo Protheus (MSMM) são
armazenados de forma diferente, na tabela SYP.
Tabela TOP_FIELD
A tabela Top Field é utilizada para conversão automática dos tipos de dados diferentes de
caractere para os tipos correspondentes na linguagem ADVPL, quando utilizamos modo de acesso
ISAM. Ela também armazena a quantidade de inteiros e casas decimais dos campos numéricos.
A Top Field é alimentada automaticamente durante a criação de uma tabela pelo DBAccess e
também é atualizada quando as propriedades dos campos são alteradas usando comandos e
funções padrões do ADVPL.
Dessa forma, quando um campo é criado ou mantido pelo configurador, atualização de versão ou
update, a top field é corretamente alimentada.
Por outro lado, se a tabela é manipulada diretamente no banco, pode ocorrer divergência entre a
Top Field e a tabela física. Isso vai causar mau funcionamento do sistema.
A tabela Top Field só faz a conversão automática de tipo no modelo ISAM. Quando trabalhamos
com result sets de querys, devemos utilizar a função TcSetField() para converter manualmente os
tipos de dados.
A linguagem SQL
Definição:
Vantagens do SQL:
•É uma linguagem universal de acesso a dados, e apesar das variações, roda em uma
enorme quantidade de plataformas.
•Concentra-se nos resultados desejados, deixando ao banco de dados a missão de escolher
a melhor estratégia para consulta dos dados.
•Reduz o trafego de rede pois muitos dados são avaliados no servidor do banco de dados,
sem que obrigatoriamente sejam enviados à aplicação, como no ISAM.
Desvantagens:
•A lógica de conjuntos não é natural para muitas pessoas, o que exige certo tempo de
adaptação.
•Queries grandes e complexas podem ser tornar muito abstratas, dificultando o
entendimento.
•Devido ao desconhecimento dos fundamentos da linguagem, pode-se fazer uma query
esperando uma resposta e receber algo totalmente diferente, e o pior, sem saber disso.
Queries no Protheus
Desenvolvendo Queries
Nos primeiros momentos do DBAccess, um tipo de query foi bastante utilizado pois necessitava de
poucas mudanças na aplicação original. É a chamada “query de recno”.
Apesar de menos eficiente, esse modelo ainda é útil quando é necessário posicionar a tabela real
para alterar dados ou excluir.
Esta query retorna um campo R_E_C_N_O_ para que na varredura seja efetuado um dbgoto() no
registro real.
Ao contrário do que se poderia supor, esta abordagem é mais rápida que simplesmente varrer a
tabela ISAM, mesmo indexada, pelas seguintes razões:
Efetuar um dbkip() no record set é muito mais rápido que o dbskip() na tabela real.
O dbGoto() na tabela real é muito eficiente, pois o R_E_C_N_O_ é a chave primária de
todas as tabelas e seu índice é diferenciado e muito rápido.
A query consegue eficientemente desprezar as linhas deletadas logicamente, ao passo
que o dbskip() na tabela física ter de desprezar sequencialmente os excluídos. Existem
casos reais na TOTVS de um único dbskip() levar 20 minutos por ter encontrado 1 milhão
de registros excluídos no início da tabela.
Exemplo de “query de recno”
Esta query posiciona registros pois chamará uma função que depende do registro posicionado.
Exemplo
lQuery := .T.
cAliasSC9 := GetNextAlias()
•Defina um Alias (apelido) para o record set da query. Não utilize alias fixo, pois em caso de
recursividade haverá erro por duplicidade. Utilize GetNextAlias():
cAliasAB9 := GetNextAlias()
•Comece a escrever a string da query. Evite usar “*” na seleção de campos, pois todos os campos
serão retornados aumentando o trárego de rede.
•Efetue o filtro por filial. Sempre iguale o campo filial ao xFilial da tabela correspondente. Nunca
compare o campo _FILIAL de uma tabela com o campo _FILIAL de outra. Algumas rotinas de
processamento multi-filial podem comparar o campo _FILIAL a uma variável contendo o código da
filial (exceção).
•Efetue o filtro para remover linhas excluídas. Compare D_E_L_E_T_=‘ ‘ pois é mais rápido que
fazer D_E_L_E_T_<>’*’
•Coloque o maior número possível de condições de filtro na query. Quanto mais restritivo for o
filtro, menos registros vão ser incluídos no record set e menos trafégo ocorrerá na rede local
•Efetue o disparo da query e criação de seu record set através do uso conjunto de dbUseArea() e
TcGenQry(). Nessa chamada utilizaremos:
•Caso existam campos com tipo diferente de caractere na lista de retorno, deve-se utilizar
TcSetField() para converter para um tipo válido no ADVPL. Nesse caso, converteremos o campo
AB9_DTFIM para o tipo data do ADVPL.
•Quando for necessário converter uma grande quantidade de campos de uma tabela, pode-se usar
a estrutura da mesma usando a função dbStruct() e efetuar uma varredura.
Next nLoop
•Utilizar o record set conforme desejado. Nesse caso, será efetuada varredura por While.Utilizar
dbSkip() para varrer e Eof() para testar o fim do record set.
•Ao final do processo, excluir o record set com dbCloseArea(). Após o dbCloseArea(), é
recomendável selecionar uma tabela real, pois o Protheus ficará sem tabela aberta e um
GetArea() subsequente poderá derrubar o sistema
( cAliasAB9)->( dbCloseArea() )
Em muitos casos, os dados que precisamos dependem da interação entre duas ou mais tabelas.
O SQL é bastante eficiente para tratar este tipo de consulta, que chamamos de JOIN.
O tipo mais comum de JOIN é chamado de INNER JOIN. Neste modelo, devem existir dados tanto
na primeira tabela (“esquerda”) quanto na segunda (“direita”) para que alguma linha seja
recuperada.
O INNER JOIN pode ser escrito tanto no “estilo antigo”, em que os campos de uma tabela
simplesmente são comparados com os campos de outra tabela, ou no “estilo novo” (ANSI) onde o
relacionamento é explicitado.
Abaixo listamos um exemplo de INNER JOIN usando o “estilo antigo”. As tabelas AB9
(atendimentos) e AAG (tipos de ocorrências) são relacionadas através dos campo AB9_CODPRB
e AAG_CODPRB (código de ocorrência). O que se deseja é trazer, junto dos dados do
atendimento, o tipo da ocorrência (AAG_TIPPRB) que existe apenas em AAG.
cAlias := GetNextAlias()
cQuery := “”
cQuery += "SELECT
AB9_DTINI,AB9_HRINI,AB9_DTFIM,AB9_HRFIM,AAG_TIPPRB FROM
"
cQuery += RetSqlName( "AB9" ) + " AB9," + RetSqlName(
"AAG" ) + " AAG WHERE "
cQuery += "AB9_NUMOS='" + AB7->AB7_NUMOS + AB7->AB7_ITEM
+ "' AND "
cQuery += "AB9_CODPRB=AAG_CODPRB AND "
cQuery += "AB9_FILIAL='" + xFilial("AB9") + "' AND "
O OUTER JOIN permite que dados sejam recuperados mesmo que um do lados (tabela) não
possua correspondência. Por isso é “externo”.
Existem duas variantes : LEFT JOIN preserva os dados da tabela da “esquerda”, ou primeira
tabela mencionada, mesmo que não encontre uma linha correspondente na tabela da “direita”.
RIGHT JOIN preserva os dados da tabela da “direita”, ou segunda tabela mencionada, mesmo que
não encontre uma linha correspondente na tabela da “esquerda”.
No Protheus, o OUTER JOIN pode ser escrito apenas em sintaxe ANSI. Sintaxes antigas como o
*= do SQL Server não devem ser utilizadas, pois ocorrerá erro em outros SGBDs.
•Abaixo listamos um exemplo de OUTER JOIN usando LEFT JOIN. A tabela principal é a tabela
DA1 (itens da tabela de preços) , portanto é a tabela da “esquerda”. A segunda tabela é a tabela
SB1 (produtos), portanto a tabela da “direita”. Essa query traz os dados de DA1 mesmo que não
exista uma linha correspondente em SB1.
cQuery := "SELECT DA1.*,DA1.R_E_C_N_O_ DA1RECNO,
B1_DESC, B1_PRV1 FROM "
cQuery += RetSqlName("DA1")+ " DA1 "
cQuery += "LEFT JOIN " +RetSqlName("SB1")+ " SB1 "
cQuery += " ON SB1.B1_FILIAL = '"+xFilial("SB1")+"'"
cQuery += " AND SB1.B1_COD = DA1.DA1_CODPRO"
cQuery += " AND SB1.D_E_L_E_T_ = ' ' "
cQuery += "WHERE DA1.DA1_FILIAL = '"+xFilial("DA1")+"'"
cQuery += " AND DA1.DA1_CODTAB = '"+DA0->DA0_CODTAB+"'"
cQuery += " AND DA1.D_E_L_E_T_ = ' ' "
cQuery += "ORDER BY "+SqlOrder(DA1->(IndexKey()))
Funções de agregação
As funções de agregação do SQL permitem que um conjunto de linhas sejam agregadas em uma
única linha.
As funções de agregação podem otimizar a performance do sistema pois reduzem o número de
linhas retornadas diminuindo o tráfego de rede.
Principais funções de agregação:
cAliasSC9 := GetNextAlias()
cQuery := "SELECT SUM(C9_QTDLIB) C9_QTDLIB "
cQuery += "FROM "+RetSqlName("SC9")+" SC9 "
cQuery += "WHERE SC9.C9_FILIAL='"+xFilial("SC9")+"' AND
"
cQuery += "SC9.C9_PEDIDO='" +(cCurSorSC6)->C6_NUM+"' AND
"
cQuery += "SC9.C9_ITEM='" +(cCurSorSC6)->C6_ITEM+"'
AND "
cQuery +=
"SC9.C9_NFISCAL='"+Space(Len(SC9->C9_NFISCAL))+"' AND
cQuery += "SC9.D_E_L_E_T_=' ' "
cQuery := ChangeQuery(cQuery)
dbUseArea(.T.,"TOPCONN",TcGenQry(,,cQuery),cAliasSC9,.T.
,.T.)
nQtdLib := (cAliasSC9)->C9_QTDLIB
dbCloseArea()
dbSelectArea("SC9")