VB - Exibindo dados no layout Mestre - Detalhes
Recebo muitas consultas sobre como exibir dados relacionados entre tabelas , o clássico layout Mestre/Detalhe). Na verdade a dificuldade as vezes não está em como fazer isto no VB mas em ter as tabelas normalizadas e prontas para que isto seja possível.
Se você estiver tentando exibir dados entre tabelas relacionadas e elas não estão relacionadas fica difícil não é mesmo ...
Vou mostrar como fazer isto usando ADO , depois DAO e a seguir no VB.NET. Lembrando que existem muitas outras formas de realizar esta tarefa.
Preparando as tabelas
Eu não vou perder tempo criando e normalizando tabelas vou pegar o banco de dados Nwind.mdb que já vem com o Access e com o VB e usar duas tabelas contidas neste banco de dados e que já estão relacionadas. Abaixo estou exibindo o relacionamento entre as tabelas do banco de dados Nwind.mdb.
Vou dar um zoom nas tabelas Pedidos e Detalhes do Pedido para ver o relacionamento entre elas:
- Existe um
relacionamento entre a tabela Detalhes do Pedido e Pedidos do tipo : UM para
MUITOS ( 1-N ) - Temos a coluna NúmeroDoPedido como chave primária nas duas tabelas - A coluna NúmeroDoPedido estão relacionadas entre as duas tabelas Para cada registro na tabela Pedidos existem n registros na tabela Detalhes do Pedido relacionados pela coluna NúmeroDoPedido |
Como podemos ver as tabelas estão preparadas e prontos para serem usadas. Espero que você entenda que a normalização de dados e o relacionamento entre as tabelas é fundamental para que possamos trabalhar com as tabelas no layout Mestre/Detalhe.
Agora você vai ver como é fácil tratar os dados no VB. Vou começar usando a tecnologia ADO...
Exibindo dados relacionados com ADO
Vou usar os controles vinculados para mostrar como é fácil acessar e exibir dados com ADO. Você já deve saber que o para ADO temos o controle de dados vinculado ADO Data Control - Adodc . Vou usar também o controle Data Grid para exibir os dados no formato de uma grade de dados. (Não podemos usar o DBGrid com o Adodc).
Nosso projeto constará de dois ado data control - adodc , dois datagrids e uma label ,conforme o layout abaixo:
No menu Project | Components inclua os controles Microsoft Ado Data Control e Microsot Data Grid Control no seu projeto.
Percebeu que eu destaquei a coluna Sub Total e a label - Total para o Pedido ? Pois bem , além de exibir os dados relacionados entre as tabelas Pedidos e Detalhes do Pedido vou calcular o subtotal para cada número pedido e o total para o pedido usando comandos SQL.
Na seção General Declarations do formulário vou declarar as variáveis objeto usadas para a conexão e o recordset:
Option
Explicit 'define as variáveis objetos usadas no projeto Dim conn As ADODB.Connection Dim rs As ADODB.Recordset |
No evento Load do formulário estou definindo a conexão , o recordset a string de conexão com o banco de dados nwind.mdb para os controles vinculados : datDetalhe e datMestre e atribuindo os controles a seus respectivos datagrids.
Private Sub Form_Load() 'define objetos conn e rs Set conn = New ADODB.Connection Set rs = New ADODB.Recordset 'define o cursor e a string de conexão conn.CursorLocation = adUseClient conn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\teste\nwind.mdb;Persist Security Info=False" ' Define o nome do banco de dados para o detalhe datDetalhe.ConnectionString = _ "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\teste\Nwind.mdb;Persist Security Info=False" ' define o nome do banco de dados para o master datMestre.ConnectionString = _ "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\teste\Nwind.mdb;Persist Security Info=False" datMestre.RecordSource = "Select NúmeroDoPedido, CódigoDoCliente , DataDoPedido, DataDeEntrega,PaísDeDestino,_ Frete FROM Pedidos" datMestre.Refresh Set grdDetalhe.DataSource = datDetalhe datDetalhe.Refresh Set grdMestre.DataSource = datMestre End Sub |
No evento MoveComplete do controle
vinculado datMestre estou definindo duas consultas
SQL que são o coração da aplicação:
1- A consulta selecao :
subtotal = "SELECT DISTINCTROW [Detalhes do Pedido].NúmeroDoPedido, _ Sum(CCur([Detalhes do Pedido].[PreçoUnitário]*[Quantidade]*(1-[Desconto])/100)*100) AS Subtotal" subtotal = subtotal & " From [Detalhes do Pedido]" subtotal = subtotal & " GROUP BY [Detalhes do Pedido].NúmeroDoPedido" subtotal = subtotal & " HAVING [Detalhes do Pedido].NúmeroDoPedido=" & datMestre.Recordset.Fields("NúmeroDoPedido") |
Esta selecionando os detalhes do pedido para cada pedido selecionado na tabela Pedidos. Nela estou fazendo o cálculo , usando a função agregada SQL Sum - Sum(CCur([Detalhes do Pedido].[PreçoUnitário]*[Quantidade]*(1-[Desconto])/100)*100) AS Subtotal"
2- A consulta total calcula o total do pedido fazendo uma soma de todos os produtos para o pedido relacionado na tabela pedidos:
total = "SELECT DISTINCTROW [Detalhes do Pedido].NúmeroDoPedido, _ Sum(CCur([Detalhes do Pedido].[PreçoUnitário]*[Quantidade]*(1-[Desconto])/100)*100) AS Subtotal" total = subtotal & " From [Detalhes do Pedido]" total = subtotal & " GROUP BY [Detalhes do Pedido].NúmeroDoPedido" total = subtotal & " HAVING [Detalhes do Pedido].NúmeroDoPedido=" & datMestre.Recordset.Fields("NúmeroDoPedido") |
Nota : As principais funções agregadas para o SQL padrão Access são :
|
O código do evento MoveComplete é dado abaixo:
' Quando selecinar um novo registro no master ' selecionar os registros correspondentes no detalhe Private Sub datMestre_MoveComplete(ByVal adReason As ADODB.EventReasonEnum, ByVal pError As ADODB.Error, adStatus As _ ADODB.EventStatusEnum, ByVal pRecordset As ADODB.Recordset) Dim selecao As String Dim total As String 'consulta que exibe os detalhes do pedido para cada pedido selecionado na tabela pedidos selecao = "SELECT NúmeroDoPedido, CódigoDoProduto, PreçoUnitário, quantidade, Desconto , _ CCur([Detalhes do Pedido].[PreçoUnitário]*[Quantidade]*(1-[Desconto])/100)*100 AS PreçoTotal FROM [Detalhes do Pedido] _ WHERE NúmeroDoPedido = " & _ datMestre.Recordset.Fields("NúmeroDoPedido") 'esta consulta calcula o valor total para o pedido total = "SELECT DISTINCTROW [Detalhes do Pedido].NúmeroDoPedido, _ Sum(CCur([Detalhes do Pedido].[PreçoUnitário]*[Quantidade]*(1-[Desconto])/100)*100) AS Subtotal" total = subtotal & " From [Detalhes do Pedido]" total = subtotal & " GROUP BY [Detalhes do Pedido].NúmeroDoPedido" total = subtotal & " HAVING [Detalhes do Pedido].NúmeroDoPedido=" & datMestre.Recordset.Fields("NúmeroDoPedido") 'abre a conexão e executa a consulta retornando um recordset com duas colunas : ' rs(0) - número do pedido rs(1) - valor total para o pedido conn.Open Set rs = conn.Execute(total) 'exibe na label o valor total para o pedido lblTotalPedido.Caption = Format(rs(1), "##,##0.00") 'atribui a seleção SQL ao data control e refresca o controle datDetalhe.RecordSource = selecao datDetalhe.Refresh 'fecha a conexão rs.close conn.Close Set rs = Nothing End Sub
|
Note que eu estou abrindo uma conexão e executando uma consulta SQL usando o método Excecute do objeto Connection.
O método Execute executa uma consulta definida e armazena o resultado em um recordset do tipo read-only , forward-only. A sintaxe é a seguinte :
objconn.Execute commandtext,ra,options
onde :
Parametro | Descrição |
---|---|
commandtext | Requerido. O texto com a instrução SQL ou store procedure a ser executada. |
ra | Opcional. O número de registros afetados pela consulta. |
options | Opcional. Define como o
provedor irá avaliar o parâmetro commandtext .
Podemos ter os valores: - CommandTypeEnum - ExecuteOptionEnum O valor padrão é adCmdUnspecified |
Os valores para - CommandTypeEnum - são :
Constante | Valor | Descrição |
---|---|---|
adCmdUnspecified | -1 | Não define o argumento de tipo de comando. |
adCmdText | 1 | Avalia CommandText como um Texto ou um chamada de procedimento armazenado. |
adCmdTable | 2 | Avalia CommandText como uma tabela. |
adCmdStoredProc | 4 | Avalia CommandText como um nome de procedimento armazenado. |
adCmdUnknown | 8 | Indica que o tipo de comando na propriedade CommandText não é conhecido. |
adCmdFile | 256 | Avalia CommandText como o nome do arquivo de um procedimento armazenado. |
adCmdTableDirect | 512 | Avalia CommandText como um nome de tabela cujas colunas serão todas retornadas. Para usar o método seek o recordset deve ser aberto com com adCmdTableDirect. |
Os valores para - ExecuteOptionEnum - são :
Constante | Valor | Descricão |
---|---|---|
adOptionUnspecified | -1 | indica que o comando não esta especificado. |
adAsyncExecute | 16 | indica que o comando será executado de forma assíncrona. |
adAsyncFetch | 32 | indica as linhas restantes depois da quantidade inicial definida na propriedade CacheSize serão retornadas de forma assíncrona. |
adAsyncFetchNonBlocking | 64 | indica que a thread principal nunca bloqueia enquanto estiver retornando. |
adExecuteNoRecords | 128 | indica o texto do comando não retorna linhas. ( Ex: somente insere dados). |
adExecuteStream | 256 | indica que o resultado da execução do comando será retornado como um stream.(fluxo de dados) |
adExecuteRecord | 512 | indica que CommandText é um comando ou stored procedure que retorna uma única linha. |
Rodando o projeto e selecionado um determinado pedido você irá obter os detalhes do pedido o sub-total para cada produto em detalhes do pedido e o valor total do pedido conforme figura abaixo:
Nota: Para configurar as colunas e a aparência do datagrid faça o
seguinte :
Exibindo dados relacionados com DAO
Para fazer a mesma coisa com DAO vou usar os controles vinculados Data Control e o controle DBGrid pois não posso usar DataGrid com Data Control.
No menu Project | Components inclua os controles Microsoft Data Bound Grid Control no seu projeto.(O controle vinculado para DAO já esta presente na barra de ferramentas basta selecionar e incluir no formulário.)
A aparência será idêntica á do projeto ADO , por isto , só vou mostrar o código do projeto:
Código de declaração das variáveis objetos da seção - General Declarations do formulário:
Option Explicit
Dim daoDB As DAO.Database
Dim daoRS As DAO.Recordset
Código do evento Load do formulário :
Private Sub Form_Load() ' define o nome do banco de dados para o master datMestre.DatabaseName = "C:\teste\nwind.mdb" datMestre.RecordsetType = 1 datMestre.RecordSource = "Select NúmeroDoPedido, CódigoDoCliente , DataDoPedido, DataDeEntrega,PaísDeDestino, Frete _ FROM Pedidos" datMestre.Refresh ' define o nome do banco de dados para o detalhes datDetalhe.DatabaseName = "C:\teste\nwind.mdb" datDetalhe.RecordsetType = 1 datDetalhe.Refresh End Sub
|
O controle vinculado Data Control possui o evento Reposition que é equivalente ao MoveComplete do Ado data control. O código do evento reposition é o seguinte :
Private Sub datMestre_Reposition() ' Quando selecinar um novo registro no master ' selecionar os registros correspondentes no detalhe Dim selecao As String Dim total As String selecao = "SELECT NúmeroDoPedido, CódigoDoProduto, PreçoUnitário, quantidade, Desconto ,_ CCur([Detalhes do Pedido].[PreçoUnitário]*[Quantidade]*(1-[Desconto])/100)*100 AS PreçoTotal _ FROM [Detalhes do Pedido] WHERE NúmeroDoPedido = " & _ datMestre.Recordset.Fields("NúmeroDoPedido") 'esta consulta calcula o valor total para o pedido total = "SELECT DISTINCTROW [Detalhes do Pedido].NúmeroDoPedido, _ Sum(CCur([Detalhes do Pedido].[PreçoUnitário]*[Quantidade]*(1-[Desconto])/100)*100) AS Subtotal" total = subtotal & " From [Detalhes do Pedido]" total = subtotal & " GROUP BY [Detalhes do Pedido].NúmeroDoPedido" total = subtotal & " HAVING [Detalhes do Pedido].NúmeroDoPedido=" & datMestre.Recordset.Fields("NúmeroDoPedido") 'abre a conexão e executa a consulta retornando um recordset com duas colunas : ' rs(0) - número do pedido rs(1) - valor total para o pedido Set daoDB = DBEngine.Workspaces(0).OpenDatabase("C:\teste\nwind.mdb") Set daoRS = daoDB.OpenRecordset(total, dbOpenSnapshot) 'exibe na label o valor total para o pedido lbTotal.Caption = Format(daoRS(1), "##,##0.00") 'atribui a seleção SQL ao data control e refresca o controle datDetalhe.RecordSource = selecao datDetalhe.Refresh 'fecha a conexão daoRS.Close daoDB.Close End Sub |
Rodando o projeto teremos o mesmo resultado obtido com ADO:
Vou mostrar como fazer o mesmo com VB.NET em outro artigo , este já ficou muito extenso...
Nota: Reparem que a carga do projeto para DAO é ligeiramente mais rápida do que com ADO.
Até breve ...
José Carlos Macoratti