Accessアプリの終了時に、ローカルのメインDBからネットワーク上のバックアップDBへテーブルを確実にコピー(上書き)したい――その要件はシンプルに見えて、DoCmd.TransferDatabase の引数の取り違えやネットワーク制約、ロックの影響でつまずきがちです。本記事では、実際のパスとテーブル名を使った再現性の高いコード、増殖テーブル対策、エラー3024の切り分け、そして安全な二段階入れ替え方式まで、現場で実戦投入できる手順をまとめて解説します。
前提とゴール
- 元DB(メイン):
C:\Users\Mikemoo\Desktop\PGA Matrix.accdb - バックアップ先DB:
Z:\CSC\MMOORE\ACCESS\Backup\PGA MATRIX 6‑2022 Rep Version.accdb - テーブル名:Main Data
- アプリ終了時(フォームの
Unload/Close、もしくはAutoExecマクロ終了時など)にコピーを実行
本記事の最終目標は、「エラー3024を出さず」「MainData1, MainData2… のような重複テーブルを作らず」「既存テーブルを確実に置き換える」ことです。
症状と原因の整理
| 症状 | 主な原因 | ポイント |
|---|---|---|
| 実行時エラー 3024(ファイルが見つかりません) | TransferDatabase の第3引数にコピー先ではなく誤って元DBを渡している/パスの誤り/ドライブ割り当てがない | 第3引数 DatabaseName はコピー先(エクスポート先)の ファイル を示す |
| MainData1, MainData2… と重複テーブルが増殖 | 先に同名テーブルが存在し、Access が競合回避のため自動リネーム | コピー前に該当テーブルを削除する「事前削除」または「二段階入れ替え」を採用 |
| 「上書きしたはずなのに内容が変わらない」 | バックアップ先DBが開きっぱなしでロック/別ユーザーが使用中/書き込み権限なし | 排他ファイル(.laccdb)や共有状況、権限を確認 |
| たまにだけ失敗する | ネットワーク断・スリープ復帰直後・ローミング環境でZ:が未マウント | UNC パスも選択肢。事前にフォルダ書き込みテストを行う |
TransferDatabase の正しい呼び出し(最小コード)
元DB(現在開いているDB)から、バックアップDBへテーブルをエクスポートする最小コードです。第3引数がコピー先ファイルであることに注意してください。
Option Compare Database
Option Explicit
Public Sub Export_MainData_To_Backup_Min()
Dim strDest As String
strDest = "Z:\CSC\MMOORE\ACCESS\Backup\PGA MATRIX 6-2022 Rep Version.accdb"
' Main Data → 同名 Main Data としてエクスポート
DoCmd.TransferDatabase _
acExport, _ ' エクスポート
"Microsoft Access", _ ' 種別
strDest, _ ' コピー先 .accdb
acTable, _ ' オブジェクト種別
"Main Data", _ ' 元テーブル名
"Main Data" ' 先テーブル名
End Sub
引数の対応関係(早見表)
| 引数 | 指定例 | 意味 | 注意点 |
|---|---|---|---|
| TransferType | acExport | 「現在のDB」→「外部DBへ」コピー | インポートの場合は acImport |
| DatabaseType | "Microsoft Access" | コピー先/元のDB種別 | Access 間コピーならこれでOK |
| DatabaseName | バックアップ先の .accdb パス | エクスポート時はコピー先ファイル | ここを取り違えると 3024 の温床 |
| ObjectType | acTable | テーブルを対象 | クエリやフォームも可 |
| Source | "Main Data" | 現在DBの元テーブル名 | スペースはそのままOK |
| Destination | "Main Data" | 先DBに作成(上書き)する名称 | 既存有りだと自動リネームされる場合あり |
| StructureOnly | False(既定) | データ含む/含まない | True だと「定義のみ」 |
「テーブルが増える」問題を止める確実策:事前削除→コピー
既存の Main Data がバックアップDBに残っていると、Access が衝突回避のために Main Data1 などを作成してしまうことがあります。コピー前に対象テーブルを明示的に削除(存在しなければ無視)してからエクスポートすれば、増殖は止まります。
Public Sub Backup_MainData_Overwrite()
Dim dbs As DAO.Database
Dim strDest As String
strDest = "Z:\CSC\MMOORE\ACCESS\Backup\PGA MATRIX 6-2022 Rep Version.accdb"
' 先DBを開いて事前削除
Set dbs = OpenDatabase(strDest)
On Error Resume Next
dbs.Execute "DROP TABLE [Main Data]" ' 無ければスルー
On Error GoTo 0
dbs.Close
' 改めて転送
DoCmd.TransferDatabase acExport, "Microsoft Access", strDest, acTable, "Main Data", "Main Data"
End Sub
ポイント:
DROP TABLEは存在しない時にエラーが出るため、On Error Resume Nextでいったん握りつぶしてOK。直後にOn Error GoTo 0で戻すのを忘れないこと。- リレーションや外部キーで依存している場合は削除に失敗します。バックアップ専用DBでは、バックアップ対象テーブルにリレーションを張らない設計が無難です。
さらに堅牢に:安全な「二段階入れ替え」パターン
ネットワーク断や予期せぬ中断でコピーが中途半端に終わると、先テーブルが消えたまま…という事態を避けたいときは、テンポラリ名で転送→検証→名前入れ替えの順で行うと安全です。
Public Sub Backup_MainData_SafeSwap()
Const SRC As String = "Main Data"
Const DEST As String = "Main Data"
Dim destPath As String
destPath = "Z:\CSC\MMOORE\ACCESS\Backup\PGA MATRIX 6-2022 Rep Version.accdb"
Dim tmpName As String
tmpName = DEST & "_tmp_" & Format(Now, "yyyymmdd_hhnnss")
' 1) 一時名で転送
DoCmd.TransferDatabase acExport, "Microsoft Access", destPath, acTable, SRC, tmpName
' 2) レコード件数チェック(任意)
Dim cntSrc As Long, cntTmp As Long
cntSrc = DCount("*", "[" & SRC & "]")
cntTmp = CountTableRecordsInExternal(destPath, tmpName)
If cntSrc <> cntTmp Then
Err.Raise vbObjectError + 5000, , "件数不一致: src=" & cntSrc & ", tmp=" & cntTmp
End If
' 3) 先DBで入れ替え(旧を削除→tmp を正式名にリネーム)
Dim dbs As DAO.Database
Set dbs = OpenDatabase(destPath)
On Error Resume Next
dbs.TableDefs.Delete DEST
On Error GoTo 0
dbs.TableDefs(tmpName).Name = DEST
dbs.Close
End Sub
Private Function CountTableRecordsInExternal(ByVal dbPath As String, ByVal tbl As String) As Long
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = OpenDatabase(dbPath)
Set rs = db.OpenRecordset("SELECT COUNT(*) AS Cnt FROM [" & tbl & "]")
CountTableRecordsInExternal = rs!Cnt
rs.Close: db.Close
End Function
この方式なら、コピー途中で失敗しても既存の正式テーブルは残るため、復旧が容易です。
パス/権限/ロックの切り分け手順(エラー3024対策)
動作確認は「ローカル→ローカル」→「ローカル→ネットワーク」→「ネットワーク→ネットワーク」の順で段階的に進めます。以下のチェック関数を先に走らせ、エラーを潰すと調査が早く終わります。
Public Function CanWriteFolder(ByVal folderPath As String) As Boolean
On Error GoTo EH
Dim f As Integer, testFile As String
If Right$(folderPath, 1) = "\" Then
testFile = folderPath & "__writetest.tmp"
Else
testFile = folderPath & "\__writetest.tmp"
End If
f = FreeFile
Open testFile For Output As #f
Print #f, "test"
Close #f
Kill testFile
CanWriteFolder = True
Exit Function
EH:
CanWriteFolder = False
End Function
- ドライブ割当(Z:)が無い:サービスやタスクスケジューラ経由だと、ユーザーセッションと異なるためZ:が未割り当てのケースがあります。可能なら UNC(例:
\\Server\Share\CSC\MMOORE\ACCESS\Backup\...)を使用。 - フォルダ権限:読み取りだけでなく書き込み/削除権限が必要。上記の
CanWriteFolderで判定。 - ロック:バックアップ先DBが開きっぱなしだと
.laccdbが生成され、書き込みに失敗することがあります。コピー前に閉じる運用に。
スペース・全角半角・大文字小文字の差異に注意
テーブル名やファイル名にスペースが含まれていても TransferDatabase は問題なく扱えますが、SQL の DROP TABLE、DCount などで参照するときは必ず角括弧 [ ] で囲みます。また、見た目が同じでも全角空白や末尾空白が混ざると別名とみなされます。以下のユーティリティで目視しづらい空白を検出できます。
Public Function ShowCodePoints(ByVal s As String) As String
Dim i As Long, ch As String, out As String
For i = 1 To Len(s)
ch = Mid$(s, i, 1)
out = out & Right$("0000" & Hex(AscW(ch)), 4) & " "
Next
ShowCodePoints = Trim$(out)
End Function
終了時に自動でバックアップする実装例(イベント連動)
メインフォームの Form_Unload などにフックして、アプリ終了時にバックアップを走らせます。失敗時のログ蓄積も併せて仕込んでおくと保守が楽です。
Private Sub Form_Unload(Cancel As Integer)
On Error GoTo EH
Backup_MainData_SafeSwap
AppendLog "Backup OK"
Exit Sub
EH:
AppendLog "Backup NG: " & Err.Number & " - " & Err.Description
End Sub
Private Sub AppendLog(ByVal msg As String)
Dim f As Integer
f = FreeFile
Open Environ$("TEMP") & "\PGA_Matrix_Backup.log" For Append As #f
Print #f, Format$(Now, "yyyy-mm-dd hh:nn:ss") & " " & msg
Close #f
End Sub
ファイル全体のバックアップも選択肢
「テーブル単体の同期」ではなく「DBファイル全体を退避」したいだけなら、FileCopy が最もシンプルです。アプリ終了直前に下記を呼ぶだけで済みます。
Public Sub Backup_FileCopy_All()
Dim src As String, dest As String
src = "C:\Users\Mikemoo\Desktop\PGA Matrix.accdb"
dest = "Z:\CSC\MMOORE\ACCESS\Backup\PGA MATRIX 6-2022 Rep Version.accdb"
' 既存バックアップを時刻付きで退避してから上書きしたい場合
Dim bak As String
bak = dest & "." & Format$(Now, "yyyymmdd_hhnnss") & ".bak"
On Error Resume Next
Name dest As bak ' 失敗しても続行
On Error GoTo 0
FileCopy src, dest
End Sub
利点:シンプル・高速・テーブル依存なし。欠点:サイズが大きい場合の転送コスト、他のユーザーが先DBを開いていると失敗しやすい。
よくあるエラーコードと対処
| エラー | 主な原因 | 対処 |
|---|---|---|
| 3024: ファイルが見つかりません | 第3引数取り違え/パス誤記/Z:未マウント | 第3引数は先DBのフルパス。UNCで再試行 |
| 3044: パスが無効です | 存在しないフォルダ/権限不足 | 事前にフォルダを作成/CanWriteFolder で検証 |
| 3045: ファイルは別のユーザーが使用中 | 先DBが開いている/.laccdb 残留 | 先DBを閉じる/残留ロックを削除(全員退出後) |
| 3211: テーブルをロックできません | 他プロセスが該当テーブルを使用中 | 時間をずらす/依存オブジェクトを解除 |
| 3033: 権限が足りません | 共有フォルダのACL設定 | 書き込み・削除・リネーム権限を付与 |
実用ユーティリティ:共通化して使い回す
プロジェクトで再利用できるよう、パス検証、事前削除、転送、入れ替え、ログをひとまとめにしたラッパーを用意しておくと便利です。
Public Sub ExportTableWithOverwrite( _
ByVal destAccdb As String, _
ByVal srcTable As String, _
Optional ByVal destTable As String = vbNullString, _
Optional ByVal safeSwap As Boolean = True)
If destTable = vbNullString Then destTable = srcTable
' 先DBの存在チェック
If Dir$(destAccdb, vbNormal) = vbNullString Then
Err.Raise 3024, , "先DBが見つかりません: " & destAccdb
End If
If safeSwap Then
Dim tmpName As String
tmpName = destTable & "_tmp_" & Format$(Now, "yyyymmdd_hhnnss")
DoCmd.TransferDatabase acExport, "Microsoft Access", destAccdb, acTable, srcTable, tmpName
' 簡易検証(件数一致)
Dim cntSrc As Long, cntTmp As Long
cntSrc = DCount("*", "[" & srcTable & "]")
cntTmp = CountTableRecordsInExternal(destAccdb, tmpName)
If cntSrc <> cntTmp Then
' 失敗時はテンポラリを後始末
Dim dbs As DAO.Database
Set dbs = OpenDatabase(destAccdb)
On Error Resume Next
dbs.TableDefs.Delete tmpName
On Error GoTo 0
dbs.Close
Err.Raise vbObjectError + 5001, , "検証失敗(件数不一致)"
End If
' 入れ替え
Dim dbs2 As DAO.Database
Set dbs2 = OpenDatabase(destAccdb)
On Error Resume Next
dbs2.TableDefs.Delete destTable
On Error GoTo 0
dbs2.TableDefs(tmpName).Name = destTable
dbs2.Close
Else
' 事前削除→一発転送
Dim dbs3 As DAO.Database
Set dbs3 = OpenDatabase(destAccdb)
On Error Resume Next
dbs3.TableDefs.Delete destTable
On Error GoTo 0
dbs3.Close
DoCmd.TransferDatabase acExport, "Microsoft Access", destAccdb, acTable, srcTable, destTable
End If
End Sub
運用のヒント(落とし穴を避けるコツ)
- 文字列は変数に入れてから渡す:長いフルパスに空白が混じっても、変数経由ならタイプミスが減ります。
- バックアップDBを固定:世代管理が必要なら「テーブル名に日付列を持つ」方針も検討。毎回別DBを作らないとロックや肥大化が減ります。
- コンパクト最適化:世代管理しないなら、バックアップDB側に定期的な Compact を実施。肥大化抑制に有効。
- 参照設定:Microsoft Office XX.0 Access database engine Object Library にチェック(DAO早期バインディングを使う場合)。
- タスクスケジューラ実行:ユーザーコンテキストが変わるため Z: が無いことが多い。UNC に変更するか、起動時にマップドライブを作成する。
検証チェックリスト
- ローカル→ローカル(例:
C:\Temp\dest.accdb)で成功するか - フォルダ書き込みテスト(
CanWriteFolder)が通るか - バックアップ先DBが開いていないか(
.laccdbの有無) - 事前削除または二段階入れ替えで重複テーブルが消えるか
- 件数検証で元と同数か(差分があれば制約・既定値・拒否された行を調査)
- エラー時にログが残るか(復旧時の手がかり)
フルサンプル:終了時バックアップを丸ごと組み込む
以下を標準モジュールに置き、終了時イベントから RunBackup を呼び出します。実運用レベルの詳細ログ、二段階入れ替え、エラー捕捉を一式含みます。
Option Compare Database
Option Explicit
Public Sub RunBackup()
Dim dest As String
dest = "Z:\CSC\MMOORE\ACCESS\Backup\PGA MATRIX 6-2022 Rep Version.accdb"
On Error GoTo EH
LogMsg "Backup start"
If Dir$(dest, vbNormal) = vbNullString Then
Err.Raise 3024, , "先DBが見つかりません: " & dest
End If
If Not CanWriteFolder(GetFolderOfPath(dest)) Then
Err.Raise vbObjectError + 5100, , "書き込み不可: " & GetFolderOfPath(dest)
End If
ExportTableWithOverwrite dest, "Main Data", "Main Data", True
LogMsg "Backup done"
Exit Sub
EH:
LogMsg "Backup failed: " & Err.Number & " - " & Err.Description
End Sub
Private Function GetFolderOfPath(ByVal p As String) As String
Dim i As Long: i = InStrRev(p, "")
If i > 0 Then GetFolderOfPath = Left$(p, i - 1)
End Function
Private Sub LogMsg(ByVal msg As String)
Dim f As Integer
f = FreeFile
Open Environ$("TEMP") & "\PGA_Matrix_Backup.log" For Append As #f
Print #f, Format$(Now, "yyyy-mm-dd hh:nn:ss"), msg
Close #f
End Sub
まとめ:これで「確実に」「静かに」バックアップできる
- 第3引数はコピー先ファイル:エクスポート時はここを間違えない。
- 重複テーブル対策:事前削除、または二段階入れ替え(テンポラリ→検証→リネーム)。
- ネットワークは切り分け優先:ローカルで成功→UNC で再検証。Z: の存在と書き込み権限をチェック。
- ログ常設:成功/失敗を記録し、あとから追跡できるように。
上記の手順とサンプルをそのまま組み込めば、アプリ終了時に Main Data を確実にバックアップDBへコピーし、エラー3024や重複テーブルの悩みから解放されます。まずはローカルで動作を固め、ネットワークに段階展開するのが成功の近道です。

コメント