Você está na página 1de 6

79 Universidade Federal de Santa Maria

Anexo II

Construção de Aplicações de Acesso a Banco de Dados – Parte II (Relação Mestre/Detalhe)

O objetivo deste material é demonstrar possíveis códigos (enxutos e seguros) para realizar
operações de acesso/manutenção em uma tabela resultante de um relacionamento NxN (muitos para
muitos) utilizando componentes DataWare (componentes que representam automaticamente informações
contidas em uma origem de dados). Nas aplicações este tipo de relação (NxN) é normalmente
denominado de relação Mestre/Detalhe. Neste tipo de relação os dados exibidos na tabela Detalhe são
dependentes dos dados exibidos na tabela Mestre. Para exemplificar essa relação vamos utilizar uma
tabela resultante da relação Receitas e Ingredientes (uma receita pode conter muitos ingredientes e um
ingrediente pode estar contido em muitas receitas). O modelo de dados utilizado neste exemplo utiliza
três tabelas relacionadas da seguinte forma:

CREATE TABLE INGREDIENTES_RECEITA


(
ID_RECEITA INTEGER NOT NULL, CREATE TABLE INGREDIENTES
ID_INGREDIENTE INTEGER NOT NULL, (
CREATE TABLE RECEITAS QUANTIDADE DECIMAL(5,2) NOT NULL ID_INGREDIENTE INTEGER NOT NULL,
( ); DESCRICAO VARCHAR(100) NOT NULL,
ID_RECEITA INTEGER NOT NULL, UNIDADE CHAR(3) NOT NULL
NOME VARCHAR(100) NOT NULL, );
ORIGEM VARCHAR(100),
TIPO_RECEITA CHAR(1) NOT NULL,
TEMPO_PREPARO DECIMAL(5,2),
MODO_PREPARO BLOB SUB_TYPE 1,
FOTO BLOB SUB_TYPE 0
);
A relação mestre/detalhe pode se dar por qualquer uma das tabelas relacionadas. Por exemplo, no
cadastro de receitas podem-se exibir todos os seus ingredientes (neste caso a tabela mestre é a tabela
Receitas e a tabela detalhe é a tabela Ingredientes_Receita). Essa relação também seria válida no
cadastro de Ingredientes, ou seja, exibir todas as receitas onde determinado ingrediente é utilizado (neste
caso a tabela mestre é a tabela Ingredientes e a tabela detalhe é a tabela Ingredientes_Receita).
Independente de quem seja a tabela mestre, a tabela detalhe sempre será aquela que representa o
relacionamento NxN. Observe na figura abaixo uma relação de dependência entre Receitas e
IngredientesReceita

Caderno Didático – Lazarus IDE Página 79


80 Universidade Federal de Santa Maria

Em uma aplicação com uma relação mestre/detalhe, os códigos discutidos no Anexo I (Cadastro
Simples) sofrerão algumas alterações (agora será necessário executar operações e controlar mais de
uma tabela). Para fazer a demonstração será utilizada uma janela de manutenção semelhante à figura
abaixo com o devido tratamento dos eventos que possam ocorrer objetivando um código simples que
possa funcionar para qualquer tela de cadastro com características semelhantes. De forma a organizar o
código, um componente ActionList será utilizado com a criação e implementação 3 novas ações
(actIncluirDetalhe, actSalvarDetalhe, actExcluirDetalhe) além daquelas sete já discutidas anteriormente
actNovo, actLocalizar, actSalvar, actCancelar, actExcluir, actSair, actLocalizarPorDescricao).

Edit

DBEdit (ParentColor = true, ReadOnly = true)

GroupBox para agrupar todas os campos


da tabela detalhe e suas relações.

Três novas ações para


DBGrid para listar as informações da tabela permitir a manutenção
detalhe evitando a necessidade de mecanismos nas informações da
para pesquisa (o usuário clica sobre o tabela Detalhe.
registro que deseja fazer manutenção)

Relacionamento Mestre/Detalhe entre dois componentes SQLQuery

O relacionamento de dependência (mestre/detalhe) entre duas consultas é realizado por meio de


dois procedimentos:

 Inclusão de uma condição na SQLQuery da tabela Detalhe: a propriedade SQL da tabela


detalhe deve ser alterada de forma a conter uma condição na cláusula WHERE que filtre os
dados da tabela detalhe de acordo com o registro selecionado na tabela mestre. A condição
de filtro da tabela detalhe deve-se utilizar de um campo obrigatoriamente existente na tabela
mestre (geralmente o campo chave), precedido do sinal de ":" (dois pontos);

 Alterar a propriedade DataSource da SQLQuery Detalhe indicando o DataSource da


SQLQuery Mestre.

Caderno Didático – Lazarus IDE Página 80


81 Universidade Federal de Santa Maria

A figura a seguir ilustra as alterações necessárias para ativar uma relação mestre/detalhe entre dois
componentes SQLQuery:

Com essa relação configurada, quando a tabela MESTRE está ativa a tabela DETALHE se
comporta exibindo somente os registros que fazem referência ao registro atual da tabela MESTRE. Em
outras palavras, o comando SQL da SQLQueryDetalhe é alterado de forma a substituir o valor do
parâmetro “:ID_RECEITA” pelo valor contido no campo de mesmo nome (ID_RECEITA) da
SQLQueryMestre.

Abertura e Fechamento de Consultas

Os procedimentos AbrirConsultas() e FecharConsultas() desenvolvidos no Anexo I (Cadastro


Simples) precisarão ser alterados para ativar e desativar os dados da tabela detalhe e eventualmente
tabelas relacionadas a ela.

procedure AbrirConsultas();
begin //Se a transação não está ativa então inicia ela
if not SQLTransaction.Active then SQLTransaction.StartTransaction;
... //Restante do código discutido no Anexo I

//Se a tabela detalhe e suas tabelas relacionadas não estão ativas então devem ser ativadas
if not TabelaRelacionadaComDetalhe.Active then TabelaRelacionadaComDetalhe.Open;
if not TabelaDetalhe.Active then TabelaDetalhe.Open;

//se a tabela não está ativa então a ative


if not TabelaMestre.Active then TabelaMestre.Open;
end;

procedure FecharConsultas();
begin //se as tabelas relacionadas estão ativas então as mesmas devem ser fechadas
... //Restante do código discutido no Anexo I
//Se a tabela detalhe e suas tabelas relacionadas estão ativas então devem ser fechadas
if TabelaDetalhe.Active then TabelaDetalhe.Close;
if TabelaRelacionadaComDetalhe.Active then TabelaRelacionadaComDetalhe.Close;

//se a tabela está ativa então ela deve ser encerrada


if TabelaMestre.Active then TabelaMestre.Close;
//se a transação estiver ativa a mesma deve ser encerrada
if SQLTransaction.Active then SQLTransaction.EndTransaction;
end;

Caderno Didático – Lazarus IDE Página 81


82 Universidade Federal de Santa Maria

Mantendo as tabelas (mestre e detalhe) com o mesmo estado

O código a seguir demonstra como sincronizar o estado da tabela mestre e da tabela detalhe.
Sempre que a tabela mestre estiver ativa a tabela detalhe deve ser ativada e sempre que a tabela mestre
estiver inativa então a tabela detalhe deve ser inativada. Essa alteração será feita no procedimento
HabilitarDesabilitarControles que também será alterado para habilitar e/ou desabilitar o
GroupBox onde estão os controles da tabela detalhe.

procedure HabilitarDesabilitarControles;
begin
... //Código que habilita e desabilita os controles visuais
//de acordo com o status da tabela mestre

//sincronização da situação da tabela detalhe em função da situação da tabela mestre


TabelaDetalhe.Active:=TabelaMestre.Active;

//Se a tabela mestre estiver em status de inserção ou edição então os controles da


//tabela detalhe devem ficar desabilitados caso contrario os controles devem ser
//habilitados se a tabela detalhe estiver ativa ou desabilitados se estiver fechada

if TabelaMestre.State = dsInsert then


GroupBoxDadosTabelaDetalhe.Enabled := false
else
GroupBoxDadosTabelaDetalhe.Enabled := TabelaDetalhe.Active;

//Se a tabela detalhe está ativa e não contém nenhum registro (está vazia) então
//a ação de gravação da tabela detalhe deverá ficar desabilitada
if TabelaDetalhe.Active and TabelaDetalhe.IsEmpty then
actSalvarDetalhe.Enabled := false;
end;

Inclusão de um novo registro na tabela Datalhe (ação actIncluirDetalhe)

A inclusão de um registro na tabela detalhe ocorre normalmente através do método insert e deve
atribuir ao campo que representa a relação entre as tabelas o valor da tabela mestre. Também é valido
verificar se o status da tabela detalhe já não é de inclusão, neste o método insert nem precisa ser
executado. Se existirem campos que precisarem ser inicializados ou então componentes que precisam
receber o foco este é o momento apropriado para isso.

//Se a tabela detalhe estiver ativa


if TabelaDetalhe.Active then
begin //Coloca a tabela detalhe em modo de inserção apenas se já não o estiver
if TabelaDetalhe.State <> dsInsert then
TabelaDetalhe.Insert;

//Atribui ao campo que faz a relação entre a tabela mestre e a tabela detalhe
//o valor do campo chave da tabela mestre
TabelaDetalhe.FieldByName('CampoRelacaoMestreDetalhe').Value :=
TabelaMestre.FieldByName('CampoChave').value;

//Aqui pode-se indicar componentes que receberão o foco e/ou componentes que
//precisarão ser inicializados.
DBEditDetalheQualquer.SetFocus;
DBLkpCmbBxDetalheQualquer.ItemIndex := -1

//Após o usuário clicar no botão que faz a inserção na tabela detalhe então
//a ação de salvamento deve ficar habilitada
actSalvarDetalhe.Enabled:=true;
end;

Caderno Didático – Lazarus IDE Página 82


83 Universidade Federal de Santa Maria

Persistência (gravação) do registro atual da tabela detalhe (ação actSalvarDetalhe)

A gravação das informações da tabela detalhe vai exigir um pouco de atenção. O procedimento de
gravação/persistência é simples: o comando Post grava os dados no DataSet, o comando
ApplyUpdates envia os dados do DataSet para serem persistidos na base de dados e por último, a
ação de Commit da transação vai efetivar os dados e encerrar a transação. Essa última situação é
justamente onde se deve prestar mais atenção: quando a transação for encerrada, todos os DataSets
ligados a ela serão automaticamente fechados. Isso significa, transpondo a situação para o exemplo dos
ingredientes e receitas, que a cada vez que um ingrediente for incluído em uma receita o usuário teria
que novamente localizar a receita para fazer uma nova inclusão ou alteração de dados. Para contornar
essa situação, a cada vez que os dados da tabela detalhe forem persistidos haverá necessidade de ativar
novamente as consultas e posicionar os registros. O código a seguir demonstra como isso pode ser feito:

var //variáveis auxiliares para guardar informações do registro que será salvo
Chave1, Chave2 : integer; //Chave1 = Chave da tabela mestre, e parte da chave
begin // da tabela detalhe, Chave2 = chave complementar da tabela detalhe

if TabelaDetalhe.State in [dsInsert, dsEdit] then //Se o status da tabela detalhe for


begin // inserção o edição
//Inicia a validação dos campos do cadastro detalhe
//(se forem muitos campos recomenda-se a criação de uma subrotina)
if Length(Trim(DBEditQualquer.Text)) = 0 then
begin
DBEditQualquer.SetFocus;
MessageDlg('Uma informação obrigatória não foi informada.', mtError, [mbOK], 0);
Abort;
end;
if DBLkpCmbBxQualquer.ItemIndex = -1 then
begin
DBLkpCmbBxQualquer.SetFocus;
MessageDlg('Uma informação obrigatória não foi selecionada.', mtError, [mbOK], 0);
Abort;
end;

//Guarda as informações da chave da tabela Detalhe


Chave1:= TabelaDetalhe.FieldByName('CAMPO_CHAVE_1_TABELA_DETALHE').asInteger;
Chave2:= TabelaDetalhe.FieldByName('CAMPO_CHAVE_2_TABELA_DETALHE').asInteger;

try //Inicia o processo de gravação


TabelaDetalhe.Post; //Grava os dados no DataSet
TabelaDetalhe.ApplyUpdates; //Envia os dados do DataSet para o banco de dados
SQLTransaction.Commit; //Efetiva as alterações no BD e encerra a transação
except //Se ocorrer um erro
on E : Exception do
begin //Mostra uma mensagem de aviso ao usuário
MessageDlg('Erro de gravação',
'Não foi possível salvar. Erros: ' + #13 + E.Message, mtError, [mbOK],0);
SQLTransaction.Rollback; //Cancela a transação
end;
end;

AbrirConsultas; //Ativa as consultas


//Posiciona a tabela mestre no registro que ela estava antes do salvamento
TabelaMestre.Locate('CampoChave', Chave1, []);
//Posiciona a tabela detalhe no registro que ela estava antes do salvamento
TabelaDetalhe.Locate('CampoChave1;CampoChave2',
VarArrayOf([Chave1, Chave2]),[]); //uses variants
HabilitarDesabilitarControles; //Atualiza a situação dos controles visuais
end;
end;

Caderno Didático – Lazarus IDE Página 83


84 Universidade Federal de Santa Maria

Exclusão do registro atual na tabela Detalhe (ação actExcluirDetalhe)

A exclusão de uma informação na tabela detalhe pressupõe que o usuário esteja posicionado sobre
o registro ao qual deseja excluir. Assim como na ação de gravação, a ação de exclusão exigirá que a
transação seja encerrada, de forma que será necessário prever uma forma de reposicionar o registro da
tabela mestre

var chaveTbMestre: integer; //Variável auxiliar para guardar a chave da tabela mestre
begin
//Se a tabela detalhe está ativa e não estiver vazia (há algo para excluir)
if (TabelaDetalhe.Active) and (not TabelaDetalhe.IsEmpty) then
begin
//Guarda em uma variável auxiliar o campo da tabela detalhe que faz relação com
//a tabela mestre
chaveTbMestre := TabelaDetalhe.FieldByName('CampoRelacaoMestreDetalhe').asInteger;

try //inicia o processo de exclusão


TabelaDetalhe.Delete; //Delata o registro no DataSet
TabelaDetalhe.ApplyUpdates; //Envia a exclusão para o BD
SQLTransaction.Commit; //Efetiva a exclusão no BD
except //Se ocorrer um erro
on E : Exception do
begin //Mostra uma mensagem de alerta para o usuário
MessageDlg('Erro de exclusão',
'Não foi possível excluir o registro. Ocorreram os seguintes erros:' + #13 +
E.Message, mtError, [mbOK],0);

//Desfaz as alterações da transação


SQLTransaction.Rollback;
end;
end;

AbrirConsultas; //Ativa as consultas


//Posiciona a tabela mestre no registro que ela estava antes da exclusão
TabelaMestre.Locate('CampoChave', chaveTbMestre, []);
HabilitarDesabilitarControles; //Atualiza a situação dos controles visuais
end;

Caderno Didático – Lazarus IDE Página 84

Você também pode gostar