「現在とある業務で使用してるAccessのテーブル「A」と「B」と「C」は正規化の観点からするとひとつのテーブルに集約することができるが、フィールド数が多くなることと、業務の流れとして「A」が確定してから「B」と「C」を入力するということであえて分割してある。現在、それぞれのテーブルを更新・削除を行う場合、個別のフォームを用いているが、作業量が増えてきた関係で煩わしくなってきた。更新・削除を1つのフォームから行うことはできないだろうか?」
…というのが悩みの種のようです。ちなみに後輩君のレベルは簡単なクエリとそれに基づいたフォームが作れるくらいです。確かにちょっと悩んじゃうかもしれません。個人的には追加部分を工夫するとして正規化しちゃえばいいじゃんってとこですが、まぁ、そうできないケースも今後発生するかもしれないのでとりあえずこのまま挑戦させてみようかなぁってことで、このブログでは後輩君をほっておいて自分的な考察やらコードをあげちゃいます。聞くだけ聞いてなんなんですが後輩君には自分で考えてもらいます。
とりあえずサンプルデータです。「A」「B」「C」のテーブルは「A」を基点としてそれぞれ1対1で連結しています(フィールド数が少ないことは気にしない)。
A | B | C | |||||||||
Id | AName | Id | AId | BName | Id | AId | CName | ||||
1 | 太郎 | 1 | 1 | 犬 | 1 | 1 | オス | ||||
2 | 花子 | 2 | 2 | 猫 | 2 | 2 | メス | ||||
次に根本的な考え方ですが、フォームとテーブル(クエリ)の連結は最初からあきらめちゃいます。どうやら後輩君が1番引っかかってる部分のようですが、1つのフォームに複数のテーブルを連結することはできません(たぶん)。サブフォームを2つ使えばって考えもありますがその場合、更新のタイミングの問題も出てくると思います(例えばBを更新する際、AとCの更新は?みたいな)。イメージ的には下図のようになります。
フォームに表示する流れ
更新・削除をする流れ
VBA上でSQL文を使用して「A」「B」「C」各テーブルと「A」「B」「C」を結合した独自型をつなげ、その独自型とフォームを連動させます。独自型を使うのははっきり言って趣味です。飛ばして直でフォームにいっちゃっても問題ないと思います。それでは流れに基づいてメソッドを作ってみます。
Option Compare Database Option Explicit '独自型 Public Type MyData Id As Integer 'このIdはAのId AName As String BName As String CName As String End Type '生成メソッド Public Function GetMyData(targetId As Long) As MyData On Error GoTo ErrProcess Dim cn As ADODB.Connection Set cn = CurrentProject.Connection Dim selectSql As String selectSql = "SELECT A.Id, A.AName, B.BName, C.CName " _ & "FROM (A INNER JOIN B ON A.Id = B.AId) " _ & "INNER JOIN C ON A.ID = C.AId WHERE A.Id = " & targetId Dim rs As ADODB.Recordset Set rs = New ADODB.Recordset rs.Open selectSql, cn, adOpenKeyset, adLockReadOnly Dim result As MyData '0件はエラー、2件以上については今回は考えません If (rs.RecordCount = 0) Then MsgBox "レコードを取得できませんでした。", vbExclamation, "エラー" Else result.Id = rs!Id result.AName = rs!AName result.BName = rs!BName result.CName = rs!CName End If rs.Close: Set rs = Nothing cn.Close: Set cn = Nothing GetMyData = result Exit Function ErrProcess: rs.Close: Set rs = Nothing cn.Close: Set cn = Nothing MsgBox Err.Description, vbCritical, "エラー" End Function '更新メソッド Public Sub UpdateMyData(updateData As MyData) On Error GoTo ErrProcess Dim cn As ADODB.Connection Set cn = CurrentProject.Connection Dim updateSqlA As String updateSqlA = "UPDATE A SET AName = '" & updateData.AName & "' " _ & "WHERE Id = " & updateData.Id Dim updateSqlB As String updateSqlB = "UPDATE B SET BName = '" & updateData.BName & "' " _ & "WHERE AId = " & updateData.Id Dim updateSqlC As String updateSqlC = "UPDATE C SET CName = '" & updateData.CName & "' " _ & "WHERE AId = " & updateData.Id cn.Execute (updateSqlA) cn.Execute (updateSqlB) cn.Execute (updateSqlC) cn.Close: Set cn = Nothing Exit Sub ErrProcess: cn.Close: Set cn = Nothing MsgBox Err.Description, vbCritical, "エラー" End Sub '削除メソッド Public Sub DeleteMyData(deleteData As MyData) On Error GoTo ErrProcess Dim cn As ADODB.Connection Set cn = CurrentProject.Connection Dim deleteSqlA As String deleteSqlA = "DELETE FROM A WHERE Id = " & deleteData.Id '連鎖削除設定してたら以下のBとCのDELETE文は不要 Dim deleteSqlB As String deleteSqlB = "DELETE FROM B WHERE AId = " & deleteData.Id Dim deleteSqlC As String deleteSqlC = "DELETE FROM C WHERE AId = " & deleteData.Id cn.Execute (deleteSqlA) cn.Execute (deleteSqlB) cn.Execute (deleteSqlC) cn.Close: Set cn = Nothing Exit Sub ErrProcess: cn.Close: Set cn = Nothing MsgBox Err.Description, vbCritical, "エラー" End Sub
やってることはシンプルにしたつもりです。あとはリスト型のフォームと入力用のフォームを用意して各メソッドを呼び出してやれば完成です。さすがにフォームは省略させて下さい。
以上が自分なりの後輩君の悩みへの解決方法です。いやぁ、後輩君はどんな方法で解決するか楽しみですね~。簡単にギブアップしないことを祈ってます。