Excel VBA | 上級問題セット

VBA
スポンサーリンク

では 12問すべて の模範解答コードを 1問ずつ、コード + 丁寧な解説(なぜそう書くか・注意点・テスト方法)付きでまとめます。
長いので見やすく分割してあります。実際に試すときは各コードを 新しいモジュール/クラスモジュール に貼ってください(クラスは必ず「挿入 → クラスモジュール」で作り、ファイル名を指示どおりにしてください)。

  1. 1. クラスモジュール:Employee(社員オブジェクト)
    1. Employee クラス(クラスモジュール名を Employee に)
    2. 呼び出し例(標準モジュール)
    3. 解説・ポイント
  2. 2. クラス:Product(小計・税込を返す)
    1. Product クラス(クラスモジュール名 Product)
    2. 呼び出し例
    3. 解説・注意点
  3. 3. クラス:CSV を読み込んで LineItem を返す
    1. LineItem クラス(クラスモジュール名 LineItem)
    2. CSVReader クラス(クラスモジュール名 CSVReader)
    3. 呼び出し例(標準モジュール)
    4. 解説・注意点
  4. 4. Dictionary:社員名→部署マップ
    1. サンプルコード(標準モジュール)
    2. 解説・ポイント
  5. 5. Dictionary に Product オブジェクトを格納(商品ID→Product)
    1. 前提:Product クラスは問題2と同じものを利用
    2. サンプルコード(標準モジュール)
    3. 解説
  6. 6. JSON:Dictionary → JSON(変換)
    1. サンプル(標準モジュール)
    2. 解説・注意点
  7. 7. JSON:JSON文字列 → パースして名前一覧を表示
    1. サンプル(標準モジュール)
    2. 解説
  8. 8. Web API:HTTP GET(WinHttp)
    1. サンプル(標準モジュール)
    2. 解説・注意点
  9. 9. Web API:HTTP POST(JSON送信)
    1. サンプル(標準モジュール)
    2. 解説
  10. 10. ADO:Access (.accdb) から社員一覧を取得
    1. サンプル(標準モジュール)
    2. 解説・注意点
  11. 11. ADO:INSERT でデータを追加
    1. サンプル(標準モジュール)
    2. 解説・注意点
  12. 12. 標準モジュール設計:CSV→フィルタ→JSON→API を責務分離して実装
    1. モジュール構成(ファイル名)
    2. CsvReader.bas
    3. ScoreFilter.bas
    4. JsonWriter.bas
    5. ApiClient.bas
    6. Main.bas
    7. 解説・設計意図
  13. 最後に — 実行時のチェックポイント & トラブルシューティング

1. クラスモジュール:Employee(社員オブジェクト)

目的:クラス(オブジェクト)にプロパティとメソッドを持たせ、標準モジュール側で使う。

Employee クラス(クラスモジュール名を Employee に)

' クラスモジュール: Employee
Private pName As String
Private pAge  As Long
Private pDepartment As String

Public Property Get Name() As String
    Name = pName
End Property
Public Property Let Name(val As String)
    pName = val
End Property

Public Property Get Age() As Long
    Age = pAge
End Property
Public Property Let Age(val As Long)
    pAge = val
End Property

Public Property Get Department() As String
    Department = pDepartment
End Property
Public Property Let Department(val As String)
    pDepartment = val
End Property

Public Function GetInfo() As String
    GetInfo = pName & " / " & pAge & " / " & pDepartment
End Function
VB

呼び出し例(標準モジュール)

Sub TestEmployee()
    Dim e1 As Employee, e2 As Employee, e3 As Employee
    Set e1 = New Employee
    Set e2 = New Employee
    Set e3 = New Employee

    e1.Name = "田中": e1.Age = 30: e1.Department = "総務"
    e2.Name = "佐藤": e2.Age = 25: e2.Department = "営業"
    e3.Name = "鈴木": e3.Age = 40: e3.Department = "開発"

    Debug.Print e1.GetInfo
    Debug.Print e2.GetInfo
    Debug.Print e3.GetInfo
End Sub
VB

解説・ポイント

  • カプセル化のため Private なフィールドを持ち Property Get/Let で公開する。こうすると将来の変更に強くなる(例:入力検証を追加できる)。
  • GetInfo はクラス内のデータをフォーマットして返す簡単なメソッド。
  • テストは Debug.Print で Immediate ウィンドウに出力するか MsgBox でも可。

2. クラス:Product(小計・税込を返す)

目的:商品クラスに数量・単価を持たせ、小計と税込(税率可)を返す。

Product クラス(クラスモジュール名 Product)

' クラスモジュール: Product
Private pUnitPrice As Double
Private pQty As Long
Private Const TAX_RATE As Double = 0.1 ' 10%

Public Property Get UnitPrice() As Double
    UnitPrice = pUnitPrice
End Property
Public Property Let UnitPrice(val As Double)
    pUnitPrice = val
End Property

Public Property Get Qty() As Long
    Qty = pQty
End Property
Public Property Let Qty(val As Long)
    pQty = val
End Property

Public Function Subtotal() As Double
    Subtotal = pUnitPrice * pQty
End Function

Public Function TaxIncluded() As Double
    TaxIncluded = Subtotal() * (1 + TAX_RATE)
End Function
VB

呼び出し例

Sub TestProduct()
    Dim p As Product
    Set p = New Product
    p.UnitPrice = 120
    p.Qty = 3
    Debug.Print "小計=" & p.Subtotal          ' 360
    Debug.Print "税込=" & p.TaxIncluded      ' 396 (360 * 1.1)
End Sub
VB

解説・注意点

  • 税率を定数にしておくとメンテナンスしやすい。国や取引で可変なら Property にする。
  • 金額計算は Double を使うが、金融アプリなら丸め方(Decimal 等)に注意。

3. クラス:CSV を読み込んで LineItem を返す

目的:CSVを読み込み、1行を1オブジェクト(LineItem)にしてコレクションで返す。

LineItem クラス(クラスモジュール名 LineItem)

' クラスモジュール: LineItem
Private pColumns As Variant

Public Property Get Columns() As Variant
    Columns = pColumns
End Property

Public Property Let Columns(val As Variant)
    pColumns = val
End Property

Public Function GetColumn(idx As Long) As String
    If idx >= LBound(pColumns) And idx <= UBound(pColumns) Then
        GetColumn = pColumns(idx)
    Else
        GetColumn = ""
    End If
End Function
VB

CSVReader クラス(クラスモジュール名 CSVReader)

' クラスモジュール: CSVReader
Public Function Load(path As String) As Collection
    Dim col As New Collection
    Dim fnum As Integer: fnum = FreeFile
    Dim line As String
    Open path For Input As #fnum
    Do While Not EOF(fnum)
        Line Input #fnum, line
        Dim item As LineItem
        Set item = New LineItem
        item.Columns = Split(line, ",")
        col.Add item
    Loop
    Close #fnum
    Set Load = col
End Function
VB

呼び出し例(標準モジュール)

Sub TestCSVReader()
    Dim reader As New CSVReader
    Dim rows As Collection
    Set rows = reader.Load("C:\Test\sample.csv") ' フルパスに置き換え

    Dim item As LineItem
    Dim i As Long
    For i = 1 To rows.Count
        Set item = rows(i)
        Debug.Print "行 " & i & " 列2=" & item.GetColumn(1) ' 0ベース
    Next i
End Sub
VB

解説・注意点

  • Split で簡易CSVを分割している。カンマを含む要素・引用符付きCSVには対応しない(実務では専用パーサーが必要)。
  • LineItem.Columns は 0ベースの配列になる点に注意(Split の仕様)。
  • ファイルパスの存在チェックやエラーハンドリング(On Error)を本番では追加。

4. Dictionary:社員名→部署マップ

目的:Scripting.Dictionary の基本操作(追加・取得・削除・列挙)を練習。

サンプルコード(標準モジュール)

Sub TestDictionary()
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary") ' late binding

    ' 追加
    dic.Add "佐藤", "開発"
    dic.Add "田中", "総務"
    dic("鈴木") = "営業" ' 既存キーは代入、未登録は追加

    ' 検索
    If dic.Exists("田中") Then
        Debug.Print "田中 の部署は " & dic("田中")
    End If

    ' 一覧表示
    Dim key As Variant
    For Each key In dic.Keys
        Debug.Print key & " -> " & dic(key)
    Next key

    ' 削除
    dic.Remove "田中"

    ' 全削除
    dic.RemoveAll
End Sub
VB

解説・ポイント

  • CreateObject("Scripting.Dictionary") は late binding。早期バインドにしたい場合は「参照設定 → Microsoft Scripting Runtime」を使い Dim dic As Scripting.Dictionary と書ける。
  • Exists で存在確認をする習慣をつけるとエラー回避になる。

5. Dictionary に Product オブジェクトを格納(商品ID→Product)

目的:オブジェクトを Dictionary に格納するパターン。

前提:Product クラスは問題2と同じものを利用

サンプルコード(標準モジュール)

Sub TestProductDictionary()
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")

    Dim p As Product

    Set p = New Product
    p.UnitPrice = 100
    p.Qty = 2
    dic.Add "A001", p

    Set p = New Product
    p.UnitPrice = 250
    p.Qty = 1
    dic.Add "A002", p

    ' ID A001 の税込価格を表示
    If dic.Exists("A001") Then
        Debug.Print "A001税込=" & dic("A001").TaxIncluded
    End If
End Sub
VB

解説

  • Dictionary の値にオブジェクトを入れると、後でそのオブジェクトのメソッドやプロパティを直接呼べる(dic("A001").TaxIncluded)。
  • 破棄(Set obj = Nothing)は明示的には必要ないが、長時間動くアプリではリソース管理に注意。

6. JSON:Dictionary → JSON(変換)

目的:VBAで辞書をJSONに変換する(VBA-JSON 等を利用)。

前提JsonConverter.bas(VBA-JSON)がプロジェクトにインポートされ、JsonConverter モジュールが利用できること。
(VBA-JSON は外部ライブラリなので、入手・インポートしてから実行してください)

サンプル(標準モジュール)

Sub TestDictToJson()
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")

    dict("Name") = "山田"
    dict("Age") = 40

    Dim skills As Variant
    skills = Array("Excel", "VBA", "SQL")
    dict("Skills") = skills

    Dim json As String
    json = JsonConverter.ConvertToJson(dict, Whitespace:=2) ' 整形出力
    Debug.Print json
End Sub
VB

解説・注意点

  • ConvertToJsonDictionary と配列をJSONに変換する。Whitespace オプションで見やすく整形できる。
  • JsonConverter がない場合は別途ダウンロード(Github等)して読み込む必要がある(これは実行環境依存)。

7. JSON:JSON文字列 → パースして名前一覧を表示

目的:JSON をパースして VBA の配列や Dictionary として使う。

サンプル(標準モジュール)

Sub TestJsonParse()
    Dim json As String
    json = "[{""Name"": ""田中"", ""Score"": 80},{""Name"": ""鈴木"", ""Score"": 90}]"

    Dim parsed As Object
    Set parsed = JsonConverter.ParseJson(json) ' parsed は Collection (配列相当)

    Dim item As Variant
    For Each item In parsed
        Debug.Print item("Name") ' Dictionaryとして扱える
    Next item
End Sub
VB

解説

  • ParseJson は JSON 配列を Collection(順序付き)で返し、各要素は Dictionary(キーで参照)となる。
  • 型は late-binding の Object/Variant で扱うのが一般的。

8. Web API:HTTP GET(WinHttp)

目的:外部の REST API へ GET リクエストを送り JSON を受け取り解析する。

サンプル(標準モジュール)

Sub TestHttpGet()
    Dim url As String
    url = "https://jsonplaceholder.typicode.com/posts/1"

    Dim http As Object
    Set http = CreateObject("WinHttp.WinHttpRequest.5.1")

    http.Open "GET", url, False
    http.SetRequestHeader "Accept", "application/json"
    http.Send

    If http.Status = 200 Then
        Dim body As String
        body = http.ResponseText
        Debug.Print body

        ' JSON をパースして title を取得 (JsonConverter 前提)
        Dim obj As Object
        Set obj = JsonConverter.ParseJson(body)
        Debug.Print "title = " & obj("title")
    Else
        Debug.Print "HTTP Error: " & http.Status
    End If
End Sub
VB

解説・注意点

  • WinHttpRequest は比較的使いやすく、同期(False)での呼び出し例。
  • HTTPS を使う API はプロキシや証明書の設定によって追加処理が必要になることがある(企業ネットワーク)。
  • CORS はブラウザ側の概念なので、VBA側は影響を受けない(ただしサーバーが認証を要求する場合はヘッダ追加が必要)。

9. Web API:HTTP POST(JSON送信)

目的:JSON を POST してレスポンスを受け取る。

サンプル(標準モジュール)

Sub TestHttpPost()
    Dim url As String
    url = "https://jsonplaceholder.typicode.com/posts" ' テスト用のPOSTエンドポイント

    Dim payload As String
    payload = "{""name"":""Taro"",""age"":20}"

    Dim http As Object
    Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
    http.Open "POST", url, False
    http.SetRequestHeader "Content-Type", "application/json"
    http.Send payload

    Debug.Print "Status=" & http.Status
    Debug.Print http.ResponseText
End Sub
VB

解説

  • Content-Type を正しくセットすることが重要。
  • 実運用のAPIは認証(APIキー、Bearerトークン等)が必要なことが多い。SetRequestHeader "Authorization", "Bearer xxxxx" のように設定する。
  • テスト環境では jsonplaceholder.typicode.com のようなダミーサービスで試すと安全。

10. ADO:Access (.accdb) から社員一覧を取得

目的:ADO を使った DB 接続とデータ取得。

サンプル(標準モジュール)

Sub TestAdoSelect()
    Dim cn As Object, rs As Object
    Set cn = CreateObject("ADODB.Connection")
    Set rs = CreateObject("ADODB.Recordset")

    Dim dbPath As String
    dbPath = "C:\Test\test.accdb" ' 実際のパスに変更

    Dim connStr As String
    connStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath & ";Persist Security Info=False;"
    On Error GoTo ErrHandler
    cn.Open connStr

    Set rs = cn.Execute("SELECT Name FROM Employees")
    Do While Not rs.EOF
        Debug.Print rs.Fields("Name").Value
        rs.MoveNext
    Loop

Cleanup:
    On Error Resume Next
    If Not rs Is Nothing Then
        If rs.State = 1 Then rs.Close
    End If
    If Not cn Is Nothing Then
        If cn.State = 1 Then cn.Close
    End If
    Set rs = Nothing
    Set cn = Nothing
    Exit Sub

ErrHandler:
    Debug.Print "Error: " & Err.Number & " - " & Err.Description
    Resume Cleanup
End Sub
VB

解説・注意点

  • Microsoft.ACE.OLEDB.12.0 は 64bit/32bit Excel とドライバの整合性に注意。環境により Microsoft.Jet.OLEDB.4.0(古い)や別の接続文字列が必要になる。
  • Accessファイルのパスや権限(読み取り権)が必要。
  • SQLはSQLインジェクションに注意(ユーザー入力を組み込む場合はパラメータ化を検討)。

11. ADO:INSERT でデータを追加

目的:テーブルへデータを追加する方法。

サンプル(標準モジュール)

Sub TestAdoInsert()
    Dim cn As Object
    Set cn = CreateObject("ADODB.Connection")

    Dim dbPath As String
    dbPath = "C:\Test\test.accdb"

    Dim connStr As String
    connStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath & ";Persist Security Info=False;"
    On Error GoTo ErrHandler
    cn.Open connStr

    Dim name As String: name = "Suzuki"
    Dim age As Long: age = 28
    Dim dept As String: dept = "Sales"

    Dim sql As String
    sql = "INSERT INTO Employees ([Name], Age, Department) VALUES ('" & Replace(name, "'", "''") & "', " & age & ", '" & Replace(dept, "'", "''") & "')"
    cn.Execute sql

    MsgBox "INSERT 完了"

Cleanup:
    On Error Resume Next
    If Not cn Is Nothing Then
        If cn.State = 1 Then cn.Close
    End If
    Set cn = Nothing
    Exit Sub

ErrHandler:
    Debug.Print "Error: " & Err.Number & " - " & Err.Description
    Resume Cleanup
End Sub
VB

解説・注意点

  • SQL内に文字列を直挿入する場合は ''' に置き換えてエスケープする(Replace(name,"'","''"))。
  • 本番ではパラメータ化(Command オブジェクト)を使うべき(セキュリティと型の安全)。
  • テーブル構造(列名・型)を事前に確認。

12. 標準モジュール設計:CSV→フィルタ→JSON→API を責務分離して実装

目的:モジュールを責務ごとに分け、テストしやすくする。ここでは実装サンプルを簡略化して示す。

モジュール構成(ファイル名)

  • CsvReader.bas(CSV読み込み機能)
  • ScoreFilter.bas(抽出ロジック)
  • JsonWriter.bas(JSON 変換)
  • ApiClient.bas(POST)
  • Main.bas(ワークフロー制御)

CsvReader.bas

' CsvReader.bas
Option Explicit

Public Function ReadCsv(path As String) As Collection
    Dim col As New Collection
    Dim fnum As Integer: fnum = FreeFile
    Dim line As String
    Open path For Input As #fnum
    Do While Not EOF(fnum)
        Line Input #fnum, line
        col.Add line
    Loop
    Close #fnum
    Set ReadCsv = col
End Function
VB

ScoreFilter.bas

' ScoreFilter.bas
Option Explicit

' 各行 "Name,Score" を受け、Score>=threshold のものを Dictionary として返す
Public Function FilterScores(lines As Collection, threshold As Long) As Collection
    Dim out As New Collection
    Dim i As Long
    For i = 1 To lines.Count
        Dim parts As Variant
        parts = Split(lines(i), ",")
        If UBound(parts) >= 1 Then
            Dim name As String: name = Trim(parts(0))
            Dim score As Long: score = CLng(Trim(parts(1)))
            If score >= threshold Then
                Dim dict As Object
                Set dict = CreateObject("Scripting.Dictionary")
                dict("Name") = name
                dict("Score") = score
                out.Add dict
            End If
        End If
    Next i
    Set FilterScores = out
End Function
VB

JsonWriter.bas

' JsonWriter.bas
Option Explicit

Public Function ToJson(items As Collection) As String
    ' JsonConverter が前提
    Dim arr() As Variant
    ReDim arr(0 To items.Count - 1)
    Dim i As Long
    For i = 1 To items.Count
        arr(i - 1) = items(i) ' items(i) は Dictionary
    Next i
    ToJson = JsonConverter.ConvertToJson(arr, Whitespace:=2)
End Function
VB

ApiClient.bas

' ApiClient.bas
Option Explicit

Public Function PostJson(url As String, json As String) As String
    Dim http As Object
    Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
    http.Open "POST", url, False
    http.SetRequestHeader "Content-Type", "application/json"
    http.Send json
    PostJson = http.ResponseText
End Function
VB

Main.bas

' Main.bas
Option Explicit

Public Sub RunPipeline()
    Dim lines As Collection
    Set lines = ReadCsv("C:\Test\scores.csv") ' CSV を用意

    Dim filtered As Collection
    Set filtered = FilterScores(lines, 80)

    Dim json As String
    json = ToJson(filtered)

    Debug.Print json

    Dim response As String
    response = PostJson("https://example.com/api/receive", json)
    Debug.Print "API Response: " & response
End Sub
VB

解説・設計意図

  • 責務分離:CSV読み込み・抽出ロジック・JSON生成・API送信を明確に分けると、ユニットテストや差し替えが簡単になる(例:APIが変わったら ApiClient だけ書き換え)。
  • テストしやすさFilterScoresCollection を受けて Collection を返すため、テスト用のダミーデータを渡して動作検証できる。
  • 拡張性:JSON化のルールを変えたい場合は JsonWriter を変更すれば他に影響を与えない。
  • 注意:実行時に JsonConverterWinHttp が使えること、CSVパス・APIエンドポイントが正しいことを確認してから実行する。

最後に — 実行時のチェックポイント & トラブルシューティング

  1. 参照設定
    • JsonConverter(VBA-JSON)を使う場合はそのモジュールをプロジェクトにインポートしてください。参照設定は不要(late binding)ですが、コードを読む・編集する上でモジュールは必須です。
    • ADO を早期バインドしたい場合は「参照設定 → Microsoft ActiveX Data Objects x.x Library」を追加できます(ただし late binding を使えば不要)。
  2. 権限・ネットワーク
    • ファイル/DB のパスと読み書き権限を確認。
    • Web API は社内プロキシやファイアウォールでブロックされていることがある(会社PCでエラーが出る場合はネットワーク管理者へ確認)。
  3. 64bit/32bit の差
    • Windows API 宣言や一部の外部参照は 64bit/32bit で違う場合がある(今回のサンプルでは回避しているが ADO ドライバ等は注意)。
  4. デバッグのコツ
    • Debug.Print を多用して途中経過を確認。
    • エラー処理は最小限(今回のサンプルでは簡潔化)→ 実運用では On Error を適切に配置する。
タイトルとURLをコピーしました