Вы находитесь на странице: 1из 9

Desenvolvendo queries no Protheus

bjetivo
Abordar o desenvolvimento de queries utilizando o framework Protheus.

O DBAcess (antigo TopConnect)


O DBAccess um produto que permite a conexo do ERP Protheus aos diversos bancos de
dados SQL suportados.
O DBAcess possui vrias funes. Destacamos as mais importantes:
Comunicao com o banco de dados.
Gerenciamento das conexes de estaes Protheus x banco da dados.
Traduo dos comandos e funes enviados pela RDD (ISAM) em linguagem SQL.
Envio e tratamento dos comandos SQL nativos ao banco de dados.
Gerenciamento de travas (locks) atravs 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 diferenas intrnsecas ao dois modelos, esta forma de acesso muito
lenta, alm de onerar demasiadamente o banco de dados, a rede e o prprio DBAccess.
A recomendao utilizar sempre que possvel a linguagem SQL para acesso aos dados quando
se utiliza DBAccess.
A leitura de um conjunto de resultados (result set) obtido atravs de uma query muitssimo mais
rpida que a varredura na tabela real usando ISAM.
Alm disso, a linguagem SQL permite o uso de funes de agregao, entre outros, que otimizam
ainda mais o desempenho.
Implementao no banco de dados
Listamos abaixo algumas caractersticas exigidas para a manipulao de tabelas pelo DBAccess.
Campos especiais: as tabelas gerenciadas pelo DBAccess devem possuir campos de controle.
Eles so criados automaticamente quando da criao das tabelas. So eles:
Campo

Descrio

R_E_C_N_O_

Nmero do registro. a chave primria de


todas as tabelas de dados e por
consequncia nico. alimentado
automaticamente quando da insero de uma
linha via TOP. um inteiro de 4 bytes.
Permite compatibilidade ISAM com a funo
DbGoto().

D_E_L_E_T_

Flag de excluso. Indica a excluso lgica


de uma linha ou registro, ou seja, a linha no
mais considerada no Protheus mas
permanece no banco de dados. um
caractere de tamanho 1, e os contedos
permitidos so branco (linha ativa) e asterisco
(linha excluda). Criado tambm por questes
de compatibilidade com ISAM.

R_E_C_D_E_L_

Nmero do registro do item excludo. Este


campo recebe o valor de R_E_C_N_O_
quando da excluso lgica do registro. Este
campo necessrio pois caso haja excluso
de duas linhas com a mesma chave nica
alternativa (X2_UNICO), haveria duplicidade
de chave. Dessa forma, todas as chaves
nicas criadas por X2_UNICO recebem
automaticamente D_E_L_E_T_ e
R_E_C_D_E_L_ no final.

Status do
documento

Concludo

Data

25/03/2015

Verso

1.0

Verso
anterior

1.0

Autores

Srgio Lus de
Alcntara
Silveira

Implementao no banco de dados


Campos do tipo DATA.
Os campos tipo data so armazenados como caractere de 8 dgitos.
Campos do tipo NUMRICO
Os campos do tipo numrico so armazenados como FLOAT (ponto flutuante) de 8
dgitos. Os campos so armazenados de forma binria, dessa forma um campo de 8
dgitos pode armazenar um nmero muito maior que 8 inteiros ou decimais.
Campos do tipo LGICO (BOOLEANO)
Os campos tipo lgico so armazenados como caractere de 1 dgito.
Campos do tipo MEMO (texto grande)
Os campos tipo memo so 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) so
armazenados de forma diferente, na tabela SYP.

Tabela TOP_FIELD

A tabela Top Field utilizada para converso automtica dos tipos de dados diferentes de
caractere para os tipos correspondentes na linguagem ADVPL, quando utilizamos modo de acesso
ISAM. Ela tambm armazena a quantidade de inteiros e casas decimais dos campos numricos.
A Top Field alimentada automaticamente durante a criao de uma tabela pelo DBAccess e
tambm atualizada quando as propriedades dos campos so alteradas usando comandos e
funes padres do ADVPL.
Dessa forma, quando um campo criado ou mantido pelo configurador, atualizao de verso ou
update, a top field corretamente alimentada.
Por outro lado, se a tabela manipulada diretamente no banco, pode ocorrer divergncia entre a
Top Field e a tabela fsica. Isso vai causar mau funcionamento do sistema.

A tabela Top Field s faz a converso automtica de tipo no modelo ISAM. Quando trabalhamos
com result sets de querys, devemos utilizar a funo TcSetField() para converter manualmente os
tipos de dados.
A linguagem SQL

Definio:
Structured Query Language, ou Linguagem de Consulta Estruturada ou SQL, uma
linguagem de pesquisa declarativa para banco de dados relacional (base de dados
relacional). Muitas das caractersticas originais do SQL foram inspiradas na lgebra
relacional.
O principal objetivo do SQL o de providenciar um mtodo de acesso s bases de dados
de forma interativa atravs de consultas (queries) base de dados.
A linguagem SQL baseia-se na lgica de conjuntos ao invs da lgica procedural
(varredura).

Vantagens do SQL:
uma linguagem universal de acesso a dados, e apesar das variaes, roda em uma
enorme quantidade de plataformas.
Concentra-se nos resultados desejados, deixando ao banco de dados a misso de escolher
a melhor estratgia para consulta dos dados.
Reduz o trafego de rede pois muitos dados so avaliados no servidor do banco de dados,
sem que obrigatoriamente sejam enviados aplicao, como no ISAM.

Desvantagens:
A lgica de conjuntos no natural para muitas pessoas, o que exige certo tempo de
adaptao.
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
Existem duas formas de executar queries no Protheus:

Construo de string: nesse modelo, o comando SQL (statement) construdo em uma


varvel caractere e enviado ao banco de dados usando a funo TcGenQry() ( mais
usado ).
Embedded SQL: nesse modelo a query construda diretamente no programa ADVPL
usando uma sintaxe especfica.
Em ambos os modelos, o funcionamento e a performance so os mesmos.
Abaixo listamos as principais funes e comandos de suporte a queries no ADVPL:

Comando / funo

Descrio

#IFDEF TOP / #ENDIF

Diretiva de compilao utilizada para separar


o trecho de cdigo a ser usado apenas em
TOP / SQL.

TcGenQry( )

Efetua o disparo da query passada como


string para o DBAccess.

dbUseArea()

Efetua a criao de uma rea de trabalho


para o record set retornado por TcGenQry()

GetNextAlias()

Retorna um alias para ser utilizado no record


set definido em dbUseArea()

ChangeQuery()

Funo de framework para compatibilizar a


sintaxe das queries aos diferentes bancos de
dados (SQL Server, Oracle, IBM DB2,
Informix, MySql entre outros). Seu uso
obrigatrio, salvo em casos especiais.

TcSetField()

Compatibiliza os tipos de dados diferentes de


caractere retornados pela query aos tipos do
ADVPL.

SqlOrder()

Converte uma sintaxe de chave ndice em


ADVPL para sintaxe SQL para uso na
clusula ORDER BY da query.

xFilial( )

Usada na construo da query para


selecionar a filial corrente, da mesma forma
que em ISAM.

RetSqlName()

Retorna o nome fsico da tabela, baseado em


um alias, para ser usado em na contruo do
SELECT ... FROM TABELA da query.

DTOS()

Usada para quando deseja-se comparar um


campo data do banco com uma varivel tipo
data do ADVPL.

dbGoTop()

Move o ponteiro do record set para a primeira


linha.

dbSkip()

Move o ponteiro do record set para a linha


seguinte. Diferentemente da varredura ISAM,
o ponteiro s poder ser movido para frente.
Usado nos loops de varredura.

Eof()

Informa se o record set chegou ao fim.

dbCloseArea()

Fecha a rea de trabalho e apaga o record


set. Aps essa funo, o record set no pode
mais ser acessado. Uso obrigatrio ao final do
processo.

TcGetDb()

Devolve o nome do SGBD (sistema


gerenciador de banco de dados) em uso.
Exemplo : MSSQL -> SQL Server,
"ORACLE -> Oracle. Usado apenas em
casos especiais, quando se deseja difenciar
sintaxe, etc...

BeginSql

Comando que marca o incio de uma query


em embedded SQL

EndSql

Comando que marca o trmino de uma query


em embedded SQL

Marca o incio e o fim de uma expresso em


embedded SQL.

Desenvolvendo Queries
Nos primeiros momentos do DBAccess, um tipo de query foi bastante utilizado pois necessitava de
poucas mudanas na aplicao original. a chamada query de recno.
Apesar de menos eficiente, esse modelo ainda til quando necessrio 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 contrrio do que se poderia supor, esta abordagem mais rpida que simplesmente varrer a
tabela ISAM, mesmo indexada, pelas seguintes razes:
Efetuar um dbkip() no record set muito mais rpido que o dbskip() na tabela real.
O dbGoto() na tabela real muito eficiente, pois o R_E_C_N_O_ a chave primria de
todas as tabelas e seu ndice diferenciado e muito rpido.
A query consegue eficientemente desprezar as linhas deletadas logicamente, ao passo
que o dbskip() na tabela fsica ter de desprezar sequencialmente os excludos. Existem
casos reais na TOTVS de um nico dbskip() levar 20 minutos por ter encontrado 1 milho
de registros excludos no incio da tabela.
Exemplo de query de recno
Esta query posiciona registros pois chamar uma funo que depende do registro posicionado.
O primeiro campo retornado o R_E_C_N_O_ que recebe o apelido SC9RECNO.

Exemplo

lQuery
:= .T.
cAliasSC9 := GetNextAlias()

cQuery := "SELECT R_E_C_N_O_ SC9RECNO,C9_PEDIDO,C9_ITEM


"
cQuery += "FROM "+RetSqlName("SC9")+" SC9 "
cQuery += "WHERE SC9.C9_FILIAL='"+xFilial("SC9")+"' AND
"
If ( lInverte )
cQuery += "SC9.C9_OK<>'"+cMarca+"' AND "
Else
cQuery += "SC9.C9_OK='"+cMarca+"' AND "
EndIf
cQuery += "SC9.C9_BLEST<>'10' AND "
cQuery += "SC9.C9_BLEST<>'ZZ' AND "
cQuery += "SC9.C9_BLCRED<>'10' AND "
cQuery += "SC9.C9_BLCRED<>'ZZ' AND "
cQuery += "SC9.D_E_L_E_T_=' ' "
cQuery += "AND "+aFiltro[2]
cQuery := ChangeQuery(cQuery)
dbUseArea(.T.,"TOPCONN",TcGenQry(,,cQuery),cAliasSC9,.T.
,.T.)
TcSetField( cAliasSC9, SC9RECNO, N, 10, 0 ) //
Utilizar tcsetfield para o campo RECNO
// efetuada varredura do record set e efetuado dbGoto()
na tabela real
dbSelectArea(cAliasSC9)
While !Eof()
.
.
.
If lValido
If lQuery
dbSelectArea("SC9")
dbGoto((cAliasSC9)->SC9RECNO)
EndIf
dbSelectArea("SC9")
If Empty(c460Cond) .Or. &(c460Cond)
//-----------------------------------------------// Verifica ponto de entrada
//-----------------------------------------------If ExistBlock("MA461EST")
lValido := ExecBlock("MA461EST",.F.,.F.)
EndIf
If lValido
Begin Transaction
a460Estorna()
End Transaction
EndIf
EndIf
EndIf
.
.
EndIf

Sequncia para desenvolvimento de uma query bsica no Protheus:


Defina um Alias (apelido) para o record set da query. No 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 seleo de campos, pois todos os campos
sero retornados aumentando o trrego de rede.
cQuery := "SELECT AB9_DTFIM, AB9_HRFIM FROM
Utilize RetSqlName( cAlias ) para retornar o nome fsico da tabela.

cQuery += RetSqlName( "AB9" ) + " AB9 "


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 varivel contendo o cdigo da
filial (exceo).

cQuery += "WHERE "


cQuery += "AB9_FILIAL='" + xFilial( "AB9" ) + "' AND

Efetue o filtro para remover linhas excludas. Compare D_E_L_E_T_= pois mais rpido que
fazer D_E_L_E_T_<>*

cQuery += "AB9.D_E_L_E_T_=' '"

Coloque o maior nmero possvel de condies de filtro na query. Quanto mais restritivo for o
filtro, menos registros vo ser includos no record set e menos trafgo ocorrer na rede local

cQuery += "AB9_NUMOS='" + ( cAliasAB7 )->AB7_NUMOS + (


cAliasAB7 )->AB7_ITEM + "' AND

Utilize a clusula ORDER BY apenas quando precisar de resultados ordenados. ORDEY BY


diminui a performance da query.
Utilize a funo ChangeQuery() para que a sintaxe da query seja adequada aos diferentes bancos
de dados.

cQuery := ChangeQuery( cQuery )

Efetue o disparo da query e criao de seu record set atravs do uso conjunto de dbUseArea() e
TcGenQry(). Nessa chamada utilizaremos:
TOPCONN -> Nome da RDD do DBAccess
cQuery -> String que contm a query (statement) propriamente dito.
cAliasAB9 -> Alias do record set definido por GetNextAlias()

DbUseArea( .T., "TOPCONN", TcGenQry( , , cQuery ),


cAliasAB9, .F., .T. )

Caso existam campos com tipo diferente de caractere na lista de retorno, deve-se utilizar
TcSetField() para converter para um tipo vlido no ADVPL. Nesse caso, converteremos o campo
AB9_DTFIM para o tipo data do ADVPL.

TcSetField( cAliasAB9, "AB9_DTFIM", "D", 8, 0 )

Quando for necessrio converter uma grande quantidade de campos de uma tabela, pode-se usar
a estrutura da mesma usando a funo dbStruct() e efetuar uma varredura.

aStruAB9 := AB9->( dbStruct() )


For nLoop := 1 to Len( aStruAB9 )
If aStruAB9[ nLoop, 2 ] <> C // s quando no for
caractere
TcSetField( cAliasAB9, aStruAB9[nLoop,1],
aStruAB9[nLoop,2], aStruAB9[nLoop,3], aStruAB9[nLoop,4])
EndIf
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.

While (cAliasAB9)->( Eof())


dDataF := Max(dDataF,( cAliasAB9 )->AB9_DTFIM)
If ( ( cAliasAB9 )->AB9_DTFIM == dDataF )
cHoraF := If(cHoraF< ( cAliasAB9 )->AB9_HRFIM,;
( cAliasAB9 )->AB9_HRFIM,cHoraF)
EndIf
( cAliasAB9 )->( DbSkip() )
EndDo

Ao final do processo, excluir o record set com dbCloseArea(). Aps o dbCloseArea(),


recomendvel selecionar uma tabela real, pois o Protheus ficar sem tabela aberta e um
GetArea() subsequente poder derrubar o sistema
( cAliasAB9)->( dbCloseArea() )
dbSelectArea( AB9 ) // seleo de tabela preventiva

Consultando mais de uma tabela


Em muitos casos, os dados que precisamos dependem da interao 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 so 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 ocorrncias) so relacionadas atravs dos campo AB9_CODPRB
e AAG_CODPRB (cdigo de ocorrncia). O que se deseja trazer, junto dos dados do
atendimento, o tipo da ocorrncia (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 "
cQuery += "AAG_FILIAL='" + xFilial("AAG") + "' AND "
cQuery += "AB9.D_E_L_E_T_= ' AND "
cQuery += "AAG.D_E_L_E_T_= '"
cQuery := ChangeQuery( cQuery )
DbUseArea( .T., "TOPCONN", TcGenQry( ,, cQuery ),
cAlias, .F., .T. )

Tipos mais avanados de JOIN : OUTER JOIN


O OUTER JOIN permite que dados sejam recuperados mesmo que um do lados (tabela) no
possua correspondncia. Por isso externo.
Existem duas variantes : LEFT JOIN preserva os dados da tabela da esquerda, ou primeira
tabela mencionada, mesmo que no encontre uma linha correspondente na tabela da direita.
RIGHT JOIN preserva os dados da tabela da direita, ou segunda tabela mencionada, mesmo que
no 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 no 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 preos) , 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 no
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()))

Funes de agregao
As funes de agregao do SQL permitem que um conjunto de linhas sejam agregadas em uma
nica linha.
As funes de agregao podem otimizar a performance do sistema pois reduzem o nmero de
linhas retornadas diminuindo o trfego de rede.
Principais funes de agregao:
COUNT() Contagem de registros
SUM() Soma dos valores de uma expresso
MAX() Mximo valor de uma expresso
MIN() - Mnimo valor de uma expresso
Ao informar uma funo de agregao em um SELECT, no se pode trazer outros campos na
seleo a menos que faam parte de uma clusula GROUP BY. Isso ocorre porque a funo de
agregao resultado de vrios registros e no faz sentido trazer apenas um deles.
Esta query exemplo retorna a quantidade total de itens liberados de um pedido de vendas (que
ainda no foram faturados), considerando todas as sequencias de liberao existentes (SC9).

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")

Вам также может понравиться