[Access VBA] 数式エラーの発生しているExcelシートをテーブルに取り込むときの注意点とサンプルコード
概要
この記事について
かんたんな概要と結論
エラーをキャッチし、代わりの値を設定するなどの対処が必要です。
こんにちは、dedeです。
この記事では、
AccessからExcelシートのデータを取り込む際に、
数式エラーが発生しているセルがあった場合を取り扱います。
その場合の注意点と、
エラーの回避方法について、
サンプルコード付きで解説します。
環境
以下は、
Office 2019のAccess環境で検証済みです。
※2022/1時点の最新バージョンのOfficeでも内容は変わりません。
現象
数式エラーとは
数式エラーについては、
Excelでテーブルの作成や集計、分析などを行った経験がある人であれば
見たことがあるかと思います。
セルに値を入力した後、
その値が不正なデータであった場合には、
#(シャープ)から始まるエラー値が表示されます。
それぞれのエラー値には、
別々の原因が存在し、
VBAで参照した際には個別の「エラー番号」を含む特殊な値を返すため、
戻り値を受け取る変数は、Variant 型である必要があります。
表示されるエラー | エラー番号 |
---|---|
#DIV/0! | 2007 |
#VALUE! | 2015 |
#NAME? | 2029 |
#REF! | 2023 |
数式エラーを持つセルを取り込む場合
数式エラーは、
シートの作業時になるべく排除しておくべきではあるでしょう。
しかしながら、
別のシステムで自動作成されたシートに含まれる数式エラーなどは、
あらかじめ排除しておくことが困難でしょう。
そのようなときに、
Accessでシートのテーブルデータを取り込むと、
予期しないエラーが発生し、
悩まさせるかもしれません。
例えば、
次のように、ある列のデータに数式エラーが発生している売上管理テーブルを取り込むことを考えます。
Access側のテーブル構造もシートに寄せます。
フィールド名 | データ型 |
---|---|
管理番号 | 数値型 |
店舗 | 短いテキスト |
売上金 | 数値型 |
調整後売上金 | 数値型 |
TransferSpreadsheetメソッド使用デモ
まず、
AccessのDoCmd.TransferSpreadsheet
メソッドを利用して
取り込むデモを行います。
1Sub Excelインポート_Docmd使用_エラー発生デモ()
2 DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel9, _
3 "売上管理テーブル", Application.CurrentProject.Path & "\" & "数式エラーのある表.xlsx", True, "売上管理テーブル!"
4End Sub
5
実行すると、
「キー違反」のアラートが表示されてしまいます。
「はい」をクリックすると、
数式エラーの発生したセルの値だけがからっぽのデータが挿入されます。
エラーメッセージが発生することや、
からっぽのデータが挿入されることは、
おそらく望んでいた挙動に沿う処理ではないでしょう。
DAOで手続き的に取り込むデモ
DAO
を用いて、
手続き的に取り込む場合は、
挙動が少々異なります。
「手続き的(Imperative )」とは、
処理の記述の方法の種類の一つで、
行いたい処理を細かく一行ずつ書いていくことを指します。
対義語は宣言的(Declarative )で、
処理の記述の際に、
行いたい処理の内容を端的に示す名前を持つ構文や関数名だけを書けば処理が行われることを指します(SQLなど)。
宣言的な関数を使うことは便利ですが、VBAにおいては自由度は手続き的な方が高いように感じます。
1
2Sub Excelインポート_DAO使用_エラー発生デモ()
3 Const xlUp = -4162
4 Dim excelPath As String
5 Dim exApp As Object
6 Dim wb As Object
7 Dim sheetValues() As Variant
8 Dim rs As Recordset
9 Dim i As Long
10
11 On Error GoTo Err
12
13 excelPath = Application.CurrentProject.Path & "\" & "数式エラーのある表.xlsx"
14
15 '//Excelアプリを立ち上げる
16 Set exApp = CreateObject("Excel.Application")
17 Set wb = exApp.Workbooks.Open(excelPath, , True)
18 '//売上管理シートを参照する
19 With wb.Worksheets(1)
20 '//売上管理シート上のテーブルデータを、行数のぶんだけ参照し、
21 '//その内容を二次元配列にパースする
22 sheetValues = .Range( _
23 .Cells(2, 1), _
24 .Cells( _
25 .Cells(.Rows.Count, 1).End(xlUp).Row, _
26 4 _
27 ) _
28 ).Value
29 End With
30
31 '//売上管理テーブルのレコードセットを開く
32 Set rs = CurrentDb.OpenRecordset("売上管理テーブル", dbOpenDynaset)
33
34 With rs
35 For i = LBound(sheetValues, 1) To UBound(sheetValues, 1)
36 '//対応するフィールドに、データを入れていく
37 .AddNew
38
39 .Fields("管理番号").Value = sheetValues(i, 1)
40 .Fields("店舗").Value = sheetValues(i, 2)
41 .Fields("売上金").Value = sheetValues(i, 3)
42 '//※※ エラーの発生
43 .Fields("調整後売上金").Value = sheetValues(i, 4)
44
45 .Update
46 Next i
47
48 End With
49
50Exits:
51
52 rs.Close
53 wb.Close
54 exApp.Quit
55
56 Exit Sub
57
58Err:
59
60 MsgBox Err.Description, vbExclamation, Err.Number
61 GoTo Exits
62
63End Sub
64
65
これを実行すると、
「Fields2オブジェクトのエラー」という文言で、
調整後売上金を挿入する際にエラーが発生します。
どうすればいいのか?
では、どうすればエラーを回避する、
あるいは利用者にエラーについてアラートしてあげることができるのか?
それについて、次のセクションで見ていきます。
回避策
ABOUT
発生したエラーを検知するには、
TransferSpreadsheet
メソッドではなく、
DAO
を用いてコードを記述する必要があります。
エラー発生箇所において、
VBA.Information
のメンバーであるIsError
メソッドを利用して、
エラー発生有無を検知します。
IsError(value)
は、valueがエラーの場合のみTrueを返します。
1'//※※ 例:エラーの発生箇所で条件分岐を行う
2.Fields("調整後売上金").Value = IIF(IsError(sheetValues(i, 4)),エラー発生した場合の値,エラー発生なしの場合の値)
なお、
説明で使用したファイルについて、
こちらからダウンロードできます。
1. エラーの場所を教えてあげる
エラー検知した際にエラーの発生箇所を教えてあげるためには、
次のようにエラー情報を変数に退避し、メッセージなどに渡す必要があります。
なお、このケースでは、エラー発生時はテーブルにはデータを格納しないものとします。
1
2Sub Excelインポート_エラー発生箇所を通知()
3 Const xlUp = -4162
4 Dim excelPath As String
5 Dim exApp As Object
6 Dim wb As Object
7 Dim sheetValues() As Variant
8 Dim myWorkspase As Workspace
9 Dim myDB As DAO.Database
10 Dim rs As Recordset
11 Dim i As Long
12 Dim errAlertText As String
13 Dim isUpdatable As Boolean
14 Dim isCommit As Boolean
15
16 On Error GoTo Err
17
18 excelPath = Application.CurrentProject.Path & "\" & "数式エラーのある表.xlsx"
19
20 '//Excelアプリを立ち上げる
21 Set exApp = CreateObject("Excel.Application")
22 Set wb = exApp.Workbooks.Open(excelPath, , True)
23 '//売上管理シートを参照する
24 With wb.Worksheets(1)
25 '//売上管理シート上のテーブルデータを、行数のぶんだけ参照し、
26 '//その内容を二次元配列にパースする
27 sheetValues = .Range( _
28 .Cells(2, 1), _
29 .Cells( _
30 .Cells(.Rows.Count, 1).End(xlUp).Row, _
31 4 _
32 ) _
33 ).Value
34 End With
35
36 '//トランザクションの開始
37 Set myWorkspase = DBEngine.Workspaces(0)
38 myWorkspase.BeginTrans
39 isCommit = False
40
41 '//DBの取得
42 Set myDB = myWorkspase.Databases(0)
43
44 '//売上管理テーブルのレコードセットを開く
45 Set rs = myDB.OpenRecordset("売上管理テーブル", dbOpenDynaset)
46
47 With rs
48 errAlertText = ""
49 For i = LBound(sheetValues, 1) To UBound(sheetValues, 1)
50 '//対応するフィールドに、データを入れていく
51 .AddNew
52
53 '//サブ関数を呼び出し、エラーが発生しなかった場合のみフィールドに値を代入する
54 If Excelインポート_エラー発生箇所を通知_サブ関数_エラー検知(i, sheetValues(i, 1), "管理番号", errAlertText) Then
55 .Fields("管理番号").Value = sheetValues(i, 1)
56 Else
57 isUpdatable = True
58 End If
59
60 '//管理番号と同様
61 If Excelインポート_エラー発生箇所を通知_サブ関数_エラー検知(i, sheetValues(i, 2), "店舗", errAlertText) Then
62 .Fields("店舗").Value = sheetValues(i, 2)
63 Else
64 isUpdatable = True
65 End If
66
67 '//管理番号と同様
68 If Excelインポート_エラー発生箇所を通知_サブ関数_エラー検知(i, sheetValues(i, 3), "売上金", errAlertText) Then
69 .Fields("売上金").Value = sheetValues(i, 3)
70 Else
71 isUpdatable = True
72 End If
73
74 '//管理番号と同様
75 If Excelインポート_エラー発生箇所を通知_サブ関数_エラー検知(i, sheetValues(i, 4), "調整後売上金", errAlertText) Then
76 .Fields("調整後売上金").Value = sheetValues(i, 4)
77 Else
78 isUpdatable = True
79 End If
80
81 '//エラー発生時は新規行の挿入をキャンセル
82 If isUpdatable Then
83 .Update
84 Else
85 .CancelUpdate
86 End If
87
88 Next i
89
90 End With
91
92 '//変更をコミットする
93 '//エラー発生時はコミットせず、エラーメッセージを表示する
94 If errAlertText = "" Then
95 myWorkspase.CommitTrans
96 isCommit = True
97 Else
98 errAlertText = "下記の行・項目において数式エラーが発生しました。" & vbLf & vbLf & errAlertText
99 MsgBox errAlertText, vbExclamation, "数式エラー"
100 End If
101
102
103Exits:
104
105 '//コミットしていなければロールバックする
106 If Not isCommit Then myWorkspase.Rollback
107
108 rs.Close
109 wb.Close
110 exApp.Quit
111
112 Exit Sub
113
114Err:
115
116 MsgBox Err.Description, vbExclamation, Err.Number
117 GoTo Exits
118
119End Sub
120
121
122'******************************************************************************************
123'*機能 :指定した値のエラーを検知する
124'*引数 :行番号
125'*引数 :調査するセル値
126'*引数 :項目名
127'*引数 :エラー情報格納用テキスト
128'*戻り値 :True > エラーなし、False > エラー発生
129'******************************************************************************************
130Function Excelインポート_エラー発生箇所を通知_サブ関数_エラー検知(ByVal rowNumber As Long, ByVal sheetValue As Variant, _
131ByVal tgtItem As String, ByRef errAlertText As String) As Boolean
132
133 If IsError(sheetValue) Then
134 '//エラー発生時、エラーテキストに情報を追加し、戻り地をFalseとする
135 errAlertText = errAlertText & rowNumber & "行目:" & tgtItem & vbLf
136 Excelインポート_エラー発生箇所を通知_サブ関数_エラー検知 = False
137 Else
138 Excelインポート_エラー発生箇所を通知_サブ関数_エラー検知 = True
139 End If
140
141End Function
142
こちらを実行すると、
数式エラーの発生箇所を記録し、
次のようにエラーメッセージが表示されます。
ただAccess標準のエラーが出るよりも、
こちらのほうがユーザフレンドリーですね。
2. エラー時に代わりに特定の値を入れる
エラー発生時に逐一メッセージを出すよりも、
あらかじめ決められた規則で代打の値を代入したい場合もあるでしょう。
そのような場合のサンプルコードを記しました。
1
2Sub Excelインポート_エラー発生時に代わりの値を代入()
3 Const xlUp = -4162
4 Dim excelPath As String
5 Dim exApp As Object
6 Dim wb As Object
7 Dim sheetValues() As Variant
8 Dim rs As Recordset
9 Dim i As Long
10
11 On Error GoTo Err
12
13 excelPath = Application.CurrentProject.Path & "\" & "数式エラーのある表.xlsx"
14
15 '//Excelアプリを立ち上げる
16 Set exApp = CreateObject("Excel.Application")
17 Set wb = exApp.Workbooks.Open(excelPath, , True)
18 '//売上管理シートを参照する
19 With wb.Worksheets(1)
20 '//売上管理シート上のテーブルデータを、行数のぶんだけ参照し、
21 '//その内容を二次元配列にパースする
22 sheetValues = .Range( _
23 .Cells(2, 1), _
24 .Cells( _
25 .Cells(.Rows.Count, 1).End(xlUp).Row, _
26 4 _
27 ) _
28 ).Value
29 End With
30
31 '//売上管理テーブルのレコードセットを開く
32 Set rs = CurrentDb.OpenRecordset("売上管理テーブル", dbOpenDynaset)
33
34 With rs
35 For i = LBound(sheetValues, 1) To UBound(sheetValues, 1)
36 '//対応するフィールドに、データを入れていく
37 .AddNew
38
39 '//エラー発生時には、代わりに現在のテーブルの最大の管理番号よりも1だけ大きい管理番号を挿入する
40 .Fields("管理番号").Value = IIf(IsError(sheetValues(i, 1)), Nz(DMax("管理番号", "売上管理テーブル"), 1) + 1, sheetValues(i, 1))
41 '//エラー発生時には、代わりに不正な店舗名であることを示す
42 .Fields("店舗").Value = IIf(IsError(sheetValues(i, 2)), "※不正な店舗名です", sheetValues(i, 2))
43 '//エラー発生時には、売上金はゼロとする
44 .Fields("売上金").Value = IIf(IsError(sheetValues(i, 3)), 0, sheetValues(i, 3))
45 '//エラー発生時には、代わりに売上金の90%の金額を設定
46 .Fields("調整後売上金").Value = IIf(IsError(sheetValues(i, 4)), .Fields("売上金").Value * 0.9, sheetValues(i, 4))
47
48 .Update
49 Next i
50
51 End With
52
53Exits:
54
55 rs.Close
56 wb.Close
57 exApp.Quit
58
59 Exit Sub
60
61Err:
62
63 MsgBox Err.Description, vbExclamation, Err.Number
64 GoTo Exits
65
66End Sub
67
68
こちらを実行すると、
調整後売上金として、
数式エラーの行は売上金の90%の金額を設定するようになります。
サンプルファイル
上にも記載しましたが、
説明で使用したファイルについて、
こちらからダウンロードできます。
終わりに
エラー発生時の分岐処理については、
VBAマクロを扱う限り避けては通れない問題となるでしょう。
様々なケースを想定し、
なるべくユーザに優しいマクロを作ることができるようになることが、
マクロの性能向上にとって重要となります。
関連記事
- [教えて!VBA] 第3回 Accessマクロで、テーブルのデータを全クリアするにはどうすればいいの??
- [Access VBA] フォームのコントロール操作系の関数をどのようにユニットテストするかの方法メモ
- [Access VBA] RequeryとRefreshの使い分けデモ 「単票フォームの編集」編
- [Access VBA] 見積書作成ツール(Accessバージョン)を作成した
- [VBA, PowerShell] Accessのモジュール・クラスやクエリのSQLから特定文字列を抽出するためのテクニック