こんにちは!Access VBAの世界へようこそ。
マクロの記録ボタンをポチポチ押すだけのステップから抜け出し、「自分の手でシステムをコントロールしたい!」という熱意を持っているあなたなら、きっと今日のテーマは最高にワクワクする内容になるはずです。
今回は、Access開発の現場で中級者への登竜門とされる「クロス集計クエリの列見出し動的変更」について、徹底的に解説していきます。
「クロス集計の列(項目)が、データの追加によって勝手に増えたり減ったりして困る……」
「レポートのデザインが固定できなくて途方に暮れている……」
そんな悩みを、`QueryDef`(クエリ定義)をVBAで自在に操ることで鮮やかに解決してみせましょう。
ここをクリアすれば、あなたのAccess VBAスキルは間違いなく一段上のステージに到達します。さあ、一緒に本質を学んでいきましょう!
—
1. なぜクロス集計クエリの動的化で悩むのか?
Accessの「クロス集計クエリ」は、エクセルでいうピボットテーブルのようなもので、データを縦と横にクロスさせて集計する非常に強力な機能です。例えば、以下のようなSQLを見たことがあるかもしれません。
TRANSFORM Sum(売上金額)
SELECT 顧客名
FROM T_売上
GROUP BY 顧客名
PIVOT 月;
このクエリを実行すると、行に「顧客名」、列に「月(1月, 2月, 3月…)」が並ぶ綺麗な表が完成します。
固定クエリの限界
しかし、ここで大きな問題が発生します。
もし来月になって「4月」のデータが新しく追加されたとき、`PIVOT 月;` のままであれば自動で4月の列が出てくることもありますが、逆に「過去のデータが変わって列の構成が変わった」「特定の条件に合致する列だけを動的に出し分けたい」という場合、デザイン画面で作った固定のクエリでは太刀打ちできなくなります。
特に、Accessのレポートにクロス集計クエリをバインド(連結)している場合、列名が固定されていないと「あれ?コントロールが見つからない!」というエラー(3078や2450など)のオンパレードになってしまうのです。
—
2. 解決の鍵:`QueryDef`オブジェクトによるSQLの動的書き換え
ここで登場するのが、今回の主役である`QueryDef`(クエリ定義)オブジェクトです。
Accessのナビゲーションウィンドウに見えている「クエリ」は、VBAの世界では`QueryDef`というオブジェクトとして扱われます。これを利用すると、「クエリを実行する直前に、VBAでSQL文の文字列を組み立てて上書き保存し、その直後に実行する」という神業が可能になります。
全体の流れ(イメージ図)
[VBAコード]
↓ 1. データベース内の最新マスタ等から「現在の列見出し(例: 4月, 5月…)」をかき集める
↓ 2. TRANSFORM句やPIVOT句を含んだSQL文の文字列を動的に組み立てる
↓ 3. QueryDefのSQLプロパティを書き換える
[Accessクエリ (Q_DynamicCross)] ← ここが書き換わる!
↓ 4. 書き換わったクエリをレポートやフォームのデータソースとして開く
このアプローチの最大のメリットは、「あらかじめデザイン画面でクエリを作り込んでおく必要がなく、常に最新の状況に合わせたSQLをオンデマンドで生成できる」という点にあります。
—
3. 実践!動的クロス集計クエリを生成するVBAコード
それでは、実際に開発現場でそのまま使える実用的なコードを見ていきましょう。
今回は例として、「商品カテゴリごとの月別売上」を動的に集計するシナリオを想定します。
以下のコードを標準モジュールに貼り付けてみてください。
Option Compare Database
Option Explicit
Public Sub GenerateDynamicCrossQuery()
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim strSQL As String
Dim rsColumns As DAO.Recordset
Dim strPivotColumns As String
‘ データベースオブジェクトの取得
Set db = CurrentDb
On Error GoTo ErrorHandler
‘ ————————————————–
‘ ステップ1: PIVOT句に使う「列見出し」を動的に取得する
‘ (例として、T_売上テーブルから存在する「月」の一覧を重複なく取得)
‘ ————————————————–
strSQL = “SELECT DISTINCT 销售月 FROM T_売上 ORDER BY 销售月;”
Set rsColumns = db.OpenRecordset(strSQL, dbOpenSnapshot)
‘ 取得した列見出しをカンマ区切りの文字列にまとめる (IN句用)
strPivotColumns = “”
Do While Not rsColumns.EOF
‘ 文字列型や日付型の場合はシングルクォーテーションが必要ですが、
‘ 今回は数値の「月」(例: 1, 2, 3…)と仮定してそのまま結合します
If strPivotColumns = “” Then
strPivotColumns = rsColumns.Fields(0).Value
Else
strPivotColumns = strPivotColumns & “, ” & rsColumns.Fields(0).Value
End If
rsColumns.MoveNext
Loop
rsColumns.Close
‘ 列が見つからなかった場合のガード
If strPivotColumns = “” Then
MsgBox “集計対象のデータが存在しません。”, vbExclamation, “処理中断”
GoTo Cleanup
End If
‘ ————————————————–
‘ ステップ2: TRANSFORM句を含む完全なSQL文を組み立てる
‘ ————————————————–
‘ 【重要ポイント】
‘ PIVOT … IN (…) を使うことで、データの有無に関わらず
‘ 常に決まった順序・決まった列の数でクロス集計結果を固定化できます。
strSQL = “TRANSFORM Nz(Sum(T_売上.売上金額), 0) ” & _
“SELECT T_売上.商品カテゴリ ” & _
“FROM T_売上 ” & _
“GROUP BY T_売上.商品カテゴリ ” & _
“PIVOT T_売上.销售月 IN (” & strPivotColumns & “);”
‘ ————————————————–
‘ ステップ3: QueryDefオブジェクトを使ってクエリのSQLを書き換える
‘ ————————————————–
‘ あらかじめ「q_DynamicCross」という名前の空のクエリを定義しておきます
Set qdf = db.QueryDefs(“q_DynamicCross”)
‘ SQLプロパティに、今組み立てた動的SQLを代入!
qdf.SQL = strSQL
‘ ————————————————–
‘ ステップ4: 確認のため、データシートビューでクエリを開く
‘ ————————————————–
DoCmd.OpenQuery “q_DynamicCross”, acViewNormal, acReadOnly
MsgBox “動的クロス集計クエリの生成に成功しました!”, vbInformation, “完了”
Cleanup:
‘ オブジェクトの解放(メモリリークを防ぐプロの作法)
If Not rsColumns Is Nothing Then rsColumns.Close
Set rsColumns = Nothing
Set qdf = Nothing
Set db = Nothing
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
Resume Cleanup
End Sub
コードのここがポイント!
1. `DISTINCT` による列の動的収集
`T_売上` テーブルから今ある「月」を自動で拾い集め、`strPivotColumns` という変数に `1, 2, 3, 4` のようにカンマ区切りで格納しています。
2. `IN (…)` 句の魔法
`PIVOT … IN (1, 2, 3, 4)` と明示的に指定することで、たとえ「3月」のデータが全くなかろうと、常に1月〜4月の4列を強制的に出力させることができます。これにより、レポート側のコントロールが消えてしまうエラーを完全に防げます。
3. `Nz(…, 0)` によるNULL対策
クロス集計でデータが存在しないセルは自動的に `Null` になり、レポートなどで表示崩れの原因になります。`Nz` 関数で包むことで、空白セルを「0」に変換するプロのテクニックを組み込んでいます。
—
4. 陥りやすい罠とエンジニアの知見
このテクニックを実践する際、多くの開発者がハマる「罠」がいくつかあります。先輩エンジニアからのアドバイスとして心に留めておいてください。
罠その1:クエリを事前に作っていないエラー(実行時エラー 3265)
VBA内で `db.QueryDefs(“q_DynamicCross”)` と指定する際、Accessのナビゲーションウィンドウに同名のクエリが存在しないと、容赦なくエラー3265(アイテムが見つかりません)が発生します。
- 対策: あらかじめAccessの画面で「デザインビューで適当なクエリを作り、名前を `q_DynamicCross` として保存しておく(中身のSQLは何でも良い)」という下準備を必ず行ってください。
罠その2:文字列型や日付型データの列見出し指定
今回のサンプルコードでは「月が数値(1, 2, 3…)」であると仮定しましたが、列見出しが「文字列(例: ‘東京’, ‘大阪’, ‘福岡’)」や「日付」の場合、`IN` 句の中に渡す文字列にはシングルクォーテーション (`’`)が必要になります。
- 対策: 文字列の列見出しを展開する場合は、ループ内で `’` を前後に付加するエスケープ処理を忘れずに実装しましょう。
—
5. まとめ
今回は、Access VBAにおける最難関の一つである「クロス集計クエリの列見出し動的変更(QueryDefの書き換え)」について解説しました。
- クロス集計の列が固定できない悩みは、VBAによるSQLの動的生成で解決できる。
- `QueryDef` オブジェクトを使えば、クエリの設計図をプログラムから自由自在に書き換えられる。
- `PIVOT … IN (…)` を組み合わせることで、出力される列をコントロールし、レポートエラーを未然に防げる。
ここをマスターしたあなたは、もはや単なる「Accessの利用者」ではなく、立派な「Accessデータベース・アーキテクト」です。業務の自動化や、変化に強い柔軟なシステム構築に、ぜひこの知見を役立ててください。
あなたのAccess開発ライフが、より一層素晴らしいものになりますように。それでは、次のステップでお会いしましょう!
