■クエリー実行プロシージャ

 今度は、今メインのプロシージャから呼び出したクエリーを実行するFunctionプロシージャ「RunQuery」を作成します。
 なぜ、Functionプロシージャとして作成するのかというと、クエリーの実行結果により、案内状の作成を行なうかどうかを判断できるようにするためです。クエリーによって指定の月名のデータが検索・抽出できなければ、案内状の送付はできません。プロシージャとしてクエリーが成功したのか失敗したのかを、結果としてきちんと返すように作成すれば、以降の処理の組み立てが楽になります。
 また、このプロシージャには、インプットボックスで入力された月名を引数に与えて、入力された月を表わす数字を元にクエリーを実行できるようにしています。

今作成したメインプロシージャのすぐ下に、Functionプロシージャ「RunQuery」を作成します。
 このプロシージャは、引数に文字列型の「IPTData」という変数を組み込みます。また、プロシージャの戻り値は、クエリーの成功か失敗かという2通りしか設定しませんので、論理型(Boolean)で指定しておきます。

Function RunQuery(ByVal IPTData As String) As Boolean
End Function
 続いて、プロシージャの先頭で変数を宣言します。最初の4つは、データベースの操作に使用する変数で、すでにプロシージャ「データベース接続テスト」で使用したものと同じデータ型を使用します。
Dim WSP As Workspace
Dim DB As Database
Dim TBL As Recordset
Dim Fname As String
 さらにこれらの変数に加え、クエリー実行用に使用する変数を3つ宣言します。
Dim QString As String    ― 
Dim NewQRY As QueryDef   ― 
Dim QResult As Recordset ― 
クエリーに使用するSQL文字列を格納する変数
クエリーを実行するQueryDefオブジェクトを格納するオブ ジェクト変数
クエリー実行後に作成されるクエリーテーブルを参照するオブジェクトを格納するオブジェクト変数

変数が用意できたら、データベースを開く操作をします。この処理も、すでにプロシージャ「データベース接続テスト」で行なった処理と同じです。ワークスペースを作成し、データベースファイルを開き、テーブルを開きます。それぞれ作成したオブジェクトは、Setステートメントを使って、宣言してあるオブジェクト変数に格納しておきます。

Fname = ActiveWorkbook.Path & "¥Club.mdb"
Set WSP = CreateWorkspace("", "admin", "", dbUseJet)
Set DB = WSP.OpenDatabase(Fname, False)
Set TBL = DB.OpenRecordset("会員名簿", dbOpenTable)

今度は、クエリーを実行するために使用するSQL文字列を作成します。DAOを使用したデータベース操作は、SQLステートメントを使用したクエリー操作が基本になります。そして、DAOで使用できるSQLステートメントは、標準SQL(ANSI SQL)とその拡張機能です。
 このマクロでは、インプットボックスで入力された月名を使って、テーブル「会員名簿」にあるフィールド「入会年月日」から一致する月名を抽出する、という処理を行ないます。この操作を行なう基本的なSQLステートメントは、「Select」「From」「Where」です。Selectステートメントのあとに、抽出するテーブル名とフィールド名を指定します。そして、Fromステートメントのあとに抽出データのあるテーブル名を指定し、Whereステートメントで検索対象のフィールド名を指定します。
 ここでは、抽出するフィールドは「会員No」「氏名」「入会年月日」「住所1」の4つなので、これをテーブル名「会員名簿」を「.」でつないでSelectステートメントの後ろに記述します。

QString = "Select 会員名簿.会員No,会員名簿.氏名,会員名簿.入会年月日,
      会員名簿.住所1 
 取り出すテーブル名は「会員名簿」なので、Fromステートメントでこのテーブル名を指定します。
From 会員名簿 
 検索対象のフィールドは「入会年月日」なので、Whereステートメントでこのフィールドを、テーブル名をつけて指定します。
Where 会員名簿.入会年月日 
 検索を行なうには、SQLステートメントの「演算子」を使用します。
 今回は、フィールド「入会年月日」の各レコードデータを検索し、「月名」に一致するデータを取り出すようにしていますので、Likeという演算子を使用します。この演算子は、検索値を与えて実行すると、その値と同じ値が含まれるデータを検索します。例えば、
Like '08'
と記述してクエリーを実行すると、「08」が含まれる値を検索して抽出してきます。また、ワイルドカード「*」と組み合わせて使用すると、その文字を含む文字列を探し出すことができます。例えば、
Like '08*'
と実行すると、先頭が「08」ではじまる文字列を検索します。
 今回検索対象とするフィールド「入会年月日」は、「99/08/01」という形式の日付で、文字列としてデータが入力されています。ですので、このデータに対して月名の検索を行なうには、
Like '*08*'
と記述します。文字列の先頭は年を表わす数字になっていますが、「99/08」も「98/08」も検索したいので、「*08*」というように、月数「08」の含まれるデータが検索一致データとなるようにします。しかし、これだと「99/11/08」のように日付が「08」のデータも検索されてしまう可能性があります。そこで、月数の前後にある「/」も検索対象の文字列に含め、
Like '*/08/*'
とすると、「/08/」が含まれる文字列だけが一致するデータになり、検索対象を月数に限定することができます。

すでに、メインのプロシージャにあるInputBox関数とその後の文字列処理によって、このLike演算子に使用する検索文字列は作成されています。そして、その文字列はこのFunctionプロシージャ「RunQuery」の引数に設定した変数「IPTData」を使って、プロシージャに渡されます。そこで、Like演算子に、変数「IPTData」をセットすれば、インプットボックスに入力した月数でSQL文字列を作成することができます。

Like IPTData 
 しかし、上記の記述ではクエリーに失敗します。なぜなら、変数名が文字列で作成されてしまうからです。
 ここまでの記述全体を見ると、次のようになります。
QString = "Select 会員名簿.会員No,会員名簿.氏名,会員名簿.入会年月日, 
      会員名簿.住所1 From 会員名簿 Where 会員名簿.入会年月日
      Like IPTData"
 これでは、変数名「IPTData」が文字列として指定されてしまい、Like演算子は「IPTData」という文字列を含むデータを検索してしまうからです。
 必要なのは、変数に格納されている文字列なので、これをきちんとLike以前の文字列に結合しなければなりません。それには、変数IPTDataの中身が、SQLステートメントとして正しく認識されるように、若干の加工をしてあげる必要があります。そこで、文字列連結演算子「&」で変数を結合することと、変数の前後に「'」という、文字列であることを表わす記号を付ける処理を追加します。
Like" & "'" & IPTData & "'"
ちょっとわかりづらい記述ですが、「Select」から「Like」までをひとつの文字列で一度作成し、その後ろに「'」を付けて変数「IPTData」を指定し、最後にもう一度「'」を付けてあげます。
 以上のことを反映したSQL文字列を全文記述すると、
QString ="Select 会員名簿.会員No,会員名簿.氏名,会員名簿.入会年月日, 
      会員名簿.住所1 From 会員名簿 Where 会員名簿.入会年月日 
      Like" & "'" & IPTData & "'"
という記述になります。「"Select」から始まり最後の「" ' "」までを、改行を入れずに1行で記述します。
 こうすることで、たとえば変数「IPTData」に「*/07/*」という文字列が格納されていれば、このSQL文字列は、
QString = "Select 会員名簿.会員No,会員名簿.氏名,会員名簿.入会年月日,
      会員名簿.住所1 From 会員名簿 Where 会員名簿.入会年月日 
      Like'*/07/*'
という文字列に変換されて認識されるようになります。
 SQLを使ったクエリーでは、このSQL文字列の作成が大きなポイントになります。すべて文字列で記述する場合は問題ありませんが、変数と組み合わせる場合には、必ず結果として文字列になるように、記号の結合などと組み合わせておく必要があります。

クエリーのためのSQL文字列が作成できたら、この文字列を使って実際にクエリーを実行します。
 DAOでクエリーを実行するには、RecordsetオブジェクトのCreateQueryDefメソッドを使用します(書式5)。このメソッドは、QueryDefオブジェクトを作成するメソッドで、クエリーの操作はこのオブジェクトを介して行なうことになります。

書式5:CreateQueryDefメソッドの書式
  Set オブジェクト変数 = object.CreateQueryDef (name, sqltext)
 
 object
 Databaseオブジェクトを表わすオブジェクト変数を指定
 
 name
 省略可能。 新しいQueryDefオブジェクトの名前を文字列で指定
 
 sqltext
 省略可能。 実行するSQLステートメントを指定

 CreateQueryDefメソッドは、2つの引数をもちます。最初の引数に、作成するQueryDefオブジェクトの名前を指定します。2番目の引数に、実行するSQLステートメントを指定します。メソッドが実行されると、SQLステートメントにもとづきクエリーが実行され、QueryDefオブジェクトが作成されてそのオブジェクト名が返されます。
 最初の引数に名前を指定しないと、作成されたQueryDefオブジェクトは一時的なものになり、プロシージャの終了とともに抹消されます。とりあえずここでは、一時的なQueryDefオブジェクトとしてクエリーを実行することにします。

Set NewQRY = DB.CreateQueryDef("", QString)
 このメソッドでクエリーが実行され、結果は作成されたQueryDefオブジェクトにクエリーテーブルとして作成され、Recordsetオブジェクトとして格納されています。
 しかし、このままではクエリーの結果を取得できません。今度は、このクエリーテーブルを開く必要があります。そこで、今CreateQueryDefメソッドが返してきたオブジェクトへの参照を使用して、OpenRecordsetメソッドを実行します。これで初めて、クエリーの結果を把握することができます。
 メソッドの実行結果は、Recordsetオブジェクトとして返されますので、オブジェクト変数に格納しておきます。
Set QResult = NewQRY.OpenRecordset()
 これで、クエリー結果をテーブルの操作と同じような操作でアクセスできるようになります。

クエリー結果を操作する前に、クエリーが失敗した時のためのエラー処理を作成しておきます。
 QueryDefオブジェクトの中のRecordsetオブジェクトの、「RecordCount」プロパティを調べると、作成されたクエリーテーブルにレコードがあるかどうかを把握できます。この値が「0」であれば、クエリーテーブルのレコードが0件ということで、一致する条件のデータが見つからなかったということになりますので、クエリーが失敗したとみなし、このプロシージャの戻り値に「False」をセットしプロシージャを終了します。

If QResult.RecordCount = 0 Then
    MsgBox "該当データがありません"
    RunQuery = False
    Exit Function
End If

RecordCountプロパティの値が「0」でなければ、少なくとも1件のレコードがクエリーテーブルにあることになりますので、今度はその件数を把握します。
 クエリーテーブルの場合は、Accessのテーブルのようにテーブルを開いた時点でRecordCountプロパティに、総レコード数が格納されているわけではありません。一度、「MoveLast」メソッドを使用して最後尾のレコード位置にカレントレコードを移動させます。こうすることで、「RecordCount」プロパティには全レコード数が格納されますので、クエリーによる抽出結果の総レコード数を把握できます。そして、その数をメッセージボックスで表示するようにします。

QResult.MoveLast
MsgBox QResult.RecordCount & "件のデータを抽出しました。"
 テーブルのレコードには、現在アクティブになっているレコード、カレントレコードがあります。しかし、レコードの先頭が常にカレントレコードであるとは限りません。そこで、このカレントレコードの位置を操作するメソッドがいくつか用意されています。
 MoveLastメソッドは、カレントレコードをレコードの最後尾に移動させます。カレントレコードをテーブルのレコードの先頭にもってゆくには、MoveFirstメソッドを使用します。そして、MoveNextメソッドは、カレントレコードの位置を現在位置の次のレコードに移動させます。

一方、テーブルからレコードデータを取得するには、一度特定のレコードにカレントレコードを移し、その位置から指定するフィールドデータを取り出します。
 先頭のレコード位置にあるデータを取り出したければ、一度MoveFirstメソッドでカレントレコードの位置をレコードの先頭にもってゆき、そこからFieldsコレクションオブジェクトのValueプロパティの値を取得します。先頭から2番目のレコード値のデータを取り出したい場合は、MoveNextメソッドで、カレントレコードを次のレコード位置に移動し、FieldsコレクションオブジェクトのValueプロパティの値を取得します。
 この方法を使用し、For...Nextステートメントで、「1」から「RecordCount」プロパティの値までループを繰り返せば、クエリー結果の全件のレコードデータを取得できます。
 まず、MoveFirstメソッドで、カレントレコードを先頭のレコード位置に移動します。

QResult.MoveFirst
 次に、For...Nextループ内でMoveNextメソッドを使って、カレントレコード位置をひとつずつ移動させながら、FieldsコレクションオブジェクトのValueプロパティの値を、ワークシート「Sheet2」のセルに転送してゆきます。
 クエリーで抽出した(SQLのSelectステートメントで指定した)フィールドは4つですから、Fieldsプロパティの添え字に「0」から「3」までを指定すれば、抽出した全フィールドのデータを取得することができます。
With Worksheets("Sheet2")
    For i = 1 To QResult.RecordCount
        .Cells(i, 1) = QResult.Fields(0).Value
        .Cells(i, 2) = QResult.Fields(1).Value
        .Cells(i, 3) = QResult.Fields(2).Value
        .Cells(i, 4) = QResult.Fields(3).Value
        QResult.MoveNext
    Next
End With
 たとえば、インプットボックスに「08」と入力してクエリーを実行すると(検索文字列は「*/08/*」)、「98/08/07」「98/08/11」「98/08/12」の3件のデータが抽出され、これらのレコードの「会員No」「氏名」「入会年月日」「住所1」の4つのフィールドデータが、ワークシートSheet2に転送されます。

クエリーが成功したら、Functionプロシージャの戻り値に「True」をセットし、開いた各テーブル、データベースファイル、ワークスペースを閉じます。この時、クエリーで作成したクエリーテーブルも閉じることを忘れないでください。

RunQuery = True

QResult.Close
TBL.Close
DB.Close
WSP.Close
 最後に使用したオブジェクト変数にNothingを代入して、プロシージャを終了します。
Set QResult = Nothing
Set TBL = Nothing
Set DB = Nothing
Set WSP = Nothing