こんにちは!プロジェクト管理の現場で、日々MS Projectと格闘お疲れ様です。
「マクロの記録」ボタンを押してVBAの世界に足を踏み入れたものの、Excelとは違う独特のオブジェクト構造に面食らい、「なんだか動かないぞ…」と頭を抱えていませんか?
大丈夫です。その壁は、すべての優秀なエンジニアが一度は通る道です。今回は、MS Project VBAの基礎を固めつつ、実務で絶対に避けて通れない「SQL Serverへの進捗データ一括書き込み(高速化)」という実践的なテーマを、優しく、かつ本質的に解説していきます。
ここをクリアすれば、あなたのProject VBAスキルは間違いなく「初学者」から「中級エンジニア」へとシフトしますよ。一緒にマスターしていきましょう!
—
1. Project VBAの基礎:オブジェクトモデルの心臓部を知る
Excel VBAとProject VBAの最大の違い、それは「データの持ち方と時間の概念(スケジュールエンジン)」にあります。
Projectのオブジェクトモデルは、以下のような階層構造(ツリー構造)をしています。
Application (アプリ全体)
└── Project (現在開いているプロジェクトファイル)
├── Task (タスク:作業項目)
│ └── Assignment (リソース割り当て)
└── Resource (リソース:人やモノ)
Excelのように「セルを指定してズバッと値を書き換える」という感覚でタスクを操作すると、Projectのスケジュール計算エンジン(CPM:クリティカルパス法など)がその都度働き、処理が猛烈に遅くなります。
特に数千行ある大規模スケジュールで、ループを回しながら1件ずつデータベースに書き込んだり、タスクを更新したりすると……コーヒーを何杯飲んでも終わらない「地獄の低速マクロ」が完成してしまいます。
これを解決するのが、今回学ぶ「ADOを用いた一括処理(トランザクション制御)」です。
—
2. 実務で直面する課題:なぜ一括更新が必要なのか?
進捗報告のたびに、MS ProjectからSQL Serverへタスクの進捗率(`[% 完了]`や`[実績作業時間]`など)を同期させたい――。よくある要件ですね。
ここでやりがちなアンチパターンがこれです:
- タスクを1件ずつループで取得
- その都度、SQL Serverへ接続(`Open`)して `INSERT` / `UPDATE` を実行
- SQL Serverを切断(`Close`)
これではネットワークの往復回数(ラウンドトリップ)が膨大になり、データベース側にも過大な負荷がかかります。
プロのエンジニアなら、「1回の接続で、トランザクションを張って、一気に流し込む(あるいはバッチ処理する)」というアプローチを取ります。
—
3. 実装コード:ADOとトランザクションによる高速書き込み
それでは、実務でそのまま使えるコードを見ていきましょう。
VBE(Visual Basic Editor)の「ツール」>「参照設定」から、「Microsoft ActiveX Data Objects x.x Library」にチェックを入れておいてくださいね。
Sub ExportProgressToSQLServer()
‘ 定数定義(接続文字列は実際の環境に合わせて変更してください)
Const DB_SERVER As String = “Server=myServerAddress;Database=myDatabase;Trusted_Connection=True;”
Dim prj As Project
Dim tsk As Task
Dim conn As Object
Dim cmd As Object
Dim trans As Object
Dim taskCount As Long
Set prj = ActiveProject
taskCount = 0
‘ 1. ADOコネクションの生成と接続
Set conn = CreateObject(“ADODB.Connection”)
conn.ConnectionString = DB_SERVER
conn.CommandTimeout = 30
conn.Open
‘ 2. トランザクションの開始(ここがデータ整合性と高速化のキモ!)
conn.BeginTrans
On Error GoTo ErrorHandler
‘ 3. コマンドオブジェクトの準備(パラメータクエリでSQLインジェクション対策)
Set cmd = CreateObject(“ADODB.Command”)
Set cmd.ActiveConnection = conn
‘ あらかじめSQL文の雛形をセットしておく
cmd.CommandText = “MERGE INTO ProjectProgress AS target ” & _
“USING (SELECT ? AS TaskUID, ? AS TaskName, ? AS PctComplete) AS source ” & _
“ON target.TaskUID = source.TaskUID ” & _
“WHEN MATCHED THEN ” & _
” UPDATE SET TaskName = source.TaskName, PctComplete = source.PctComplete, UpdatedAt = GETDATE() ” & _
“WHEN NOT MATCHED THEN ” & _
” INSERT (TaskUID, TaskName, PctComplete, UpdatedAt) VALUES (source.TaskUID, source.TaskName, source.PctComplete, GETDATE());”
‘ パラメータの型を定義(高速化と型の安全性を担保)
cmd.Parameters.Append cmd.CreateParameter(“TaskUID”, 3, 1, , 0) : Idi_adInteger = 3
cmd.Parameters.Append cmd.CreateParameter(“TaskName”, 200, 1, 255, “”) : adVarChar = 200
cmd.Parameters.Append cmd.CreateParameter(“PctComplete”, 5, 1, , 0) : adDouble = 5
‘ 4. ProjectのタスクをループしてSQLに流し込む
For Each tsk In prj.Tasks
‘ サマリータスク(親タスク)や削除済みタスクを除外する実務的なガード節
If Not tsk Is Nothing Then
If Not tsk.Summary Then
‘ パラメータに値をバインド
cmd.Parameters(0).Value = tsk.UniqueID 注意: IDではなくUniqueIDを使うのが鉄則!
cmd.Parameters(1).Value = tsk.Name
cmd.Parameters(2).Value = tsk.PercentComplete
‘ 実行
cmd.Execute
taskCount = taskCount + 1
End If
End If
Next tsk
‘ 5. コミット(一連の処理を確定)
conn.CommitTrans
‘ 後片付け
conn.Close
Set cmd = Nothing
Set conn = Nothing
MsgBox taskCount & ” 件の進捗データをSQL Serverへ正常に同期しました!”, vbInformation, “完了”
Exit Sub
ErrorHandler:
‘ エラーが発生した場合はロールバック(変更をなかったことにする)
If Not conn is Nothing Then
If conn.State = 1 Then conn.RollbackTrans
End If
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “異常終了”
If Not conn Is Nothing Then
If conn.State = 1 Then conn.Close
End If
Set cmd = Nothing
Set conn = Nothing
End Sub
—
4. コードの深掘り:プロが仕込んだ「3つの技術的ポイント」
上記のコードには、実務でハマりがちなトラブルを防ぐための知見が詰まっています。
① `Task.ID` ではなく `Task.UniqueID` を使え!
Projectには「ID」と「UniqueID」という似たプロパティがあります。
- ID: タスクの並び順によって動的に変わる(行番号のようなもの)。行を移動すると変わる。
- UniqueID: タスクが作成された瞬間に発行され、二度と変わらない固有のID。
データベースの主キー(PrimaryKey)として外部連携させる場合、絶対に `UniqueID` を使ってください。 `ID` を使ってしまうと、タスクの順序を変えた瞬間にDB側のデータがめちゃくちゃになります。
② SQL Serverの `MERGE INTO` 構文の活用
「データが存在すれば更新(Update)、なければ挿入(Insert)」という処理を「UPSERT」と呼びます。SQL Serverの `MERGE` 構文を使うことで、プログラム側で「存在チェックのSELECT文を撃って、分岐して…」という無駄な通信を省き、データベース側で一撃で処理させることができます。これが爆速化の大きな秘訣です。
③ トランザクション制御(`BeginTrans` と `CommitTrans`)
ループの中で1件ごとにDBへ書き込み、途中でエラー(ネットワーク切断やデータの不整合など)が起きた場合、データベースには「途中まで書き込まれた中途半端なデータ」が残ってしまいます。
`BeginTrans` で囲み、最後に `CommitTrans` を呼ぶことで、「すべて成功するか、すべて失敗するか(原子性:Atomicity)」を担保します。エラー時は `RollbackTrans` で完全に巻き戻します。
—
まとめ:ここをクリアすれば、もう怖くない!
今回は、Project VBAのオブジェクトモデルの特性から、ADOを用いたSQL Serverへの高速・安全なデータ書き込み手法まで解説しました。
- Projectのタスクを回すときは `UniqueID` を使う。
- ループの内側で毎回DB接続せず、トランザクションでまとめて処理する。
- エラーハンドリングを必ず組み込み、ロールバックを忘れない。
この3つを押さえておけば、どんなに巨大なプロジェクトファイルの自動化案件が来ても、余裕を持ってスマートに解決できるはずです。
あなたのVBAライフが、より一層快適で生産的なものになりますように。それでは、次の現場でお会いしましょう!
