Business - Automação Comercial

Bematech: Trabalhando com Banco de Dados no SB-2030E

Neste artigo, iremos trabalhar com um dos recursos mais importantes do micro-terminal SB-2030E - o Banco de Dados.

por André Luiz R. Munhoz



No último artigo, vimos duas maneiras de criar menus de interação no SB-2030E (menu fixo e de rolagem) e algumas funções de entrada de dados via teclado, como getch(), gets(), getstring() e getstringvirgula().

Neste artigo, iremos trabalhar com um dos recursos mais importantes do micro-terminal SB-2030E - o Banco de Dados.

Lembrete: Caso você ainda não baixou o VisualSDK Builder, acesse:
http://www.bematech.com.br/suporte/downloads/cpus_win/SDKbuilder.zip (+/- 20MB)


- Conceito

O SB-2030E possui uma área de memória não volátil e protegida, usada exclusivamente para armazenamento de dados. Esta área pode possuir o tamanho de 512KB (modelo SB-2030E) até 2MB (modelo SB-2030EP).

O conceito de banco de dados no SB-2030E é o mesmo conceito que já conhecemos (Campos -> Registros -> Tabelas), com alguns detalhes que veremos a seguir:

a) ao criarmos uma tabela no SB-2030E, devemos determinar inicialmente o tamanho máximo de registros que esta tabela comportará;

b) a tabela depois de criada ou em uso, não poderá ser removida ou ter seu tamanho modificado e;

c) é possível criar até 128 tabelas no SB-2030E.

Usaremos estruturas para a criação de tabelas dentro do SB-2030E. Veja um exemplo:

/*
A estrutura Produto receberá registros formados pelos campos:

Codigo tipo char com 14 posições;
Descricao tipo char com 30 posições;
Aliquota tipo char com 3 posições e;
VlrUnitario tipo long.

que serão armazenados na tabela TblPro.
*/


struct Produto
{
char Codigo [ 14 ];
char Descricao [ 30 ];
char Aliquota [ 3 ];
long VlrUnitario;
};
.
.
.

Para que possamos definir o tipo (char, int, long e etc) e o tamanho de cada campo que a tabela conterá, precisamos usar uma formatação própria, da seguinte forma:

"XyXy"

onde:

"X" corresponde a primeira letra do tipo do campo (variável) em maiúscula (Char, Int, Long e etc) e;
"y" corresponde ao tamanho da área desejada.

Para entendermos melhor esta definição, vamos tomar como exemplo a estrutura Produto acima citada, onde temos 4 campos (variáveis), 3 são do tipo char e 1 do tipo long.

O formato de nossa tabela seria assim:

"C14C30C3L", 1000

onde:

C14 = char de 14 posições, usado para representar o primeiro campo "Codigo";
C30 = char de 30 posições, usado para representar o segundo campo "Descricao";
C3 = char de 3 posições, usado para representar o terceiro campo "Aliquota";
L= long, usado para representar o quarto campo "VlrUnitario" e;

1000 = quantidade de registros que a tabela armazenará.

Obs: Variáveis do tipo char são as únicas que usam definição de tamanho.

Vamos conhecer agora, as principais funções de acesso ao banco de dados que iremos usar em nosso exemplo.

- função formataSDK

A função formataSDK formata a memória não volátil destinada ao armazenamento dos dados, preparando-a para receber as tabelas e seus registros. Ao executarmos esta função, todas as tabelas existentes e seus registros serão apagados. Então, teremos que preparar uma rotina que verifique se a memória já está formatada, para que este processo não seja realizado toda vez que iniciar o sistema.

- função criatabelaSDK

A função criatabelaSDK cria uma tabela na memória não volátil, com um tamanho máximo que especificamos. Se a criação for bem sucedida, a tabela iniciará com 0 (zero) byte gravado. Caso a tabela já exista, receberemos um código de erro como retorno que poderemos tratar. Para que possamos criar as tabelas, a formatação da memória deve ter sido feita anteriormente.

- função addregistroSDK

A função addregistroSDK adiciona um novo registro ao final da tabela. Esta inclusão sempre será feita após o último registro existente na tabela. Caso for desejado, podemos exigir que o ponteiro da tabela fique posicionado sobre o novo registro incluído. Isso é configurado via parâmetro na função.

- função seekregistroSDK

A função seekregistroSDK posiciona o ponteiro da tabela sobre um determinado registro, utilizando um campo e uma condição definida, para localizá-lo. Se a pesquisa for bem sucedida, os campos contidos no registro localizado serão colocados na estrutura (struct) criada para a tabela. O campo usado para a pesquisa, poderá possuir um valor numérico (int) ou um texto (char). O início da pesquisa será determinado por um dos parâmetros da função e poderá ser realizada a partir do primeiro registro da tabela (após o BOF) ou a partir da posição atual. Para ambos os casos, a pesquisa só termina após encontrar o primeiro registro que satisfaça a condição imposta ou até atingir o fim da tabela (EOF). Ao localizar o registro, a tabela será posicionada, caso contrário o posicionamento atual não será afetado.

- função deleteregistroSDK

A função deleteregistroSDK apaga somente um determinado registro da tabela. Para isso, devemos primeiro posicionar o ponteiro da tabela sobre o registro que será excluído. Caso o ponteiro da tabela esteja posicionado sobre o <BOF> ou <EOF>, um código de retorno será enviado à função. Após a exclusão do registro, o ponteiro da tabela fica posicionado sobre o próximo registro, e se o registro deletado for o último, o ponteiro fica posicionado sobre o <EOF>.

- função positionregistroSDK

A função positionregistroSDK posiciona o ponteiro da tabela sobre um registro desejado. O ponteiro pode ser posicionado sobre qualquer registro compreendido entre <BOF> e <EOF>, mesmo que o registro esteja deletado. Esta função posiciona o ponteiro da tabela no registro para poder executar operações de escrita, consulta e exclusão. Se o número do registro a ser posicionado for maior que a quantidade total de registros da tabela, o ponteiro será posicionado sobre o <EOF>.

- função readregistroSDK

A função readregistroSDK lê um registro da tabela e coloca na estrutura (struct) criada para a tabela ou em uma nova estrutura, desde que possua os mesmos campos. Se o ponteiro da tabela estiver posicionado sobre o <BOF> ou <EOF> um código de retorno será enviado à função.

- função writeregistroSDK

A função writeregistroSDK escreve sobre um determinado registro eliminando o conteúdo anterior. Se o ponteiro da tabela estiver posicionado sobre o <BOF> ou <EOF>, um código de retorno será enviado à função.

- função zeratabelaSDK

A função zeratabelaSDK apaga todo o conteúdo da tabela especificada.

- função lestatustabelaSDK

A função lestatustabelaSDK retorna informações da tabela especificada, como:

Quantidade total de registros da tabela;
Quantidade de registros gravados na tabela;
Tamanho em bytes do registro e etc.

- Criando um projeto com banco de dados

Vamos iniciar um novo projeto no VisualSDK Builder (menu "File" - opção "New Project") para praticar as funções relacionadas ao banco de dados.

Obs: Não esqueça de configurar o ambiente para o modelo SB-2030E antes de compilar o projeto, e localizar o micro-terminal na rede antes de transferir a aplicação (menu "Project" - opção "Options" ).

O primeiro passo é criarmos a estrutura que estará recebendo os dados para posteriormente serem armazenados no banco. Para isso, abaixo das linhas de #include, insira a struct Produto da seguinte forma:

struct Produto
{
char Codigo [ 14 ];
char Descricao [ 30 ];
char Aliquota [ 3 ];
long VlrUnitario;
};


Ela será global e guardará informações sobre o código, descrição, alíquota e valor unitário de nosso produto.

Dentro do main(), vamos criar algumas variáveis que serão usadas no código e referenciar a struct.

void far main()
{
int iOpcao,
iResp,
iRet;

char cTempVlrUnitario[ 9 ];

struct Produto CampoProduto;
.
.
.


Criamos um menu de rolagem para executar as funções separadamente, assim poderemos entender melhor o funcionamento de cada uma e usá-las em nossos projetos futuros.

As opções de nosso menu de rolagem são: "Formata Memoria BD", "Cria Tabela", "Insere Registro", "Pesquisa Registro", "Apaga Registro" e "Status Tabela".

O código para a criação deste menu fica assim:

.
.
.
clrscrU();
getopcao( &iOpcao, 0, "<- Formata Memoria BD ->,<- Cria Tabela ->,<- Insere Registro ->,<- Pesquisa Registro ->,<- Apaga Registro ->,<- Status Tabela ->" );
.
.
.


Iremos tratar a escolha das opções dentro de um "switch/case", sendo que o "case 0" corresponde à opção "Formata Memoria BD", conforme abaixo:

.
.
.
switch( iOpcao )
{
case 0:
clrscrU();
printfU( "AVISO: Os dados serao apagados.\n" );
printfU( "Deseja continuar ?: " );
getopcao( &iResp, 0, "<- NAO ->,<- SIM ->" );
if ( iResp == 1 )
{
clrscrU();
centralizaU( 1, "<<< Formatando, aguarde... >>>" );
espera0( 1000 );
if ( ( formatSDK( 524288 ) ) != 0 )
{
clrscrU();
centralizaU( 1, "<<< PROBLEMAS NA FORMATACAO >>>" );
centralizaU( 2, "Pressione algo, p/ continuar..." );
getch();
}
else
{
clrscrU();
centralizaU( 1, "<<< FORMATACAO CONCLUIDA COM SUCESSO >>>" );
centralizaU( 2, "Pressione algo, p/ continuar..." );
getch();
}
}
break;
.
.
.


Repare que neste código, fazemos uma pergunta ao operador antes de iniciar a formatação da memória reservada ao banco de dados e testamos o retorno da função "formataSDK". Caso o retorno seja 0 (zero) significa que a formatação foi bem sucedida e exibimos uma mensagem de OK, caso contrário exibimos uma outra mensagem para o operador. O tamanho da memória neste exemplo, é de 512KB representados em bytes (524288 bytes). Caso o micro-terminal seja o modelo SB-2030EP, temos um pente de expansão de 2MB para o banco de dados, então este deverá ser formatado no tamanho de 2097152 bytes. O "case 1" corresponde à função "Cria Tabela":

.
.
.
case 1:
iRet = criatabelaSDK( "TblPro", "C14C30C3L", 1000 );
clrscrU();
switch( iRet )
{
case 1:
centralizaU( 1, "<<< TABELA JA EXISTE ! >>>" );
espera0( 2 SEGUNDOS );
break;
case 2:
centralizaU( 1, "<<< NOME TABELA ACIMA DE 6 CARACTERES ! >>>" );
espera0( 2 SEGUNDOS );
break;
case 3:
centralizaU( 1, "<<< QTDE MAX DE TABELAS ATINGIDA ! >>>" );
espera0( 2 SEGUNDOS );
break;
case 4:
centralizaU( 1, "<<< ERRO NA DECLARACAO DA STRUCT ! >>>" );
espera0( 2 SEGUNDOS );
break;
case 5:
centralizaU( 1, "<<< STRUCT ACIMA DE 100 CARACTERES ! >>>" );
espera0( 2 SEGUNDOS );
break;
case 6:
centralizaU( 1, "<<< QTDE REGISTROS ACIMA DA ESPECIFICADA ! >>>" );
espera0( 2 SEGUNDOS );
break;
case 7:
centralizaU( 1, "<<< MEMORIA NAO FORMATADA ! >>>" );
espera0( 2 SEGUNDOS );
break;
default:
centralizaU( 1, "<<< TABELA CRIADA COM SUCESSO ! >>>" );
espera0( 2 SEGUNDOS );
break;
}
break;
.
.
.


Na função criatabelaSDK estamos passando como parâmetros: o nome da tabela (TblPro), seu formato (C14C30C3I) e seu tamanho total de registros (1000). Estamos também analisando o retorno desta função, exibindo uma mensagem para cada situação ao operador.

No "case 2" adicionamos um registro à tabela "TblProd":

.
.
.
case 2:
memset( CampoProduto.Codigo, NULL, sizeof( CampoProduto.Codigo ) );
memset( CampoProduto.Descricao, NULL, sizeof( CampoProduto.Descricao ) );
memset( CampoProduto.Aliquota, NULL, sizeof( CampoProduto.Aliquota ) );
memset( cTempVlrUnitario, NULL, sizeof( cTempVlrUnitario ) );
CampoProduto.VlrUnitario = 0;

clrscrU();
setcursorU( ON );
printfU( "Codigo: " );
getstring( CampoProduto.Codigo, 13, NUMERICO, NULL, NULL );

if ( ( seekregistroSDK( "TblPro", (char*)&CampoProduto, (char*)&CampoProduto.Codigo, "=", "F" ) ) != 0 )
{
clrscrU();
printfU( "Descricao......: " );
getstring( CampoProduto.Descricao, 29, ALFA, NULL, NULL );
printfU( "\nCodigo Aliquota: " );
getstring( CampoProduto.Aliquota, 2, ALFA, NULL, NULL );
clrscrU();
printfU( "Valor Unitario: " );
getstring( cTempVlrUnitario, 8, FORMAT | REVERSO, " . , ", NULL );
setcursorU( OFF );

CampoProduto.VlrUnitario = atol( cTempVlrUnitario );

if ( ( addregistroSDK( "TblPro", (char*)&CampoProduto, 0 ) ) == 0 )
{
clrscrU();
setcursorU( OFF );
centralizaU( 1, "<< Produto cadastrado com sucesso ! >>" );
espera0( 2 SEGUNDOS );
}
else
{
clrscrU();
setcursorU( OFF );
centralizaU( 1, "<< Problemas com o cadastro ! >>" );
espera0( 2 SEGUNDOS );
}
}
else
{
clrscrU();
setcursorU( OFF );
centralizaU( 1, "<< Produto ja cadastrado ! >>" );
espera0( 2 SEGUNDOS );
}
break;
.
.
.

Neste case, inicializamos as variáveis da struct em memória com NULL (\0) e fazemos a primeira entrada de dados, através do código do produto (função getstring). Repare que antes de entrar com as outras informações do produto, fazemos uma pesquisa na tabela para verificar se o código do produto informado já está cadastrado (função seekregistroSDK). Caso o retorno seja diferente de 0 (zero), entramos com as demais informações (descrição, alíquota e valor unitário), caso contrário exibimos uma mensagem ao operador informando que o produto já se encontrada cadastrado na tabela, evitando que o mesmo seja duplicado. Esta é uma operação normal quando se trabalha com banco de dados!

Fazemos a pesquisa do produto cadastrado, no case 3:

.
.
.
case 3:
memset( CampoProduto.Codigo, NULL, sizeof( CampoProduto.Codigo ) );

clrscrU();
setcursorU( ON );
printfU( "Codigo: " );
getstring( CampoProduto.Codigo, 13, NUMERICO, NULL, NULL );
setcursorU( OFF );

if ( ( seekregistroSDK( "TblPro", (char*)&CampoProduto, (char*)&CampoProduto.Codigo, "=", "F" ) ) == 0 )
{
clrscrU();
printfU( "Descricao......: %s\n", CampoProduto.Descricao );
printfU( "Codigo Aliquota: %s ", CampoProduto.Aliquota );
printfU( "Valor: %l", CampoProduto.VlrUnitario );
getch();
}
else
{
clrscrU();
setcursorU( OFF );
centralizaU( 1, "<< Produto nao cadastrado ! >>" );
espera0( 2 SEGUNDOS );
}
break;
.
.
.


Esta rotina é bem simples! Inicializamos a variável CampoProduto.Codigo e fazemos a entrada do código do produto. Através da função seekregistroSDK (vista anteriormente), fazermos a busca deste código na tabela. Se o código for encontrado, exibimos a decrição, o código da alíquota e o valor unitário do produto, caso contrário exibimos uma mensagem para o operador.

No case 4, excluímos o produto:

.
.
.
case 4:
memset( CampoProduto.Codigo, NULL, sizeof( CampoProduto.Codigo ) );

clrscrU();
setcursorU( ON );
printfU( "Codigo: " );
getstring( CampoProduto.Codigo, 13, NUMERICO, NULL, NULL );
setcursorU( OFF );

if ( ( seekregistroSDK( "TblPro", (char*)&CampoProduto, (char*)&CampoProduto.Codigo, "=", "F" ) ) == 0 )
{
clrscrU();
iRet = deleteregistroSDK( "TblPro" );
switch( iRet )
{
case 1:
centralizaU( 1, "<< ERRO: Tabela inexistente ! >>" );
espera0( 2 SEGUNDOS );
break;
case 2:
centralizaU( 1, "<< ERRO: Encontrado <BOF> ! >>" );
espera0( 2 SEGUNDOS );
break;
case 3:
centralizaU( 1, "<< ERRO: Encontrado <EOF> ! >>" );
espera0( 2 SEGUNDOS );
break;
case 4:
centralizaU( 1, "<< AVISO: Produto ja excluido ! >>" );
espera0( 2 SEGUNDOS );
break;
default:
centralizaU( 1, "<< Registro excluido com sucesso ! >>" );
espera0( 2 SEGUNDOS );
break;
}
}
else
{
clrscrU();
setcursorU( OFF );
centralizaU( 1, "<< Produto nao cadastrado ! >>" );
espera0( 2 SEGUNDOS );
}
break;
.
.
.


Semelhante ao case 3, neste case fazemos a pesquisa do produto no banco de dados e caso seja encontrado, fazemos sua deleção (função deleteregistroSDK). É feito um tratamento de retorno para a função deleteregistroSDK exibindo mensagens para o operador.

E, no case 5 exibimos algumas informações do estado atual da tabela:

.
.
.
case 5:
if ( ( lestatustabelaSDK( "TblPro" ) ) == 0 )
{
clrscrU();
printfU( "Tamanho da Tabela: %l registro(s)", SDK_STATUS.QtdeTotalRegistros );
espera0( 2 SEGUNDOS );

clrscrU();
printfU( "Registros Gravados: %l registro(s)", SDK_STATUS.QtdeRegistrosGravados );
espera0( 2 SEGUNDOS );
}
else
{
clrscrU();
centralizaU( 1, "<<< TABELA INEXISTENTE >>>" );
espera0( 2 SEGUNDOS );
}
break;
}
} /* Fim do programa */


Aqui, apenas verificamos algumas situações, através da função lestatustabelaSDK, retornadas por constantes do banco de dados, como quantidade total de registros e o número de registros cadastrados na tabela.

André Luiz R. Munhoz

André Luiz R. Munhoz - Bematech: DSP - Desenvolvimento de Software e Parcerias.
Visite o site: http://www.bematech.com.br.