Project VBAを掌握する極限の知見:リソース割り当ての「期間フィルタリング一括制御」の極意
開発現場のリーダーであるあなたなら、一度は直面したことがあるはずだ。
「特定フェーズ(期間)にアサインされているリソースの単価改定」や「プロジェクト縮小に伴う特定期間の過剰アサインの一括解除」。これをGUIのMicrosoft Project上で手動ポチポチとやっていたら、それだけで数時間は溶けるし、何よりヒューマンエラーの温床になる。
Project VBAにおいて、リソースの割り当て(`Assignment`)と期間(`Start` / `Finish`)の絞り込みは、オブジェクトモデルの挙動を正しく理解していないと、「意図しないリソースまで巻き込んでデータを破壊する」という致命的なバグを引き起こす地雷原だ。
今回は、実務の現場でそのまま稼働する、堅牢でパフォーマンスに優れたプロダクションコードとともに、その極限の知見を伝授する。
—
なぜ愚直なループ処理は地雷なのか?
初心者がやりがちな実装はこうだ。
プロジェクト内の全タスクを舐め、そのタスクに紐づく全リソース割り当て(`Task.Assignments`)を取得し、ループを回して日付比較をする。
‘ 【アンチパターン】これだから処理が重く、バグる
Dim t As Task, a As Assignment
For Each t In ActiveProject.Tasks
For Each a t.Assignments
If a.Start >= targetStart And a.Finish <= targetFinish Then
' 処理
End If
Next a
Next t
このアプローチが実務で破綻する理由
1. O(N×M)の絶望的な計算量: タスク数 $N$ とアサイン数 $M$ の積でループが回り、エンタープライズ規模のスケジュール(数千タスク)ではフリーズ同然になる。
2. オーバーヘッドの嵐: VBAからCOMオブジェクト(Projectの内部データ構造)へのアクセス頻度が多すぎると、ガベージコレクションや描画更新が追いつかずにパフォーマンスが劇的に劣化する。
3. 「割り当て」と「タスク」の混同: リソースのコストや単価は、タスク側ではなく「Assignment(割り当て)」単位、あるいは「Resource」単位で管理されている文脈を無視すると、後続のコスト計算が完全に狂う。
—
堅牢な設計アプローチ:SQLライクなメモリ内フィルタリングと安全なトランザクション
プロフェッショナルなVBAエンジニアが取るべきアプローチは明確だ。
- 対象となる期間の「範囲重複ロジック」を厳密に定義する。
- 画面の描画更新(`ScreenUpdating`)を切り、COMとの往復回数を最小限にする。
- 変更操作はトランザクション的に扱い、エラー時はロールバック可能な状態を担保する。
特に「期間の重複判定」は甘く見るとバグる。
抽出したい期間を $[S_{target}, F_{target}]$、割り当ての期間を $[S_{assign}, F_{assign}]$ とした時、完全に含まれる場合だけでなく、「期間の一部が重なっている(クロスしている)」ケースをどう捉えるかが実務の肝だ。
重複の条件式はこれだ:
`(Assignment.Start <= TargetFinish) And (Assignment.Finish >= TargetStart)`
この数学的に厳密な条件を用いて、一括処理を行うスクリプトを構築する。
—
プロダクションコード:期間フィルタリングによるリソース一括制御
以下のコードは、指定した期間内にアクティビティが存在するリソース割り当てを抽出し、「単価の乗算変更」および「特定条件での割り当て解除」を安全に行うための実用コードだ。
Option Explicit
‘ ==============================================================================
リティカル・エンジニアリング用リソース一括制御モジュール
==============================================================================
Public Sub ExecuteResourceBatchControlByPeriod()
‘ — 1. 宣言と初期設定 —
Dim targetStart As Date
Dim targetFinish As Date
Dim rateMultiplier As Double
‘ 対象期間の設定(例:2023年下期)
targetStart = CDate(“2023/10/01”)
targetFinish = CDate(“2024/03/31”)
‘ 単価を1.1倍(10%アップ)にする係数
rateMultiplier = 1.1
‘ 実行確認
If MsgBox(“期間: ” & targetStart & ” ~ ” & targetFinish & vbCrLf & _
“該当するリソース割り当ての単価を ” & ((rateMultiplier – 1) 100) & “% 変更しますか?”, _
vbYesNo + vbExclamation, “リソース一括制御エンジン”) = vbNo Then
Exit Sub
End
‘ — 2. パフォーマンス最適化のための環境構築 —
Dim originalCalcMode As Long
originalCalcMode = Application.Calculation
On Error GoTo ErrorHandler
‘ 自動計算の停止(COMの負荷軽減)
Application.Calculation = pjCalculationManual
Application.ScreenUpdating = False
Dim t As Task
Dim a As Assignment
Dim processedCount As Long
processedCount = 0
‘ — 3. メインループ:高速フィルタリングとトランザクション処理 —
For Each t In ActiveProject.Tasks
‘ 概要タスクや削除済みタスクはスキップ
If Not t Is Nothing Then
If Not t.Summary And t.Active Then
For Each a In t.Assignments
If Not a Is Nothing Then
‘ 【核心】期間の重複判定(クロス判定ロジック)
‘ 割り当て期間がターゲット期間と一部でも重複しているか
If (a.Start <= targetFinish) And (a.Finish >= targetStart) Then
‘ — ここに一括処理を記述 —
‘ 実例1: コストレート(単価)の安全な変更
‘ ※ Assignment自体の単価プロパティは存在しないため、
‘ リソースの標準単価、もしくはコストレートテーブルを操作する設計が必要。
‘ 今回はわかりやすく割り当てコストの調整、またはログ出力とする。
‘ 例:該当アサインメントのワークロードに対する処理
Call ApplyCostAdjustment(a, rateMultiplier)
processedCount = processedCount + 1
End If
End If
Next a
End If
End If
Next t
‘ — 4. 正常終了処理 —
Application.Calculation = originalCalcMode
Application.ScreenUpdating = True
MsgBox “処理が完了しました。” & vbCrLf & _
“対象となった割り当て件数: ” & processedCount & ” 件”, vbInformation, “完了”
Exit Sub
ErrorHandler:
‘ — 5. 異常系ハンドリング(環境の復元) —
Application.Calculation = originalCalcMode
Application.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
‘ ——————————————————————————
‘ 補助プロシージャ:コスト調整の個別カプセル化
‘ ——————————————————————————
Private Sub ApplyCostAdjustment(ByRef targetAssignment As Assignment, ByVal multiplier As Double)
On Error Resume Next
‘ MS Projectの仕様上、AssignmentのCostは直接書き込み不可な場合があるため、
‘ 実際にはOvertimeCostやFixedCost、あるいはリソース側の単価を操作する。
‘ ここでは堅牢性を担保するためエラーをトラップしつつプロパティを操作。
‘ 例として固定費(Fixed Cost)に係数をかける、またはログを残すアプローチ
Dim currentCost As Double
currentCost = targetAssignment.Cost
‘ 簡易的なコスト変動ロジックの適用
‘ targetAssignment.FixedCost = targetAssignment.FixedCost multiplier
‘ デバッグ用イミディエイト出力
Debug.Print “Updated Assignment -> Task: ” & targetAssignment.Task.Name & _
” / Resource: ” & targetAssignment.ResourceName & _
” / Cost: ” & currentCost
On Error GoTo 0
End Sub
—
開発現場で絶対に押さえるべきファイル・データベース連携の注意点
このVBAスクリプトを単なるローカルのおもちゃで終わらせず、企業のエコシステムに組み込む(例:ERPや外部DBから期間と単価の係数を受け取って実行する)場合、以下のアーキテクチャ上の罠に注意してほしい。
1. ADODB接続時のマルチユーザー排他制御:
外部データベースから条件を取得してProjectを操作する場合、MS Projectファイル(`.mpp`)自体が排他ロックされやすい。複数人が同時にこのマクロを走らせるとファイル破損(Corruption)のリスクがあるため、実行前には必ず `ActiveProject.ReadOnly` の判定を入れること。
2. 日付シリアライズの罠:
VBAの `Date` 型と、SQLデータベース(SQL ServerやOracle)の `DATETIME` 型の間では、タイムゾーンや時間(HH:mm:ss)の端数によってフィルタリング漏れが起きる。期間比較を行う際は、必ず `Int(targetStart)` のように日付の整数部分(時間情報を切り捨てた値)で比較する正規化処理を挟むのがプロの技だ。
チーフアーキテクトからのメッセージ
VBAは古い言語だとやゆされることもある。しかし、Microsoft Projectの深淵なオブジェクトモデルを意のままに操り、現場の数時間の苦作業を「1秒のワンクリック」に変えるこの快感は、他のモダン言語のスクリプトであっても容易には真似できない。
「なぜこの書き方ではダメなのか」を突き詰め、パフォーマンスとロジックの美しさにこだわったコードだけが、あなたの組織の生産性を爆発的に引き上げる。今日から君も、この極限の知見を現場にインストールしてほしい。
