【上級者向け】外部SQL ServerからWBS階層と依存関係データを取得し、Projectへ同期するデータ連携パイプライン
Microsoft Project(MSP)の真価は、単なるガントチャートの描画ツールではなく、CPM(クリティカルパス法)に基づく厳密なスケジュール・エンジンにある。しかし、エンタープライズ環境において、ERPや基幹システムで管理されているWBSマスターやリソース配分、先行・後続タスクの依存関係を、手動でMSPへ再入力し続けるなどというのは、エンジニアリングの敗北に他ならない。
今回は、外部SQL Serverに格納された階層化タスク群および依存関係定義を、ADO(ActiveX Data Objects)を用いて高速に抽出し、MS Projectのオブジェクトモデルへ一気通貫で流し込む、エンタープライズ向けのデータ連携パイプラインの構築手法を解説する。
中途半端なラッパーや、GUIのイベント駆動に引きずられた遅延処理はすべて排除する。メモリのライフサイクル、COMオブジェクトの適切な解放、そしてProjectの再計算エンジン(Calculation)の制御を通じた「真の高速同期」の極意を授けよう。
—
1. アーキテクチャの要諦:なぜ「一括同期」でなければならないのか
Project VBAにおける最大のパフォーマンス・キラーは、タスクを追加するたびに発生する「スケジュール再計算(Calculation Engineの稼働)」である。数千行に及ぶWBSを1行ずつ `Tasks.Add` し、その都度依存関係を張ろうものなら、WindowsのUIスレッドは完全にブロックされ、数万行のデータであれば数十分の時間を要する。
これを解決するための鉄則は以下の3点だ。
1. 画面描画と自動計算の完全停止 (`ScreenUpdating = False`, `Calculation = manual`)
2. SQL Serverからのデータ一括取得 (ADOR.Recordsetによるメモリ上での高速カーソル走査)
3. フラットな追加と、後からの依存関係(Predecessors)の一括解決
—
2. データベース側の前提(スキーマ設計)
SQL Server側では、最低限以下のリレーションを持つテーブルが存在していると仮定する。
- `T_WBS_Master`
- `TaskUID` (INT, PK): 外部システム側の固有ID
- `TaskName` (NVARCHAR): タスク名
- `OutlineLevel` (INT): 階層レベル(1, 2, 3…)
- `Duration` (INT): 所要時間(分単位など)
- `StartConstraint` (DATETIME): 制約日
- `T_WBS_Dependencies`
- `SuccessorUID` (INT): 後続タスクのUID
- `PredecessorUID` (INT): 先行タスクのUID
- `DependencyType` (INT): 依存関係タイプ (0=FF, 1=FS, 2=SF, 3=SS)
—
3. 実装コード:エンタープライズ・同期パイプライン
以下のコードは、ProjectのVBAエディタ(ThisProjectまたは標準モジュール)に実装する、エラーハンドリングとオブジェクトの明示的解放を完結させた実用コードである。
Option Explicit
‘ ==============================================================================
‘ 外部SQL ServerからWBSおよび依存関係を一括取得し、MS Projectへ同期する
‘ ==============================================================================
Public Sub SynchronizeWBSFromSQLServer()
Dim conn As Object
Dim rsTask As Object
Dim rsDep As Object
Dim connString As String
Dim sqlTask As String
Dim sqlDep As String
Dim tsk As Task
Dim targetProject As Project
Set targetProject = ActiveProject
‘ 接続文字列の定義(環境に合わせて変更のこと)
connString = “Provider=SQLOLEDB;Server=192.168.1.100;Database=EnterprisePM;Uid=sa;Pwd=YourStrongPassword;”
‘ 1. パフォーマンス最適化のための環境退避と設定
On Error GoTo ErrorHandler
Application.ScreenUpdating = False
targetProject.Calculation = pjCalculationManual
‘ 2. ADOコネクションの確立
Set conn = CreateObject(“ADODB.Connection”)
conn.CommandTimeout = 60
conn.Open connString
‘ 3. 既存タスクのクリア(必要に応じてスナップショットと比較・差分更新に変更可能)
‘ ※本番環境ではUIDベースのUPSERTロジックを推奨するが、今回は全置換モデルで解説
Dim i As Long
For i = targetProject.Tasks.Count To 1 Step -1
If Not targetProject.Tasks(i) Is Nothing Then
targetProject.Tasks(i).Delete
End If
Next i
‘ 4. タスクマスターの取得と展開
sqlTask = “SELECT TaskUID, TaskName, OutlineLevel, Duration FROM T_WBS_Master ORDER BY ExecutionOrder ASC;”
Set rsTask = CreateObject(“ADODB.Recordset”)
rsTask.Open sqlTask, conn, 0, 1 ‘ adOpenForwardOnly, adLockReadOnly
Dim dictUIDMap As Object
Set dictUIDMap = CreateObject(“Scripting.Dictionary”) ‘ SQL側UIDとProject内Taskオブジェクトの紐付け
Do While Not rsTask.EOF
Dim taskName As String
Dim durationMins As Long
Dim outlineLevel As Long
Dim sourceUID As Long
sourceUID = rsTask.Fields(“TaskUID”).Value
taskName = rsTask.Fields(“TaskName”).Value
outlineLevel = rsTask.Fields(“OutlineLevel”).Value
durationMins = rsTask.Fields(“Duration”).Value
‘ タスクの追加
Set tsk = targetProject.Tasks.Add(taskName)
tsk.Duration = durationMins & “m” ‘ 分単位で設定
‘ 階層(アウトライン)の調整
‘ ※Projectの仕様上、追加時はフラットになるため、レベルに応じたインデント操作が必要
Do While tsk.OutlineLevel < outlineLevel
tsk.OutlineIndent
Loop
Do While tsk.OutlineLevel > outlineLevel
tsk.OutlineOutdent
Loop
‘ 辞書にマッピングを保存(後続の依存関係設定で使用)
dictUIDMap.Add sourceUID, tsk.ID
rsTask.MoveNext
Loop
rsTask.Close
‘ 5. 依存関係(Predecessors)の構築
sqlDep = “SELECT SuccessorUID, PredecessorUID, DependencyType FROM T_WBS_Dependencies;”
Set rsDep = CreateObject(“ADODB.Recordset”)
rsDep.Open sqlDep, conn, 0, 1
Do While Not rsDep.EOF
Dim succSourceUID As Long
Dim predSourceUID As Long
Dim depType As Long
succSourceUID = rsDep.Fields(“SuccessorUID”).Value
predSourceUID = rsDep.Fields(“PredecessorUID”).Value
depType = rsDep.Fields(“DependencyType”).Value
‘ 辞書からProject内のタスクIDを引く
If dictUIDMap.Exists(succSourceUID) And dictUIDMap.Exists(predSourceUID) Then
Dim succTaskID As Long
Dim predTaskID As Long
succTaskID = dictUIDMap(succSourceUID)
predTaskID = dictUIDMap(predSourceUID)
Dim predTaskObj As Task
Set predTaskObj = targetProject.Tasks(succTaskID)
‘ 先行タスクのリンク追加 (例: 3号タスクに1号タスクをFS関係で紐付ける)
‘ LinkPredecessorsの引数: [Task], [Type], [Lag]
predTaskObj.LinkPredecessors Task:=targetProject.Tasks(predTaskID), Type:=depType
End If
rsDep.MoveNext
Loop
rsDep.Close
‘ 6. 終了処理と再計算の実行
GoTo CleanUp
ErrorHandler:
MsgBox “同期プロセスで致命的なエラーが発生しました: ” & Err.Description, vbCritical, “Enterprise Pipeline Error”
CleanUp:
‘ オブジェクトの明示的解放(メモリリークの防止)
On Error Resume Next
If Not rsDep Is Nothing Then If rsDep.State = 1 Then rsDep.Close: Set rsDep = Nothing
If Not rsTask Is Nothing Then If rsTask.State = 1 Then rsTask.Close: Set rsTask = Nothing
If Not conn Is Nothing Then If conn.State = 1 Then conn.Close: Set conn = Nothing
Set dictUIDMap = Nothing
‘ 環境の復元
targetProject.Calculation = pjCalculationAutomatic
Application.ScreenUpdating = True
‘ 手動で強制再計算
targetProject.Calculate
If Err.Number = 0 Then
MsgBox “SQL ServerからのWBS同期が正常に完了しました。”, vbInformation, “同期成功”
End If
End Sub
—
4. チーフアーキテクトが指摘する「死角」と最適化の極意
このコードは実用十分なパフォーマンスを発揮するが、極限のエンタープライズ環境(数万タスク規模)を運用する上では、以下のアーキテクチャ上の特性を理解しておく必要がある。
① オブジェクトの明示的解放(Comオブジェクトの参照カウント)
VBAにおける `CreateObject(“ADODB.Connection”)` や `Recordset` は、ガベージコレクションのタイミングが曖昧である。特にMS Projectのプロセス内でこれらを大量に生成・破棄すると、COMコンポーネントのメモリリークを引き起こし、最終的にProject自体がクラッシュするか、OLEオートメーションエラーを誘発する。
必ず `CleanUp` ラベルを設置し、`State` を確認した上で `Close` し、変数に `Nothing` を代入して参照カウントを即時ゼロに落とすこと。
② アウトラインレベル(WBS階層)の操作コスト
MS Projectにおいて、タスクを `Tasks.Add` した瞬間、そのタスクはルートレベル(レベル1)として追加される。SQL Serverから取得した `OutlineLevel` に合致させるために、`OutlineIndent` や `OutlineOutdent` をループで呼び出しているが、これにはDOM操作のオーバーヘッドが伴う。
もしパフォーマンスがボトルネックになる場合は、タスクを一度すべてフラットに追加し終えた後、逆順(ボトムアップ)でインデント構造を構築するか、あらかじめMSP側で定義されたテンプレートをベースに差分(UPSERT)更新するアーキテクチャへ昇華させるべきである。
③ トランザクションと整合性
今回のコードは「全削除・全挿入」のモデルを採用しているが、稼働中のプロジェクトに対してこれを適用すると、すでに入力されていた「実績工数(Actual Work)」や「リソースアサイン情報(Assignment)」が吹き飛ぶ。
真のエンタープライズ連携では、SQL Server側の `TaskUID` を MSPの `Text1` や `UniqueID` (※読み取り専用のためカスタムテキストフィールドを推奨)に保持させ、「新規追加」「既存更新」「削除対象外」を判定するUPSERTエンジンをVBA内に実装することがシニアエンジニアに求められる要件となる。
—
結論
VBAは、単なる「マクロ記録の延長」ではない。WindowsのCOMアーキテクチャと外部RDBMSを直結させ、エンタープライズの基幹システムをも制御下におくことのできる極めて強力なシステム開発プラットフォームである。
メモリのライフサイクルを支配し、オブジェクトモデルの挙動を完全に掌握した者だけが、レガシーとモダンを高次元で融合させた堅牢なシステムを構築できる。このパイプラインをあなたの現場へ導入し、手動入力の悪夢からプロジェクトマネージャーたちを解放せよ。
