【極限の知見】SQL ServerとMS Projectを同期せよ:カスタムフィールド自動マッピングの設計思想
「マクロの記録」でボタン一つ動かせるようになった。次のステップとして、ExcelのデータをコピペしてProjectに貼り付ける作業を自動化したい……。
もしあなたが今、そんな壁にぶつかっているとしたら、おめでとうございます。あなたは今、「単なる作業の自動化」から「システムとしての統合」へと足を踏み入れようとしています。
こんにちは。今日は、プロジェクト管理の現場で最も求められ、かつ最も実装が難しいテーマの一つである「外部データベース(SQL Server)とMS Projectのカスタムフィールドの自動同期」についてお話ししましょう。
単にコードを書くだけではありません。オブジェクトの寿命、メモリの挙動、そして「データの一貫性」という、プロフェッショナルが絶対に妥協しない領域について、深く、優しく解説していきます。
1. なぜ「コピペ」ではいけないのか?:設計の思想
多くの初学者が陥る罠は、「Excelにデータがあるから、それをProjectに転記するVBAを書こう」という発想です。しかし、エンタープライズの現場では、マスターデータはSQL Serverのような堅牢なデータベースで管理されます。
ここで重要なのは、「MS Projectを単なる表示ツール(ビューアー)として扱い、データの実体はSQL Serverに置く」という設計思想です。
マッピングの鍵は「UniqueID」にあり
Projectのタスク名(Name)は、ユーザーが容易に変更してしまいます。名前をキーにしてデータを紐付けようとすると、名前が変わった瞬間に同期は崩壊します。
私たちが使うべき「黄金の鍵」は、Projectが内部で持つ `UniqueID` です。これはプロジェクト内で不変の整数値です。SQL Server側のテーブルにも、この `Project_Task_UID` というカラムを持たせておくこと。これが、自動マッピングを成功させる絶対条件です。
2. アーキテクチャの全体像
データの流れをイメージしてみましょう。
1. SQL Server: 正解のデータ(コスト、進捗率、外部システムIDなど)を保持。
2. VBA Connector: ADO(ActiveX Data Objects)を使用して、SQL Serverへクエリを投げる。
3. Mapping Engine: 取得したレコードを、Projectの `UniqueID` を使って各タスクへ流し込む。
4. Custom Fields: Project内の `Text1` や `Number1` といったカスタムフィールドに書き込む。
3. 実践:同期コネクタの実装
それでは、実際に動くコードを見ていきましょう。ここでは、SQL Serverからタスクの「予算額(Number1)」を取得し、Projectのタスクに反映させるシナリオを想定します。
事前準備
VBAエディタの [ツール] -> [参照設定] から、「Microsoft ActiveX Data Objects x.x Library」にチェックを入れておいてください。
Option Explicit
''' <summary>
''' SQL Serverからデータを取得し、Projectのカスタムフィールドにマッピングする
''' </summary>
Sub SyncProjectDataFromSQL()
' --- 1. オブジェクトの宣言 ---
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim t As Task
Dim sql As String
Dim connectionString As String
Dim syncCount As Long
Dim errorCount As Long
' --- 2. 設定値(実際は設定ファイルや変数から取得するのが理想) ---
' SQL Serverへの接続文字列(環境に合わせて書き換えてください)
connectionString = "Provider=SQLOLEDB;Data Source=YOUR_SERVER_NAME;Initial Catalog=YOUR_DB_NAME;Integrated Security=SSPI;"
' 取得するSQL文(UniqueIDをキーにするのが鉄則!)
sql = "SELECT TaskUID, BudgetAmount FROM ProjectTaskMaster"
' エラーハンドリングの開始
On Error GoTo ErrorHandler
' 処理の高速化:画面更新を停止(Projectでは効果が限定的な場合もありますが、作法として)
' ※Project VBAにはExcelのScreenUpdatingのような直接のプロパティがないため、
' 描画負荷を下げるために計算モードの制御などを意識します。
Debug.Print "--- 同期処理開始 ---"
' --- 3. データベース接続 ---
Set conn = New ADODB.Connection
conn.Open connectionString
Set rs = New ADODB.Recordset
rs.Open sql, conn, adOpenStatic, adLockReadOnly
' --- 4. マッピング・エンジンの実行 ---
' Project内の全タスクをループするのではなく、
' 効率を考えるなら「SQLから来たデータ」を主軸にするか、
' 「Dictionaryオブジェクト」を使って高速に照合するのがプロの技です。
' ここでは分かりやすさのため、Projectのタスクをループする基本形を示します。
syncCount = 0
errorCount = 0
For Each t In ActiveProject.Tasks
' 空のタスク(サマリータスク以外の空行など)をスキップ
If Not t Is Nothing Then
' レコードセットを走査して、現在のタスクのUniqueIDに一致するデータを探す
' ※大規模データの場合は、ここでrs.Filterを使うか、Dictionaryを使うと劇的に速くなります。
rs.Filter = "TaskUID = " & t.UniqueID
If Not rs.EOF Then
' --- 5. カスタムフィールドへの書き込み ---
' Number1 を「予算額」として使用する例
t.Number1 = rs.Fields("BudgetAmount").Value
' 同期完了の証としてText1に日付を入れる(デバッグ・監査用)
t.Text1 = "Synced: " & Now()
syncCount = syncCount + 1
End If
' フィルタをリセットして次のタスクへ
rs.Filter = ""
End If
Next t
' --- 6. 後処理(クリーンアップ) ---
' オブジェクトのライフサイクルを管理することは、メモリリークを防ぐために極めて重要です。
rs.Close
conn.Close
Set rs = Nothing
Set conn = Nothing
MsgBox "同期が完了しました。" & vbCrLf & _
"更新件数: " & syncCount & " 件" & vbCrLf & _
"エラー件数: " & errorCount & " 件", vbInformation, "完了"
Exit Sub
ErrorHandler:
' エラーが発生した場合は、必ず接続を閉じる処理へ飛ばす
MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
"エラー内容: " & Err.Description, vbCritical, "エラー"
' 接続が生きている場合のみ閉じる(トランザクション的な安全性)
If Not rs Is Nothing Then
If rs.State = adStateOpen Then rs.Close
End If
If Not conn Is Nothing Then
If conn.State = adStateOpen Then conn.Close
End If
Set rs = Nothing
Set conn = Nothing
End Sub
4. 伝説のエンジニアが教える「陥りやすい罠」
コードが書けた。でも、これだけで満足してはいけません。現場で通用するコードにするために、以下の3点を心に刻んでください。
① オブジェクトの「解放」を忘れない
VBAの世界では、`Set obj = Nothing` を忘れると、プログラムが終わってもメモリの中にオブジェクトが居座り続けることがあります。特にデータベース接続(`Connection`)は、開きっぱなしにするとSQL Server側で「接続上限」に達してしまい、他のユーザーがシステムを使えなくなるという大惨事を引き起こします。「開けたら、必ず閉じる」。これは鉄則です。
② パフォーマンスの壁($O(n^2)$ の恐怖)
上記のサンプルコードでは、`For Each t In ActiveProject.Tasks` の中で `rs.Filter` を使っています。これはタスク数が数百件なら問題ありませんが、数千、数万件になると、驚くほど動作が重くなります。
プロの解決策:
SQLから取得したデータを、一度 `Scripting.Dictionary` オブジェクトに格納してください。
`Dictionary(UniqueID) = BudgetAmount` という形にしておけば、タスクのループ内では `Dictionary(t.UniqueID)` を呼び出すだけで済みます。計算量は $O(n)$ になり、爆速になります。
③ 「トランザクション」の考え方
もし、100件の同期のうち、50件目でネットワークが切れたらどうなるでしょうか?
半分だけデータが書き換わった「中途半端な状態」は、プロジェクト管理において最も危険です。
理想的には、SQL Server側で一括更新を行うか、VBA側で「全件成功するか、さもなくば全て元に戻す(ロールバック)」という設計を検討してください。Project VBA単体では完全なロールバックは難しいですが、「同期実行前にバックアップを取る」、あるいは「同期ステータス用のフィールドを作り、完了したものだけをマークする」といった運用回避策が、実務では非常に重要になります。
最後に
「マクロの記録」は、いわば「言葉を真似る」段階です。
今回学んだ「データベースとの連携」は、「自分の意志でシステムを操る」段階への入り口です。
最初はエラーが出て当たり前です。接続文字列一つ、型一つでプログラムは止まります。しかし、そのエラーの背後にある「なぜ動かないのか?」という理屈を追求してください。オブジェクトのライフサイクルやデータの整合性を意識し始めたとき、あなたはもう、単なるユーザーではなく、エンジニアなのです。
ここをクリアすれば、Project VBAの基本はバッチリですよ。自信を持って、次の実装へ進んでください。応援しています!
