フォルダに毎日・毎月たまっていくCSVを、Accessへ1つずつ手で取り込んでいると、どのファイルを取り込んだのか分からなくなります。同じファイルを2回入れていないかも確かめにくくなります。

この記事では、次の流れをVBAで作ります。VBAの Dir 関数でフォルダ内のCSVを順に見つけ、DoCmd.TransferText で取り込みます。そのうえで、ファイル名と件数を取込ログテーブルに記録します。

1ファイルを取り込むためのインポート定義(列の型や文字コードの指定)は、すでに作ってある前提で進めます。

全体の流れ

  1. フォルダ内のCSVのファイル名をすべて集める
  2. ファイルごとに、取込ログに「完了」の記録があるかを確かめる
  3. まだ取り込んでいなければ、作業用テーブルを空にしてからCSVを取り込む
  4. 作業用テーブルの件数を数え、ファイル名を付けて本テーブルへ追加する
  5. ファイル名・更新日時・件数・結果を取込ログに書く

本テーブルへ直接取り込まず、いったん作業用テーブルに入れるのがポイントです。こうするとファイルごとの件数を正確に数えられ、本テーブルの各行がどのファイルから来たかも残せます。

準備するテーブル

作業用テーブル(例: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 ステートメントで「取込済」フォルダへ移す方法もあります。ただし、移動に失敗したときの扱いを決めておかないと、二重取り込みの原因になります。取込ログと組み合わせて使うと安心です。
  • バックアップ:初めて実行するときは、本テーブルが入っているデータベースファイルのコピーを取ってから試してください。
おすすめの記事