- Criado por Sergio Luis De Alcantara Silveira, última alteração em 17 fev, 2017
bjetivo
Abordar o desenvolvimento de queries utilizando o framework Protheus.
O DBAcess (antigo TopConnect)
O DBAccess é um produto que permite a conexão do ERP Protheus aos diversos bancos de dados SQL suportados.
O DBAcess possui várias funções. Destacamos as mais importantes:
- Comunicação com o banco de dados.
- Gerenciamento das conexões de estações Protheus x banco da dados.
- 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.
Implementação no banco de dados
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 |
---|---|
R_E_C_N_O_ | Número do registro. É a chave primária de todas as tabelas de dados e por consequência único. É alimentado automaticamente quando da inserção de uma linha via TOP. É um inteiro de 4 bytes. Permite compatibilidade ISAM com a função DbGoto(). |
D_E_L_E_T_ | “Flag” de exclusão. Indica a exclusão “lógica” de uma linha ou registro, ou seja, a linha não é mais considerada no Protheus mas permanece no banco de dados. É um caractere de tamanho 1, e os conteúdos permitidos são branco (linha ativa) e asterisco (linha excluída). Criado também por questões de compatibilidade com ISAM. |
R_E_C_D_E_L_ | Número do registro do item excluído. Este campo recebe o valor de R_E_C_N_O_ quando da exclusão lógica do registro. Este campo é necessário pois caso haja exclusão 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 R_E_C_D_E_L_ no final. |
Implementação no banco de dados
- Campos do tipo DATA.
Os campos tipo data são armazenados como caractere de 8 dígitos.
- Campos do tipo NUMÉRICO
Os campos do tipo numérico são armazenados como FLOAT (ponto flutuante) de 8 dígitos. Os campos são armazenados de forma binária, dessa forma um campo de 8 dígitos pode armazenar um número muito maior que 8 inteiros ou decimais.
- Campos do tipo LÓGICO (BOOLEANO)
Os campos tipo lógico são armazenados como caractere de 1 dígito. - Campos do tipo MEMO (texto grande)
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:
Desvantagens:
Queries no Protheus
Existem duas formas de executar queries no Protheus:
- Construção de string: nesse modelo, o comando SQL (statement) é construído em uma varíável caractere e enviado ao banco de dados usando a função TcGenQry() ( mais usado ).
- Embedded SQL: nesse modelo a query é construída diretamente no programa ADVPL usando uma sintaxe específica.
Em ambos os modelos, o funcionamento e a performance são os mesmos.
Abaixo listamos as principais funções e comandos de suporte a queries no ADVPL:
Comando / função | Descrição |
---|---|
#IFDEF TOP / #ENDIF | Diretiva de compilação utilizada para separar o trecho de código a ser usado apenas em TOP / SQL. |
TcGenQry( ) | Efetua o disparo da query passada como string para o DBAccess. |
dbUseArea() | Efetua a criação 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() | Função de framework para compatibilizar a sintaxe das queries aos diferentes bancos de dados (SQL Server, Oracle, IBM DB2, Informix, MySql entre outros). Seu uso é obrigatório, 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 cláusula ORDER BY da query. |
xFilial( ) | Usada na construção da query para selecionar a filial corrente, da mesma forma que em ISAM. |
RetSqlName() | Retorna o nome físico da tabela, baseado em um alias, para ser usado em na contrução do “SELECT ... FROM TABELA” da query. |
DTOS() | Usada para quando deseja-se comparar um campo data do banco com uma variável 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. Após essa função, o record set não pode mais ser acessado. Uso obrigatório 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 início de uma query em “embedded SQL” |
EndSql | Comando que marca o término de uma query em “embedded SQL” |
% | Marca o início e o fim de uma expressão em “embedded SQL”. |
Desenvolvendo Queries
- 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.
O primeiro campo retornado é o R_E_C_N_O_ que recebe o apelido SC9RECNO.
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
Sequência para desenvolvimento de uma query básica no Protheus:
cAliasAB9 := GetNextAlias()
cQuery := "SELECT AB9_DTFIM, AB9_HRFIM FROM “
cQuery += RetSqlName( "AB9" ) + " AB9 "
cQuery += "WHERE " cQuery += "AB9_FILIAL='" + xFilial( "AB9" ) + "' AND “
cQuery += "AB9.D_E_L_E_T_=' '"
cQuery += "AB9_NUMOS='" + ( cAliasAB7 )->AB7_NUMOS + ( cAliasAB7 )->AB7_ITEM + "' AND “
cQuery := ChangeQuery( cQuery )
- “TOPCONN” -> Nome da RDD do DBAccess
- cQuery -> String que contém a query (statement) propriamente dito.
- cAliasAB9 -> Alias do record set definido por GetNextAlias()
DbUseArea( .T., "TOPCONN", TcGenQry( , , cQuery ), cAliasAB9, .F., .T. )
TcSetField( cAliasAB9, "AB9_DTFIM", "D", 8, 0 )
•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.
aStruAB9 := AB9->( dbStruct() ) For nLoop := 1 to Len( aStruAB9 ) If aStruAB9[ nLoop, 2 ] <> “C” // só quando não for caractere TcSetField( cAliasAB9, aStruAB9[nLoop,1], aStruAB9[nLoop,2], aStruAB9[nLoop,3], aStruAB9[nLoop,4]) EndIf Next nLoop
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
( cAliasAB9)->( dbCloseArea() )
dbSelectArea( “AB9” ) // seleção de tabela “preventiva”
Consultando mais de uma tabela
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 avançados de JOIN : OUTER JOIN
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
- COUNT() – Contagem de registros
- SUM() – Soma dos valores de uma expressão
- MAX() – Máximo valor de uma expressão
- MIN() - Mínimo valor de uma expressã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")
Status do documento | Concluído |
---|---|
Data | 25/03/2015 |
Versão | 1.0 |
Versão anterior | 1.0 |
Autores |
- Sem rótulos