Situações que envolvem uma sequência de operações (cálculos complexos ou validação de registos que dependem da informação de outros registos) requerem a elaboração de programas definidos pelo utilizador. O Access dispõe de uma linguagem de programação: O Visual Basic for Applications (VBA) que é comum a outras aplicações do mesmo fabricante.
Para programarmos no Access usamos objectos Module (módulos). Na janela de cada módulo podemos definir procedimentos. Há dois tipos de procedimentos: sub-rotinas e funções. O exemplo a seguir contém a definição duma função que permite calcular a soma de dois números.
A função
Para executar a função vamos abrir a "Debug Window". Nesta janela podemos fazer testes aos procedimentos que definimos, entre outras coisas. Tendo um módulo aberto, podemos aceder à janela de debug a partir do menu View.
Depois de aberta a janela de debug escrevemos "
?soma(3, 4)". O Access vai chamar a função, e à variável que, na função, é designada por a, é atribuído o valor 3, à variável designada por b, o valor 4. A função retorna o valor 7 sendo este valor escrito na janela de debug.
A sub-rotina pode ser executada escrevendo call diz("Olá Mundo"). O argumento está entre aspas para ser identificado como texto.
Organização e Execução de Procedimentos
Cada módulo é constituído pela secção de declarações (mais adiante vamos ver o que são) e por um conjunto de procedimentos.
|
|
Qualquer procedimento, antes de poder ser utilizado tem que ser definido. A definição de um procedimento envolve:
|
Os procedimentos são de dois tipos, Funções e Subrotinas:
|
Funções |
Retornam um valor e podem ser utilizadas em expressões em qualquer objecto da Base de Dados |
Function nome(parâmetros)[ instruções] End Function |
|
SubRotinas |
Não retornam valores, e assim não podem ser utilizadas em expressões. Podem ser chamadas com Call <nome> a partir doutros procedimentos. |
Sub nome (parâmetros)[ instruções] End Sub |
As sub-rotinas podem ser chamadas a partir de qualquer procedimento, ou directamente a partir da janela de debug usando a instrução
call. As funções podem ser utilizadas em qualquer expressão.
Exemplo: A função soma pode ser usada num campo calculado de uma Query,:

Uma outra possibilidade é utilizar a função num formulário. Para isso, constrói-se um formulário com três controlos do tipo "TextBox". Os primeiros dois serão utilizados para os valores de entrada da função, e o terceiro para o resultado. Estes controlos terão os nomes "x", "y" e "resultado" respectivamente. No controlo "resultado" a propriedade Control Source deve ter o seguinte valor:
ControlSource:
=soma([ x] ,[ y] )
|
|
|
Uma função pode ser também usada por uma macro. Neste caso a caixa de texto "resultado" deveria ser "Unbound" e as caixas de texto "x" e "y" deveriam ter a propriedade:
AfterUpdade: Macro1
que dispara a macro (Macro1) que actualiza o valor de "resultado" utilizando a acção "SetValue" com os argumentos Item:[resultado] e Expression: soma([x],[y]).

A cada evento (click, update, etc) que possa suceder em cada objecto (formulários, controlos, relatórios, objectos definidos pelo utilizador, etc) está associada uma sub-rotina designada por event procedure (procedimento de evento). O programador pode alterar esta rotina mudando a sua definição. Em particular, esta rotina pode chamar outros procedimentos já definidos.
Exemplo: No formulário que nos mostra o resultado da função soma, podemos obter idêntica funcionalidade de forma diferente. Em vez de termos a propriedade ControlSource da caixa resultado com uma chamada à função soma, vamos usar um procedimento de evento.
Para isso, começamos por consultar as propriedades da caixa y. Na propriedade OnExit carregamos no botão com três pontos e escolhemos a opção CodeBuilder. aparece então um procedimento de evento com a definição vazia. O nome do procedimento é escolhido automaticamente a partir do nome do controlo (y) e do nome do evento (Exit). Agora basta acrescentar uma instrução de atribuição. Eis o procedimento de evento final:
Private Sub y_Exit(Cancel As Integer)
[resultado] = soma([x],[y])
End Sub
Nota: A funcionalidade do formulário não vai ser exactamente a mesma, pois o valor na caixa resultado só é alterado quando o cursor sai da caixa y. u
Os procedimentos de evento encontram-se em módulos especiais associados a objectos do Access, em vez de estarem nos módulos standard de que falamos acima. Para vermos/alterarmos procedimentos de evento associados, por exemplo, a um formulário, escolhemos a opção Code do menu View com o formulário em modo de construção (design).
Algumas noções básicas
Vejamos a seguinte sub-rotina de nome conta_até.
Sub conta_até(n As Integer)
Dim i As Integer
i = 1
Do While i <= n
Debug.Print i
i = i + 1
Loop
MsgBox "contei até " & n
End Sub
Esta sub-rotina, quando chamada com um argumento inteiro k, escreve na janela de debug todos os inteiros de 1 até k. Depois disso faz aparecer uma janela com a mensagem "contei até k". Nesta rotina é utilizada a variável i. Esta variável é, em primeiro lugar, declarada como inteira
Dim i As Integer
Depois é-lhe atribuído o valor 1 através de uma instrução de atribuição
i = 1
A seguir é executado um ciclo Do While-Loop que, para além de escrever o valor corrente da variável i, vai aumentar o seu valor em uma unidade. Isto é repetido enquanto o valor corrente de i for menor ou igual do que o k dado. No final i tem o valor k+1.
Este exemplo ilustra algumas das operações típicas com uma variável:
Apesar de se poderem utilizar variáveis sem as declarar, isso não é aconselhável. Declarar as variáveis permite, entre outras coisas, evitar erros de programação difíceis de detectar.
As declarações podem ser efectuadas na secção de declarações do módulo ou no início da definição de cada procedimento. Como adiante veremos, o local onde são feitas as declarações afecta a visibilidade das variáveis. Uma instrução de declaração típica tem o seguinte formato:
DIM <variável> [As <Tipo>]
onde "<tipo>" representa o tipo de variável. O Access permite declarar variáveis de vários tipos, incluindo:
Integer Números inteiros pequenos (de -32768 a 32767, 2 bytes)
Long Números inteiros maiores (de -2147483648 a 2147483647, 4 bytes)
Single Números reais pequenos
Double Números reais de maior precisão
Currency Números que representam valores monetários
String Texto
Variant Qualquer tipo
Não é aconselhável omitir declarações e/ou utilizar o tipo "Variant", pois essas podem constituir uma fonte de erros difíceis de detectar. Além da forma acima é possível declarar variáveis utilizando sufixos. Por exemplo, o símbolo "%" utilizado a seguir ao nome da variável indica que essa é de tipo "Integer", como em X% = 1. De maneira semelhante, o símbolo "$" indica que a variável é de tipo "String" etc.
O Access permite definir vectores ou matrizes, isto é, variáveis que podem conter um conjunto finito de valores que partilham o mesmo nome e o mesmo tipo de dados. Os diferentes elementos da matriz ("array") são acedidos por um índice que indica a ordem do elemento dentro da matriz. As matrizes têm de ser previamente declaradas com DIM:
|
DIM notas(9) As Integer |
Define um vector com 10 elementos |
|
DIM notas(4,4) As Integer |
Define uma matriz de 5 colunas por 5 linhas |
No primeiro exemplo é criada uma variável
notas do tipo inteiro com 10 elementos. A expressão "notas(0)" acede ao 1º elemento do array e "notas(1)" ao 2º.Visibilidade das Variáveis
As variáveis podem ser declaradas quer no interior de um procedimento quer na secção de declaração dos módulos. A maneira como uma variável é definida determina a sua visibilidade isto é, a possibilidade de alterar o valor dessa variável ou de a utilizar num determinado ponto do programa. Às vezes, utiliza-se, neste contexto, também o termo âmbito ou "scope". Assim, e de acordo com a sua visibilidade, iremos ter três tipos de variáveis:
Local
: Variável declarada num procedimento. É visível apenas no procedimento onde foi declarada. Para os outros procedimentos, esta variável não existe. Podemos inclusivamente ter duas variáveis locais com o mesmo nome em procedimentos diferentes. Apesar de terem o mesmo nome são duas variáveis distintas.
Módulo
: Variável declarada com DIM na secção de declarações do módulo. É visível por todos os procedimentos desse módulo.
Global
: Variável declarada como Public na secção de declarações de um módulo. É visível por todos os procedimentos de qualquer módulo.
Os valores 3.1415 ou "abc" representam constantes. Estes podem ser associados a nomes simbólicos, como por exemplo:
CONST pi = 3.1415
As constantes True, Yes e ON são predefinidas e têm o valor -1. De maneira semelhante, as constantes False, No, Off têm o valor 0. As constantes podem ter diferentes visibilidades de forma idêntica à das variáveis.
Um objecto representa um elemento do Access. Por exemplo, uma tabela, um formulário, um controlo ou uma query são objectos. A linguagem de programação VBA permite manipular os objectos do Access.
Cada objecto dispõe de um conjunto de propriedades e de métodos. Uma propriedade de uma tabela é por exemplo o seu nome. Um método representa uma acção que podemos realizar sobre o objecto e é utilizado de forma idêntica a uma sub-rotina. Podemos, por exemplo, ter um método Delete para apagar um registo de uma tabela.
Para nos referirmos à propriedade
prop de um objecto obj usamos a sintaxe:
obj.prop
De forma idêntica, para executarmos o método
mtd de um objecto obj usamos a expressão
obj.mtd
Uma colecção contém um conjunto de objectos. Por exemplo a colecção Forms contém o conjunto dos formulários definidos na base de dados. Para nos referirmos a um elemento
obj de uma colecção col usamos a sintaxe
col!obj
Exemplo: Suponhamos que dentro de um procedimento necessitamos de alterar a título de um formulário
form7 para "Novo". Para isso usamos a instrução de atribuição
Forms![form7].Caption = "Novo"
Ou seja, a propriedade
Caption do objecto form7 pertencente à colecção Forms passa a ter valor "Novo".
Um procedimento é definido por um conjunto de instruções, que são executadas sequencialmente. Vejamos os tipos de intruções mais importantes.
Em geral a operação de atribuição tem a forma:
<Variável> = <Expressão>
Numa instrução deste tipo, a expressão do lado direito do símbolo "="é avaliada e o resultado passa a ser o valor actual da variável cujo nome aparece no lado esquerdo. Se a variável já tiver um valor, este é substituído pelo resultado da expressão (o anterior valor é perdido).
Por vezes é conveniente alterar o modo de execução sequencial. Assim, poderemos ter situações em que um conjunto de instruções só é executado se determinada condição for verificada. Outras vezes teremos situações em que se pretende executar repetidas vezes o mesmo grupo de instruções.
O VBA, tal como grande parte das linguagens de programação ditas imperativas, contém estruturas de controlo que possibilitam determinar a sequência de instruções a ser executada. Iremos ver estruturas de decisão que permitem executar condicionalmente um conjunto de instruções e estruturas iterativas que permitem executar um conjunto de instruções de maneira repetida.
|
If <condição> Then <Instrução>
ou
If <condição> Then <bloco de instruções> End If |
|
|
If <condição> Then <bloco de instruções> Else <bloco de instruções> End If |
|
Exemplos:
|
Function maior2(a As Integer, b As Integer) ‘Retorna o maior de 2 números If a > b Then maior2 = a Else maior2 = b End If End Function |
Function maior3(a,b,c) ‘Retorna o maior de 3 numeros If a > b Then If a > c Then maior3 = a Else maior3 = c End If Else If b > c Then maior3 = b Else maior3 = c End If End If End Function |
A estrutura selectiva
permite seleccionar uma de várias possibilidades. Se pretender escolher uma de várias alternativas, pode utilizar as estruturas If-Else. No entanto, a estrutura "Select Case" que, por ser mais compreensível, é mais aconselhável.
|
Select Case <expressão> Case <valor1>: <bloco de instruções 1> Case <valor2>: <bloco de instruções 2> ... [Case Else: <bloco de instruções N>] End Select |
|
No fluxo do programa, ao encontrar uma estrutura "Case", a expressão do "Select Case" é avaliada. O resultado obtido é comparado com o valor associado aos sucessivos casos. É executado o bloco de instruções do primeiro caso com valor associado igual ao resultado da expressão e o fluxo do programa continua na instrução imediatamente a seguir ao "End Select" (O fluxo é representado pelos diagramas do lado direito das tabelas.)
Se nenhum caso é satisfeito e se houver um "Case Else" (que deverá ser o ultimo a ser declarado), então o bloco de instruções associado este caso é executado. Se nenhum caso é satisfeito e se não existir um "Case Else", nenhum dos blocos de instruções é executado e o fluxo do programa continua na instrução imediatamente a seguir ao "End Select".
Exemplos:
|
Function dia (D) Select Case WeekDay(D) Case 1: dia = "Domingo" Case 2: dia = "Segunda" Case 3: dia = "Terca" Case 4: dia = "Quarta" Case 5: dia = "Quinta" Case 6: dia = "Sexta" Case 7: dia = "Sabado" End Select End Function |
Function desconto(valor) Select Case valor Case Is < 4 desconto = 0 Case Is < 8 desconto = 0.1 Case Else desconto = 0.15 End Select End Function |
Para testar as funções, execute na janela "Debug Window":
|
?dia(#1-1-95#) Domingo |
?desconto(10) 0.15 |
A execução repetida de um conjunto de instruções pode ser feita pelas estruturas:
|
Do While <condição> <bloco de instruções> Loop |
Enquanto a <condição> for verificada executa o bloco de instruções |
|
|
Do Until <condição> <bloco de instruções> Loop |
Até a <condição> ser verificada executa o bloco de instruções |
|
Ao encontrar uma estrutura "Do While", a condição associada é avaliada. Se a condição for verdadeira o bloco de instruções é executado sequencialmente. Ao encontrar a palavra "Loop", que indica o fim do conjunto de instruções a serem repetidas, o fluxo do programa passa novamente para a instrução "Do While" e a condição é novamente avaliada. Se a condição for verdadeira, o bloco de instruções é executado mais uma vez, continuando assim enquanto a condição for verdadeira.
Para a estrutura "Do Until" o funcionamento é semelhante, apenas que, o bloco de instruções é executado enquanto a condição for falsa.
Na implementação de estruturas iterativas, deve ter em atenção duas situações:
- os ciclos que nunca começam (no caso do DoWhile a condição é inicialmente falsa),
- os ciclos que nunca acabam (no caso do Do While a condição nunca se torna falsa).
Se o segundo caso acontecer, o terminal ficará bloqueado, pois o programa entrou em "ciclo infinito". Este pode ser interrompido com as teclas Control + Break.
Para evitar ciclos infinitos, deve prestar atenção a condição associada ao ciclo. A condição determina quando este começa ou acaba. Em geral, o valor destas variáveis deve ser alterado no bloco de instruções que constituem o ciclo.
Exemplos de Programas
|
Tabuada | |
|
Function tabuada(x As Integer) Dim y As Integer Debug.Print "Tabuada dos..", x y = 1 Do While y < 10 Debug.Print x,"*",y,"=",x*y y = y + 1 Loop End Function |
|
|
Máximo Divisor Comum Function mdc(x As Integer,y As Integer)Do While x <> y If x > y Then x = x - y Else y = y - x End If Loop mdc = x End Function |
A função mdc(x,y) calcula o máximo divisor comum de dois números, utilizando o algoritmo de Euclides:
Enquanto x for diferente de y, faz: Se x maior que y x = x - y Senão y = y - x |
|
Inverte Uma String |
|
|
Function inverte(s As String)As String DIM n$, i As Integer i = Len(s) Do Until i = 0 n$ = n$ & Mid$(s,i,1) i = i - 1 Loop inverte = n$ End Function |
A função inverte(s)escreve a cadeia de caracteres s de trás para a frente.A função utiliza duas variáveis auxiliares n$ e i. A variável n$ vai acumulando o resultado parcial na execução do ciclo, enquanto i indica o próximo caracter a ser passado de s para n$.
A função inverte("abc") retorna "cba".Os sucessivos valores das variáveis são: n$ i Mid$(s,i,1) "" 3 Mid$("abc",3,1)="c" "c" 2 Mid$("abc",2,1)="b" "cb" 1 Mid$("abc",1,1)="a" "cba" 0 |
Uma outra estrutura iterativa (vocacionada para processar "arrays"), é a estrutura "For". A sintaxe é a seguinte:
|
For contador = valor_inicial To valor_final [ Step incremento] <bloco de instruções> Next |
Quando no fluxo do programa é encontrado um ciclo For, à variável declarada como contador é atribuído o valor inicial. Enquanto o valor do contador for menor que o valor final o bloco de instruções entre o For e o Next é executado. Quando encontrar a linha Next, à variável contador é acrescentado o valor do incremento a seguir de Step. Note que o incremento associado ao Step é opcional, e se não for declarado, é assumido o valor 1.
Exemplo:
|
Function iniciar(n(), max As Integer) Dim i As Integer For i = 0 To max n(i) = Int(Rnd * 10) Next End Function |
Na secção de declarações: Dim n(9) |
O funcionamento da função "
iniciar" pode ser descrito como:Á variável i, utilizada como contador é atribuido o valor inicial 0.
Enquanto o valor de i for menor que o valor de max executa:
atribui ao i-ésimo elemento do array o valor da expressão
Int(Rnd * 10);incrementa o valor de i (i = i + 1).
De uma forma geral, um ciclo "For" pode ser simulado utilizando um ciclo "Do While", tal como é exemplificado:
|
|
contador = Valor_inicial Do while contador < valor_final <bloco de instruções> contador = contador + incremento Loop |
A comunicação com o utilizador pode ser efectuada utilizando as funções MsgBox(..) e InputBox(..).
MsgBox(mensagem [, tipo [, título] ] ) mostra a mensagem numa caixa de diálogo e espera que o utilizador escolha um dos botões disponíveis. A função retorna um valor indicando qual o botão escolhido.
|
|
O segundo argumento, "tipo", permite escolher quer os botões quer o ícone que a caixa de diálogo apresenta. No exemplo foi escolhido 68. Consulte "Help" "MsgBox function" para outros valores, assim como para o significado dos valores que a função retorna. |
InputBox(mensagem, [título], [Valor_por_defeito]) mostra uma caixa de diálogo e espera que o utilizador escreve um valor e/ou escolha um botão. A função retorna o valor digitado (tipo "Variant").
|
|
nota = InputBox("Qual a nota ?") |
Todos os objectos criados pelo utilizador no Access podem ser criados e manipulados em VBA. Nesta secção consideramos objectos de tipo "DataBase" (Base de Dados) e "Recordset" (conjunto de registos).
Quando se pretende aceder directamente à informação das tabelas, é necessário declarar as variáveis correspondentes.
Isso pode ser feito, por exemplo, na secção de declarações do módulo de seguinte maneira:
Dim db As DataBase
Dim tb As Recordset
onde "
db" e "tb" são duas variáveis que escolhemos aqui (poderíamos ter usado outras). Estas variáveis vão-nos permitir referirmo-nos a uma base de dados e a um conjunto de registos (por exemplo uma tabela) a partir de um programa.
A tabela pode ser aberta dentro da janela "Debug Window" utilizando as seguintes instruções:
Set db = CurrentDb()
Set tb = db.OpenRecordset("
<tabela>")
onde <
tabela> representa o nome da tabela escolhida. Assim, se quisermos abrir a tabela "alunos", a segunda instrução seria:
Set tb = db.OpenRecordset("alunos")
A função
CurrentDb() permite aceder à base de dados corrente. A função OpenDatabase(..)permite aceder a uma Base de Dados específica. As variáveis "db" e "tb" representam referências (apontadores) para os respectivos valores. A atribuição da referência à variável é feita utilizando a instrução Set .
Após a execução das instruções acima, a variável tb aponta para o 1º registo da tabela indicada. É possível aceder a esta informação, executando na "Debug Window", o comando:
?tb![ <campo>]
que permite visualizar o valor do primeiro registo do campo indicado. Assim, para visualizar, por exemplo, o campo "nome", a instrução
seria:?tb![ nome]
Após serem efectuadas as operações pretendidas, é aconselhável "fechar" a tabela utilizando o comando:
tb.Close
Abrir uma tabela permite aceder à informação nela contida. Em cada instante só é possível aceder à informação de um registo designado por registo corrente (rc). Quando a tabela é aberta o registo corrente é o 1º registo da tabela. O comando
tb.MoveNext permite tornar o seguinte registo corrente:
|
tb.MoveNext |
torna corrente o registo seguinte. Se o rc é o ultimo, MoveNext atribui à propriedade EOF (End of File) o valor TRUE e rc fica indefinido. |
|
tb.MovePrevious |
torna corrente o registo anterior. Se o rc é o primeiro, MovePrevious atribui à propriedade BOF (Begin of File) o valor TRUE e rc fica indefinido. |
As seguintes instruções podem ser utilizados para "saltar" para o início (ou fim) da tabela:
|
tb.MoveFirst |
torna corrente o 1º registo |
|
tb.MoveLast |
torna corrente o ultimo registo |
Considere o seguinte procedimento que permite mostrar o conteúdo da tabela "alunos":
|
Sub percorre() alunos.MoveFirst Do Until alunos.Eof Debug.Print alunos![ nome] alunos.MoveNext Loop End Sub |
Posiciona no 1º registo Processa toda a tabela Escreve o conteúdo do campo "nome" Salta para o próximo registo
|
Para utilizar o procedimento, devemos antes acrescentar nas declarações do módulo:
Dim db as DataBase
Dim alunos as Recordset
Na "Debug Window" temos de abrir a base de dados assim como a tabela. Note-se que estas duas intruções set poderiam estar também, por exemplo, dentro da definição da sub-rotina percorre.
Set Db = Currentdb()
Set alunos = db.OpenRecordset("alunos")
Agora chamamos a rotina na "Debug Window".
call percorre
Como resultado da execução, irão aparecer os nomes dos alunos escritos na janela de debug.
Exemplo: Podemos agora partir da sub-rotina percorre para obter uma função que conta o número de registos de uma tabela dada como argumento. Aqui decidimos fazer as declarações dentro da própria função.
Function conta(tabela As String) As Integer
Dim db As Database, rs As Recordset
Dim n As Integer
Set db = CurrentDb()
Set rs = db.OpenRecordset(tabela)
n = 0
rs.MoveFirst
Do Until rs.EOF
n = n + 1
rs.MoveNext
Loop
conta = n
rs.Close
End Function
O argumento da função é uma string com o nome da tabela da qual queremos contar os registos. A variável n é uma variável contador, que começa por ter o valor 0 sendo incrementada à passagem de cada novo registo. No final o valor de n é retornado.
Para contarmos o número de registos da tabela alunos invocamoes a função na janela de debug:
?conta("alunos")
14
Nota: O objecto recordset tem uma propriedade RecordCount que guarda o número de registos. Por isso, poderíamos ter definido a função utilizando a instrução:
conta = rs.RecordCount
Que linhas poderíamos apagar?
Procurar os registos que satisfazem determinada condição, é das operações mais frequentes na manutenção de Bases de Dados. As instruções que permitem efectuar esta operação são:
|
<tabela>.FindFirst <critério> |
Localiza o 1º registo que satisfaz o critério |
|
<tabela>.FindLast <critério> |
Localiza o último registo que satisfaz o critério |
|
<tabela>.FindNext <critério> |
Localiza o próximo registo que satisfaz o critério |
|
<tabela>.FindPrevious <critério> |
Localiza o registo anterior que satisfaz o critério |
O critério é uma expressão do tipo "String" que contém uma condição lógica envolvendo os nomes dos campos do conjunto de registos.
Exemplo:
A sub-rotina
localiza mostra na "Debug Window" o nome de todos os alunos que satisfazem um dado critério:
Sub localiza ()
alunos.FindFirst "[ turma] = ‘TP01’"
Do Until alunos.NoMatch
Debug.Print alunos![ nome]
alunos.FindNext "[ turma] = ‘TP01’"
Loop
End Sub
Execução na Debug Window:
Set db = CurrentDb()
Set alunos = db.OpenRecordset("alunos", dbOpenDynaset)
call localiza
Note que a tabela foi aberta dando mais um argumento a OpenRecordset. Este argumento extra (
dbOpenDynaset) indica ao Access que queremos abrir a tabela como um conjunto de registos dinâmico. Isto é necessário para podermos usar os métodos FindFirst, FindNext, etc.À propriedade NoMatch é atribuido o valor TRUE quando a instrução (FindNext etc.) falha na localização do registo.
A instrução FindFirst (FindNext etc.) percorre a tabela virtual registo a registo. Para cada registo testa a condição e torna esse registo corrente se o registo satisfaz a condição.
A versão da rotina
localiza apresentada a seguir permite passar o critério através de um parâmetro.
Sub localiza (criterio$)
alunos.FindFirst criterio$
Do Until alunos.NoMatch
Debug.Print alunos![ nome]
alunos.FindNext criterio$
Loop
End Sub
Execução na janela de debug:
Set db = CurrentDb()
Set alunos = db.OpenRecordset("alunos", dbOpenDynaset)
call localiza("[turma] = ‘TP01’")
Exemplo
: Suponhamos que temos na base de dados corrente uma tabela "Encomendas" com vários campos, entre os quais "Cliente" e "Valor". Vamos definir uma função que indique o total de encomendas, em valor, para um dado cliente.
Function soma_encomendas(Cliente As Long) As Single
Dim db As Database
Dim rs As Recordset
Dim soma As Single
Dim criterio As String
Set db = CurrentDb
Set rs = db.OpenRecordset("Encomendas", dbOpenDynaset)
criterio = "[Cliente]=" & Cliente
soma = 0
rs.FindFirst criterio
Do Until rs.NoMatch
soma = soma + rs![Valor]
rs.FindNext criterio
Loop
soma_encomendas = soma
End Function
A variável soma tem o papel de acumulador. Para os seguintes valores da tabela encomendas:
|
Cliente |
Valor |
|
1 |
3200 |
|
1 |
4600 |
|
2 |
1800 |
|
3 |
900 |
|
7 |
4500 |
|
2 |
1500 |
|
1 |
2100 |
|
2 |
1000 |
Temos o seguinte resultado
?soma_encomendas(1)
9900
As operações com objectos de bases de dados que temos visto, incidiram sobre o acesso à informação. Muitas vezes há necessidade de editar essa informação. Nesta secção iremos ver como proceder para alterar o valor de campos, eliminar registos e inserir novos registos numa tabela.
Editar Informação
Alterar o valor de campos num registo envolve três passos:
1) Utilizar a instrução EDIT para preparar o registo;
2) Atribuir aos campos os valores pretendidos;
3) Utilizar a instrução UPDATE para efectivar as alterações.
Exemplo:
Sub altera (xbi, xnota)
alunos.FindFirst "[NumBI]=" & xbi
If Not alunos.NoMatch Then
alunos.Edit
alunos![ nota] = xnota
alunos.Update
End If
End Sub
Eliminar Registos
Para eliminar um registo completo, é utilizada a instrução Delete.
A rotina
elimina(bi) procura o aluno cujo número é "xbi" e retira este caso da tabela:
Sub elimina (xbi)
alunos.FindFirst "[NumBI]=" & xbi
If Not alunos.NoMatch Then
alunos.Delete
End If
End Sub
A rotina elim_todos() apaga todos os registos de uma tabela:
Sub elim_todos ()
alunos.MoveFirst
Do Until alunos.EOF
alunos.Delete
alunos.MoveNext
Loop
End Sub
Inserir novos registos
Inserir novos registos numa tabela envolve três passos:
1) Utilizar o método AddNew para criar o registo;
2) Atribuir aos campos os valores pretendidos;
3) Utilizar o método Update para efectivar o novo registo.
Exemplo:
Sub inserir (xbi, xnome, xnota)
alunos.AddNew
alunos![ bi] = xbi
alunos![ nome] = xnome
alunos![ nota] = xnota
alunos.Update
End Sub
Os procedimentos podem ser associados a controlos de formulários. A função pode ser utilizada para validar, por exemplo, o número de bilhete de identidade digitado na "Text Box". Se o nome desse controlo for "bi", e a função tiver o nome "valida", a condição para validação seria "=valida(bi)". Além disso, é necessário declarar que função deve ser chamada pelo evento "OnUpdate". O parâmetro passado à função de validação tem que ser de tipo "Control":
Function valida (bi As Control)
...
End Function
Versão Recursiva
Function mdc(x As Integer,y As Integer)
If x > y Then mdc = mdc(x-y, y)
If y > x Then mdc = mdc(x, y-x)
If x = y Then mdc = x
End Function
|
Versão iterativa |
Versão Recursiva |
|
Function modulo (a, b) Do While b >= a b = b - a Loop modulo = b End Function |
Function modulo (a, b) If a > b Then modulo = b If a <= b Then modulo = modulo(a, b - a) End Function |
A função factorial pode ser calculada de duas maneiras:
Método Iterativo Método Recursivo
X! = X * (X-1) * (X-2) * ... * 3 * 2 * 1 0! = 1
X! = X * (X - 1)!
São apresentadas duas funções, que implementam os métodos apresentados:
|
Versão iterativa |
Versão Recursiva |
|
Function facti (x) resultado = 1 Do While x > 1 resultado = x * resultado x = x -1 Loop facti = resultado End Function |
Function factr (x) If x = 0 Then factr = 1 Else factr = x * factr(x - 1) End If End Function |
|
Trace para facti(4) |
Trace para factr(4) |
|
x resultado x > 1 4 1 TRUE 3 4 TRUE 2 12 TRUE 1 24 FALSE |
x fact(x) 4 fact(4) = 4 * fact(3) 3 fact(3) = 3 * fact(2) 2 fact(2) = 2 * fact(1) 1 fact(1) = 1 * fact(0) 0 fact(0) = 1 1 fact(1) = 1 * 1 2 fact(2) = 2 * 1 3 fact(3) = 3 * 2 4 fact(4) = 4 * 6 |
O valor da função ex pode ser expandida em série:
ex=å xn/n!
ex=1+x/1+x2/2!+x3/3!+...
ex=1+x/1+(x/1)*(x/2)+(x/1)*(x/2)*(x/3)+...
|
Function e(x) n = 1 t = 1 y = 1 delta = .0001 Do While t / y > delta t = t * x / n y = y + t n = n + 1 Loop e = y End Function |
‘Nr. de termos ‘Valor de ex ‘Valor do termo n ‘Erro aceitável ‘Enquanto o erro for maior que o erro `aceitável ‘Calcula o novo termo ‘acumula o resultado ‘termo seguinte |
|
Option Compare Database Dim n(9)
Function mostra (n(), max) For i = 0 To max Debug.Print n(i) Next End Function
Function preenche (n(), max) For i = 0 To max n(i) = Int(Rnd * 10) Next End Function |
|
|
Function ordenar (a(), max) fim = False Do While Not fim fim = True For i = 0 To max - 1 If a(i) > a(i + 1) Then v = a(i) a(i) = a(i + 1) a(i + 1) = v fim = False End If Next Loop End Function |
A operação de ordenar um conjunto de valores é frequentemente utilizada em aplicações. Existem vários algoritmos que efectuam essa operação. O algoritmo utilizado na função
ordenar(), é conhecido por "Bubble Sort". Pode ser descrito como:
Repetir enquanto houver trocas
Percorrer todo o vector.
Cada elemento i do vector é comparado com o elemento seguinte i+1
se a(i) > a(i+1) os elementos são trocados
Supondo que o vector inicial contem a sequência (3,4,1,2), os sucessivos valores do vector são:
(3,4,1,2) -> (3,1,4,2) -> (3,1,2,4) -> (1,3,2,4) -> (1,2,3,4)
Quando atingir (1,2,3,4) não haverá mais trocas, pelo que o algoritmo termina.
Pense num número entre 0 e 100. A função descobre() vai descobrir o número por sucessivas tentativas. Em cada tentativa escolhe um valor e pergunta ao utilizador se o número em causa é maior ou menor que o valor escolhido. Perante a resposta do utilizador, a função reduz o espaço de possibilidades, pelo que rapidamente encontra o número a descobrir.
A função utiliza duas variáveis auxiliares min e max que contêm, respectivamente, o menor e o maior valores possíveis. Inicialmente o valor destas variáveis é 0 para min e 100 para max. Enquanto não descobre o número, a função tenta um valor x, valor médio do intervalo definido por [ min;max] . Este valor é utilizado na pergunta ao utilizador. Se a resposta do utilizador for "Yes" o intervalo de valores possíveis passa a ser [ x+1; max] , caso contrário [ min; x] . Quando min é igual a max, foi encontrado o número, pelo que a função termina.
|
Function descobre () Dim x As Integer, min As Integer, max As Integer min = 0 max = 100 Do While min <> max x = min + Int((max - min) / 2) Select Case MsgBox("O nr é maior que " & x & "?", 36) Case 6: min = x + 1 'Resposta Yes Case 7: max = x 'Resposta No End Select Loop MsgBox ("Descobri ! É:" & min) End Function |
Para o intervalo de 0 a 100 são necessários, no máximo, log2(100) » 7 perguntas. Para o intervalo de 0 a 1000 são necessárias, no máximo, log2(1000) » 10 perguntas.
Nesta secção falamos do método Seek, que permite fazer alguns tipos de procura de registos. Em comparação com os métodos Find já descritos, o método Seek é menos poderoso (não pode usar critérios arbitrários) mas tem tendência a ser mais eficiente.
A função percorre() permite apresentar os dados da tabela numa determinada ordem. Essa, em geral é determinada pela chave primária ("Primary Key") associada com a tabela. No entanto, o utilizador pode pretender ordenar os dados de outra maneira. Como podemos então ver os registos ordenados pelo valor crescente de um outro campo?
Em primeiro lugar, é necessário definir as chaves, criando um ou mais índices aquando da definição da estrutura da tabela, atribuindo à propriedade "Index" do respectivo campo o valor "Yes".
Cada índice permite efectuar as operações de procura de uma forma bem mais eficiente e rápida, devendo ser utilizado quando as operações de procura são feitas frequentemente.
O índice pode ser activado com a instrução:
<tabela>.Index = "<campo>"
onde
"<campo>" identifica a chave associada com o campo. Se, por exemplo, estivesse criado o índice para o campo "turma" da tabela "tb", poderá utilizar a seguinte instrução para activá-lo. tb.Index = "turma"
Os registos da tabela serão ordenados pelo valor crescente da chave, utilizando o respectivo índice na ordenação. A chave primária ("Primary Key") pode ser activada simplesmente através de:
<tabela>.Index = "PrimaryKey"
Assim, se préviamente à chamada da função percorre(), for estabelecido um índice, a função irá mostrar os registos ordenados pela chave escolhida.
Exemplo:
Na "Debug Window":
Set Db = Currentdb()Set alunos = db.OpenRecordset("alunos")
alunos.Index = "PrimaryKey"
?percorre() (Mostra os alunos ordenados por nome)
alunos.index = "Turma"
?percorre() (Mostra os alunos ordenados por turma)
A instrução SEEK permite localizar um dado valor, procurando a informação utilizando o índice corrente. O primeiro parâmetro define o tipo de comparação pretendida que pode ser por exemplo "=" (encontra o valor igual). O parâmetro seguinte indica o valor do campo que que será utilizado na procura (ex. "tp01").
Antes de utilizar esta instrução é necessário activar o índice pretendido, relacionado com o valor utilizado na procura. Se por exemplo, pretende procurar o valor de uma turma, deve activar o índice "turma":
Set db = CurrentDb()
Set alunos = db.
OpenRecordset("alunos")alunos.index = "turma"
(Este passo pressupõe que na definição da tabela "alunos", nas propriedades do campo "turma" foi declarado "Index : Yes (duplicates OK)" ).
O seguinte exemplo mostra a definição da função percorre(..) que inclui a instrução
alunos.Seek "=", turma$. Se a função for chamada na janela "Debug Window", com ?encontra("tp01"), o parametro turma$ teria o valor ("tp01"), e assim, o sistema mostraria os nomes dos alunos desta turma no écran.
Function encontra (xturma$)
alunos.Seek "=", xturma$
Do While alunos![ turma] = xturma$
Debug.Print alunos![ nome]
alunos.MoveNext
Loop
End Function
A maior parte das acções disponíveis nas macros, são possíveis de executar em procedimentos, utilizando o comando DoCmd. A possibilidade de passar parâmetros e controlar erros, são as vantagens de procedimentos por oposição às macros. A sintaxe do comando é:
DoCmd acção [ argumentos]
onde acção é uma das acções disponíveis nas macros e argumentos é a lista de argumentos (separados por vírgulas) que a acção requer. Tal como nas macros, o utilizador poderá não definir todos os argumentos de uma acção; o Access assume o valor por omissão para esse argumento, mas deverá colocar as vírgulas correspondentes.
Exemplos:
|
Posiciona no primeiro registo |
Posiciona no ultimo registo |
|
Sub ultimo () DoCmd GoToRecord , , A_LAST End Sub |
Sub primeiro () DoCmd GoToRecord , , A_FIRST End Sub |
A_Last
e A_First são constantes pré-definidas no Access.
Situações em que a execução de uma acção dá origem a erro não são controláveis em "Macros", mas são controláveis no VBA. As situações de erro surgem, por exemplo, quando se tenta posicionar num registo que não existe ou executar uma opção de um menu que não está disponível ou abrir uma tabela que foi aberta em modo exclusivo por outro utilizador, etc.
Exemplo:
Num formulário, pretende um botão de comando que torna corrente o registo seguinte. O procedimento "
seguinte()" associado à propriedade "OnClick" do botão, efectua a operação desejada:
|
Sub seguinte () DoCmd GoToRecord , , A_NEXT End Sub |
Este procedimento dá origem a erro, se for executado quando está no fim da fonte de registos.
|
Sub seguinte () On Error GoTo seguinte_erro DoCmd GoToRecord , , A_NEXT seguinte_exit: Exit Sub seguinte_erro: MsgBox "Registo inexistente" Resume seguinte_exit End Sub
|
A linha "On Error" indica ao Access o que fazer, no caso de ocorrer um erro, quando o procedimento é executado. Neste caso indica "GoTo seguinte_erro", ou seja, no caso de erro continua a execução do procedimento na instrucção imediatamente a seguir à etiqueta "seguinte_erro". |
Procedimento para eliminar o registo corrente num formulário:
Sub elimina ()
On Error GoTo elimina_erro
DoCmd DoMenuItem A_FORMBAR,A_EDITMENU,A_SELECTRECORD_V2,,A_MENU_VER20
DoCmd DoMenuItem A_FORMBAR, A_EDITMENU, A_DELETE_V2, , A_MENU_VER20
elimina_exit:
Exit Sub
elimina_erro:
MsgBox "Registo inexistente"
Resume elimina_exit