【入門編】DAO.Recordsetの「Clone」メソッドでフォームと同期したデータ操作を行う – Access VBA解析バイブル

スポンサーリンク

フォームを「見守りながら」レコード操作!Access VBA「Clone」メソッドで実現するスマートなデータ連携

皆さん、こんにちは!Access VBAの世界へようこそ!

「マクロの記録」から一歩踏み出して、もっと柔軟に、もっとパワフルにAccessを使いこなしたいと思っていませんか?そんなあなたのために、今日はAccess VBAのちょっとした「裏技」とも言える、でも実はとっても基本的な「Clone」メソッドを使った、フォームとレコードセットの連携テクニックをご紹介します。

このテクニックをマスターすれば、フォームの表示を崩すことなく、バックグラウンドでデータを検索したり、絞り込んだりできるようになります。まるで、フォームに表示されているデータを「見守りながら」、裏でこっそり作業してくれる優秀なアシスタントを手に入れたような感覚になるはずですよ!

なぜ「Clone」メソッドが必要なの?フォームの表示とレコード操作のジレンマ

まずは、なぜこの「Clone」メソッドが重要なのか、その背景を理解しましょう。

通常、フォームで表示しているデータは、そのフォームに紐づいたレコードソース(テーブルやクエリ)から取得されています。そして、VBAでレコードを操作しようとすると、多くの場合、`CurrentDb.OpenRecordset`メソッドなどを使って新しいレコードセットオブジェクトを作成します。

ここで問題が発生します。

  • レコードセットを直接操作すると、フォームの表示が連動して変わってしまう: 例えば、`Recordset.Find`メソッドでレコードを検索すると、フォームの表示もその検索結果のレコードに移動します。ユーザーは「あれ?今どこ見てたっけ?」となってしまうかもしれません。
  • フォームの表示を維持したまま、別の条件でレコードを絞り込みたい: ユーザーがフォームで特定のレコードを確認している最中に、別の条件で関連データを検索して表示したい、といったニーズが出てきます。しかし、上記の理由から、直接レコードセットを操作するとフォームの表示が崩れてしまいます。

これらのジレンマを解決してくれるのが、まさに`Recordset.Clone`メソッドなのです!

`Recordset.Clone`メソッドとは?「コピー」ではなく「分身」を作るイメージ

`Clone`メソッドは、既存のレコードセットオブジェクトの「クローン(複製)」を作成します。しかし、ここが重要!これは単なるデータのコピーではありません。

例えるなら、あなたが持っている「魔法の地図」から、全く同じ状態の「魔法の地図」をもう一つ作り出すようなものです。それぞれの地図は独立して操作できますが、元となる「土地」の情報は共有しています。

つまり、`Clone`メソッドで作成されたレコードセットは、

  • 元のレコードセットと同じデータソースを参照する: 元のレコードセットが参照しているテーブルやクエリと同じデータを扱います。
  • 独立して操作できる: クローンしたレコードセットで検索や移動を行っても、元のレコードセット(そしてそれに紐づいたフォーム)の表示には影響しません。
  • 元のレコードセットの現在の位置を保持できる: クローンした時点での元のレコードセットの位置(カレントレコード)を、クローンしたレコードセットも引き継ぎます。

この「独立して操作できる」という点が、フォームの表示を維持したままバックグラウンドでデータ操作を行うための鍵となるのです。

実践!フォームの表示を維持したままレコードを検索するVBAコード

では、具体的にどのように使うのか、コード例を見ていきましょう。

今回は、社員名簿フォーム (`frmEmployees`) があり、そのフォームで表示されている社員のIDを元に、別のテーブル(例えば `tblOrders`)からその社員が過去に注文した履歴を検索して表示する、というシナリオを想定します。

前提:

  • `frmEmployees` という名前のフォームがあり、`EmployeeID` というフィールドに社員IDが表示されているとします。
  • `tblOrders` というテーブルがあり、`EmployeeID` フィールドで `tblEmployees` テーブルと関連付けられているとします。

‘ Module: modEmployeeOrderOperations
‘ Description: 社員注文履歴の操作に関するモジュール

Option Compare Database
Option Explicit

‘—————————————————————————————
‘ Procedure : FindEmployeeOrders
‘ Author : あなたの伝説的なエンジニア
‘ Date : 2023/10/27
‘ Purpose : 現在フォームで表示されている社員の注文履歴を検索する
‘ (フォームの表示は維持したまま)
‘—————————————————————————————

Public Sub FindEmployeeOrders()

Dim db As DAO.Database
Dim rsEmployees As DAO.Recordset
Dim rsOrders As DAO.Recordset
Dim lngCurrentEmployeeID As Long
Dim strFilter As String

‘ — 1. 現在のフォームのレコードセットを取得 —
‘ Application.ActiveForm は現在アクティブなフォームオブジェクトを返します。
‘ ActiveForm.Recordset は、そのフォームのレコードソース(テーブルやクエリ)から
‘ 開かれたレコードセットオブジェクトを返します。
‘ ここで取得するレコードセットは、フォームの表示内容と直接連動しています。
Set rsEmployees = Application.ActiveForm.Recordset

‘ フォームが開かれていて、かつレコードが存在するか確認
If rsEmployees Is Nothing Then
MsgBox “社員フォームが開かれていないか、レコードがありません。”, vbExclamation
Exit Sub
End If

‘ 現在フォームで表示されているレコードのEmployeeIDを取得
‘ フォームのフィールド名が “EmployeeID” であることを想定しています。
On Error Resume Next ‘ エラーが発生しても処理を続行(フィールドが存在しない場合など)
lngCurrentEmployeeID = rsEmployees!EmployeeID
On Error GoTo 0 ‘ エラーハンドリングを元に戻す

If lngCurrentEmployeeID = 0 Then ‘ IDが取得できなかった場合
MsgBox “現在表示されているレコードから社員IDを取得できませんでした。”, vbExclamation
rsEmployees.Close
Set rsEmployees = Nothing
Exit Sub
End If

‘ — 2. 注文履歴テーブルのレコードセットを開く —
Set db = CurrentDb ‘ 現在のデータベースオブジェクトを取得

‘ 注文履歴テーブルのレコードセットを開きます。
‘ dbOpenDynaset は、データの追加、編集、削除が可能なレコードセットタイプです。
‘ dbSeeChanges は、他のユーザーによって変更されたレコードを即座に反映させます。
Set rsOrders = db.OpenRecordset(“tblOrders”, dbOpenDynaset, dbSeeChanges)

‘ — 3. 注文履歴レコードセットの「クローン」を作成 —
‘ rsOrders.Clone は、rsOrders と同じデータソースを参照する新しいレコードセットオブジェクトを返します。
‘ このクローンは、rsOrders とは独立して操作できます。
‘ つまり、このクローンで検索や移動を行っても、rsOrders(そしてそれに紐づいたフォーム)には影響しません。
Dim rsOrdersClone As DAO.Recordset
Set rsOrdersClone = rsOrders.Clone

‘ — 4. クローンしたレコードセットで検索条件を設定 —
‘ 検索条件(フィルター)を設定します。
‘ FieldName = Value の形式で指定します。
‘ 文字列型の場合はシングルクォーテーションで囲む必要があります。
strFilter = “EmployeeID = ” & lngCurrentEmployeeID
rsOrdersClone.Filter = strFilter

‘ Filter プロパティを設定しただけでは、レコードセットはまだフィルターされた状態になりません。
‘ 実際にフィルターを適用し、最初のレコードに移動するために .MoveFirst を実行します。
‘ FilterOnLoad プロパティを True に設定しておくと、レコードセットを開いたときに自動的にフィルターが適用されますが、
‘ 後から動的にフィルターを設定する場合は、このように .MoveFirst を使うのが一般的です。
rsOrdersClone.MoveFirst

‘ — 5. 検索結果の処理 —
If rsOrdersClone.EOF And rsOrdersClone.BOF Then
‘ 検索結果が0件の場合
MsgBox lngCurrentEmployeeID & ” さんに関連する注文履歴は見つかりませんでした。”, vbInformation
Else
‘ 検索結果が見つかった場合
‘ 検索結果の件数を表示する例
MsgBox lngCurrentEmployeeID & ” さんに関連する注文履歴が ” & rsOrdersClone.RecordCount & ” 件見つかりました。”, vbInformation

‘ もし、検索結果の最初のレコードを別のフォームで表示したい、などの処理があればここで行います。
‘ 例: DoCmd.OpenForm “frmOrderDetails”, acNormal, , “OrderID=” & rsOrdersClone!OrderID
End If

‘ — 6. クリーンアップ —
‘ 開いたレコードセットオブジェクトを閉じ、メモリを解放します。
‘ Clone で作成したレコードセットも忘れずに閉じます。
rsOrdersClone.Close
rsOrders.Close

‘ オブジェクト変数を Nothing に設定して、参照を解放します。
Set rsOrdersClone = Nothing
Set rsOrders = Nothing
Set db = Nothing
Set rsEmployees = Nothing

End Sub

コードのポイント解説

1. `Set rsEmployees = Application.ActiveForm.Recordset`:

  • `Application.ActiveForm` で現在アクティブなフォームオブジェクトを取得します。
  • `.Recordset` プロパティで、そのフォームに紐づいているレコードセットオブジェクトを取得します。これがフォームの表示内容と直接連動している「元」のレコードセットです。
  • `If rsEmployees Is Nothing Then …` で、フォームが開かれていない、またはレコードがない場合のガード処理を入れています。
  • `lngCurrentEmployeeID = rsEmployees!EmployeeID` で、フォームで現在表示されているレコードの `EmployeeID` を取得しています。`!` は、レコードセット内のフィールドにアクセスするためのショートカットです。

2. `Set rsOrders = db.OpenRecordset(“tblOrders”, dbOpenDynaset, dbSeeChanges)`:

  • `CurrentDb` で現在のデータベースオブジェクトを取得します。
  • `db.OpenRecordset` で、操作したいテーブル (`tblOrders`) のレコードセットを開きます。
  • `dbOpenDynaset` は、データの追加・編集・削除が可能な、最も一般的なレコードセットタイプです。
  • `dbSeeChanges` は、他のユーザーが変更したデータを即座に反映させるためのオプションです。

3. `Set rsOrdersClone = rsOrders.Clone`:

  • これが今回の主役!`rsOrders` のクローンを作成します。
  • `rsOrdersClone` は `rsOrders` とは独立して操作できます。

4. `rsOrdersClone.Filter = strFilter`:

  • クローンしたレコードセット (`rsOrdersClone`) に対して、`Filter` プロパティで検索条件を設定します。
  • `strFilter = “EmployeeID = ” & lngCurrentEmployeeID` のように、取得した `EmployeeID` を使って動的にフィルター文字列を作成しています。
  • 重要: `Filter` プロパティを設定しただけでは、まだデータは絞り込まれません。

5. `rsOrdersClone.MoveFirst`:

  • `Filter` プロパティを設定した後、実際にフィルターを適用してレコードセットを移動させるために `.MoveFirst` を実行します。これにより、フィルター条件に合致する最初のレコードにカレントレコードが移動します。
  • もし、`Filter` プロパティを設定したレコードセットが空(条件に合うレコードがない)の場合、`.EOF` と `.BOF` が共に `True` になります。

6. `If rsOrdersClone.EOF And rsOrdersClone.BOF Then … Else … End If`:

  • 検索結果が0件だった場合の処理と、件数が見つかった場合の処理を分けています。
  • `rsOrdersClone.RecordCount` で、フィルターされたレコードの件数を取得できます。

7. クリーンアップ:

  • `rsOrdersClone.Close`、`rsOrders.Close`、そして `Set … = Nothing` で、開いたオブジェクトを確実に閉じ、メモリを解放することは、VBAプログラミングの基本中の基本です。これを怠ると、メモリリークや予期せぬエラーの原因になります。

陥りやすいエラーとその回避策

このテクニックを使う上で、いくつか注意しておきたい点があります。

  • フォームが開かれていない、またはレコードがない場合:
  • `Application.ActiveForm` が `Nothing` を返したり、`.Recordset` が取得できなかったりする可能性があります。コードの冒頭で `If rsEmployees Is Nothing Then …` のようなチェックを行いましょう。
  • フィールド名が間違っている:
  • `rsEmployees!EmployeeID` の `EmployeeID` が、フォームの実際の設定と一致しない場合、エラーになります。フィールド名やコントロール名を確認しましょう。
  • `Filter` プロパティ設定後の `MoveFirst` の忘れ:
  • `Filter` プロパティを設定しただけでは、カレントレコードは移動せず、`RecordCount` も期待通りの値にならないことがあります。必ず `.MoveFirst` (または `.MoveLast`, `.FindFirst` など)を実行して、フィルターを適用させましょう。
  • データ型の不一致:
  • `Filter` 条件で数値を文字列として扱ったり、その逆を行ったりすると、意図しない結果になったりエラーになったりします。特に日付型や文字列型の場合は、シングルクォーテーション (`’`) で囲むのを忘れないようにしましょう。
  • 例: `strFilter = “CustomerName = ‘” & strCustomerName & “‘”` (文字列の場合)
  • 例: `strFilter = “OrderDate >= #” & Format(dtmStartDate, “yyyy/mm/dd”) & “#”` (日付型の場合)
  • `Clone` したレコードセットを閉じ忘れる:
  • `rsOrdersClone` も独立したレコードセットオブジェクトなので、使い終わったら必ず `Close` し、`Nothing` に設定してリソースを解放しましょう。

まとめ:スマートなデータ操作でAccessの可能性を広げよう!

`Recordset.Clone` メソッドは、Access VBAでフォームの操作性とデータ操作の独立性を両立させるための強力なツールです。

  • フォームの表示を維持したまま、バックグラウンドでデータを検索・絞り込みたい。
  • ユーザーにストレスを与えずに、関連データを提示したい。

そんな時は、ぜひこの `Clone` メソッドを思い出してください。

今回ご紹介したコードは、あくまで基本的な例です。ここからさらに応用して、検索結果を別のフォームに表示したり、特定のレコードをハイライト表示したりと、様々な機能を実現することができます。

Access VBAの基本は、これらのオブジェクトの「ライフサイクル」や「振る舞い」を理解することにあります。`Clone` メソッドを使いこなすことで、あなたはAccess VBAの、より深く、よりスマートな世界への扉を開けたと言えるでしょう。

ここをクリアすれば、Access VBAの基本はバッチリですよ!ぜひ、あなたの開発現場で試してみてください。応援しています!

タイトルとURLをコピーしました