フォルダに毎日・毎月たまっていくCSVを、Accessへ1つずつ手で取り込んでいると、どのファイルを取り込んだのか分からなくなります。同じファイルを2回入れていないかも確かめにくくなります。
この記事では、次の流れをVBAで作ります。VBAの Dir 関数でフォルダ内のCSVを順に見つけ、DoCmd.TransferText で取り込みます。そのうえで、ファイル名と件数を取込ログテーブルに記録します。
1ファイルを取り込むためのインポート定義(列の型や文字コードの指定)は、すでに作ってある前提で進めます。
全体の流れ
- フォルダ内のCSVのファイル名をすべて集める
- ファイルごとに、取込ログに「完了」の記録があるかを確かめる
- まだ取り込んでいなければ、作業用テーブルを空にしてからCSVを取り込む
- 作業用テーブルの件数を数え、ファイル名を付けて本テーブルへ追加する
- ファイル名・更新日時・件数・結果を取込ログに書く
本テーブルへ直接取り込まず、いったん作業用テーブルに入れるのがポイントです。こうするとファイルごとの件数を正確に数えられ、本テーブルの各行がどのファイルから来たかも残せます。
準備するテーブル
作業用テーブル(例:tmp_売上取込)
インポート定義と同じ列構成のテーブルです。毎回、中身を消してから使います。インポート定義を使って一度手動で取り込み、そのときできたテーブルを使うと、列の型がそろいます。
本テーブル(例:売上)
作業用テーブルと同じ列を用意し、さらに 取込ファイル 列(短いテキスト)を追加しておきます。どのCSVから来た行かを記録するための列です。
取込ログテーブル(例:取込ログ)
| フィールド名 | データ型 | 内容 |
|---|---|---|
| ID | オートナンバー | 主キー |
| ファイル名 | 短いテキスト | 取り込んだCSVの名前 |
| ファイル更新日時 | 日付/時刻 | CSVの最終更新日時 |
| ファイルサイズ | 数値(長整数型) | バイト数 |
| 取込件数 | 数値(長整数型) | 作業用テーブルに入った行数 |
| 追加件数 | 数値(長整数型) | 本テーブルに追加された行数 |
| 状態 | 短いテキスト | 完了/エラー |
| メモ | 短いテキスト | エラー内容など |
| 処理日時 | 日付/時刻 | 処理した日時 |
VBAコード
標準モジュールに貼り付け、定数と列名を自分の環境に合わせて書き換えてください。
Option Compare Database
Option Explicit
Private Const FOLDER_PATH As String = "C:\取込\売上CSV\" ' 末尾に \ を付ける
Private Const SPEC_NAME As String = "売上CSV_インポート定義"
Private Const WORK_TABLE As String = "tmp_売上取込"
Private Const LOG_TABLE As String = "取込ログ"
Public Sub ImportCsvFolder()
Dim files As New Collection
Dim fileName As String
Dim f As Variant
Dim processed As Long, skipped As Long
' 1. 先にファイル名をすべて集める
fileName = Dir(FOLDER_PATH & "*.csv")
Do While Len(fileName) > 0
If LCase(Right(fileName, 4)) = ".csv" Then
files.Add fileName
End If
fileName = Dir()
Loop
' 2. 1ファイルずつ取り込む
For Each f In files
If IsImported(CStr(f)) Then
Debug.Print "取込済みのためスキップ: " & f
skipped = skipped + 1
Else
ImportOneFile CStr(f)
processed = processed + 1
End If
Next f
MsgBox "対象 " & files.Count & " 件(処理 " & processed & " 件/スキップ " & skipped & " 件)" & vbCrLf & _
"結果は「" & LOG_TABLE & "」を確認してください。"
End Sub
Private Function IsImported(ByVal fileName As String) As Boolean
IsImported = DCount("*", LOG_TABLE, _
"ファイル名='" & Replace(fileName, "'", "''") & "' AND 状態='完了'") > 0
End Function
Private Sub ImportOneFile(ByVal fileName As String)
Dim db As DAO.Database
Dim fullPath As String
Dim csvRows As Long
Dim appended As Long
On Error GoTo ErrHandler
Set db = CurrentDb
fullPath = FOLDER_PATH & fileName
' 作業用テーブルを空にしてから取り込む
db.Execute "DELETE FROM " & WORK_TABLE, dbFailOnError
DoCmd.TransferText acImportDelim, SPEC_NAME, WORK_TABLE, fullPath, True
csvRows = DCount("*", WORK_TABLE)
' 本テーブルへファイル名付きで追加する(列名は環境に合わせる)
db.Execute "INSERT INTO 売上 (伝票番号, 売上日, 取引先, 金額, 取込ファイル) " & _
"SELECT 伝票番号, 売上日, 取引先, 金額, '" & Replace(fileName, "'", "''") & "' " & _
"FROM " & WORK_TABLE, dbFailOnError
appended = db.RecordsAffected
WriteLog fileName, fullPath, csvRows, appended, "完了", ""
Exit Sub
ErrHandler:
WriteLog fileName, fullPath, csvRows, 0, "エラー", Err.Number & ": " & Err.Description
End Sub
Private Sub WriteLog(ByVal fileName As String, ByVal fullPath As String, _
ByVal csvRows As Long, ByVal appended As Long, _
ByVal status As String, ByVal note As String)
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset(LOG_TABLE, dbOpenDynaset)
rs.AddNew
rs!ファイル名 = fileName
rs!ファイル更新日時 = FileDateTime(fullPath)
rs!ファイルサイズ = FileLen(fullPath)
rs!取込件数 = csvRows
rs!追加件数 = appended
rs!状態 = status
rs!メモ = Left(note, 255)
rs!処理日時 = Now()
rs.Update
rs.Close
End Sub
INSERT INTO に書いた列名(伝票番号、売上日、取引先、金額)は例です。実際の本テーブルの列名に合わせてください。TransferText の最後の引数 True は「1行目が見出し」という意味です。見出し行のないCSVなら False にします。
コードのポイント
ファイル名を先に集めてから処理する
Dir は、引数を付けて呼ぶたびに検索を最初からやり直します。そのため、ループの途中で別の処理が Dir を使うと、続きの検索がずれることがあります。このコードでは、最初にファイル名をすべて Collection に集め、そのあとで1件ずつ取り込んでいます。処理中にフォルダへ新しいファイルが置かれても、その回に処理するのは最初に集めたファイルだけです。
拡張子をもう一度確かめる
Windowsでは、*.csv のようなワイルドカードで検索すると、.csv 以外の似た拡張子のファイルまで見つかることがあります。そこで、LCase(Right(fileName, 4)) = ".csv" で拡張子をもう一度確かめています。
取り込み済みかどうかは「完了」の記録で判定する
取込ログに、同じファイル名で状態が「完了」の行があれば、そのファイルは飛ばします。エラーで終わったファイルには「完了」が付かないので、原因を直して再実行すれば取り込み直せます。
ファイル名に '(シングルクォーテーション)が含まれていると、条件式が壊れてしまいます。そのため、Replace で '' に置き換えています。
件数は作業用テーブルで数える
「取込件数」は、DCount で作業用テーブルの行数を数えた値です。「追加件数」は、INSERT INTO を実行したあとに RecordsAffected で受け取った値です。dbFailOnError を付けているので、追加の途中で失敗すると、その追加全体が取り消されてエラーとして記録されます。
取り込み後に確認すること
1. 取込ログと本テーブルの件数を照合する
本テーブルの 取込ファイル 列を使って、ファイルごとの件数を取込ログと並べます。
SELECT L.ファイル名, L.追加件数, Count(S.取込ファイル) AS 本テーブル件数
FROM 取込ログ AS L LEFT JOIN 売上 AS S ON L.ファイル名 = S.取込ファイル
WHERE L.状態 = '完了'
GROUP BY L.ファイル名, L.追加件数;
「本テーブル件数」が「追加件数」より多いファイルは、二重に取り込まれた可能性があります。逆に少ないファイルは、取り込んだあとに本テーブルから行が削除された可能性があります。
2. エラーになったファイルを確認する
取込ログを「状態」で並べ替え、「エラー」の行のメモを見ます。メモには、インポート定義の名前の間違い、列の不一致、ファイルを開けなかったことなど、エラーの内容が残っています。原因を直してから ImportCsvFolder を再実行すると、「完了」の付いていないファイルだけが取り込まれます。スキップしたファイルの名前は、イミディエイトウィンドウ(Ctrl+G)で確認できます。
3. インポートエラーのテーブルができていないか見る
列の型に合わない値があると、取り込めなかった値の内容が、名前の末尾に _ImportErrors が付いたテーブルに書き出されることがあります。この場合、処理全体はエラーにならず、値が空のまま取り込まれる行が出ることがあります。ループが終わったら、ナビゲーションウィンドウでこのテーブルができていないか確かめてください。できていれば、対象のファイルと行を確認します。
4. 件数がCSVの見た目と合うか
CSVの行数とAccessの件数が合わない原因としては、見出し行の有無の指定、末尾の空行、セル内の改行などがあります。気になるファイルは、テキストエディターで開いて行数をおおまかに確かめるか、そのファイルだけ手動で取り込んで件数を比べます。
運用でつまずきやすい点
- 同じ名前のまま中身が差し替わったファイル:ファイル名だけで判定しているので、修正版は取り込まれません。取込ログに更新日時とサイズを残してあるので、差し替えがあったかは確かめられます。修正版を入れ直す手順も決めておきます。たとえば、本テーブルから
取込ファイルが該当する行を削除し、取込ログの状態を「完了」以外に変えてから再実行します。 - サブフォルダは対象外:
Dirが探すのは、指定したフォルダの直下だけです。月別フォルダなどに分けている場合は、フォルダごとに実行するか、対象フォルダを切り替える仕組みを追加します。 - 書き込み中のファイル:別のシステムがCSVを出力している途中で取り込むと、途中までの行しか入らないことがあります。出力が終わる時刻より後に実行するか、出力が終わってから取込フォルダへ移す運用にします。
- 取り込んだファイルの移動:取込ログで管理する代わりに、取り込んだCSVを
Nameステートメントで「取込済」フォルダへ移す方法もあります。ただし、移動に失敗したときの扱いを決めておかないと、二重取り込みの原因になります。取込ログと組み合わせて使うと安心です。 - バックアップ:初めて実行するときは、本テーブルが入っているデータベースファイルのコピーを取ってから試してください。
