Power Queryで休日マスタを使い、土日祝を除いた営業日ベースのリードタイムを計算する方法

Excelでタスク管理表を運用していると、単純な「発生日から完了日までの日数」ではなく、土日祝を除いた営業日ベースのリードタイムを見たくなる場面がよくあります。

たとえば、1月30日に発生したタスクが2月2日に完了した場合、カレンダー上では数日経っていますが、実際には土日を挟んでいるため、業務上の感覚ではそこまで長く感じないことがあります。こうしたケースでは、暦日ベースの差分だけを見ても、対応スピードの実態を正しく把握しにくくなります。

特に、月次の件数集計、分類別の傾向確認、残件管理、対応スピードの見える化を行いたい場合は、営業日ベースのリードタイム列を1つ持っておくだけで、分析の質がかなり上がります。

この記事では、Power Queryで以下の構成を作り、休日マスタを利用した再利用しやすい営業日リードタイム計算を実装する方法をまとめます。

今回作る構成

  • tblTasks … タスク表
  • tblHolidayMaster … 休日マスタ
  • Holidays … 休日一覧クエリ
  • fnBusinessLeadTime … 営業日リードタイム関数
  • Tasks … タスク表にリードタイム列を追加したクエリ

考え方はシンプルです。Excelシート上に休日一覧をテーブルで持ち、それをPower Queryで読み込み、発生日から完了日までの日付リストを作成し、その中から土日と休日マスタに含まれる日付を除外して営業日数を数えます。

この構成にしておくと、休日だけを差し替えれば毎年そのまま使い回せます。また、関数として独立させることで、別のタスク表や問い合わせ管理表、障害管理表にも流用しやすくなります。

想定するタスク表

まずは、Excel上で管理しているタスク表をテーブル化し、名前を tblTasks にしておきます。最低限、次のような列があれば構成できます。

タスク名発生日分類完了期日完了日
A案件の調査2026/01/05調査2026/01/082026/01/07
B障害対応2026/01/09障害2026/01/132026/01/14
C申請処理2026/01/30申請2026/02/022026/02/02

今回の営業日リードタイムは、基本的に 発生日から完了日まで を対象に計算します。未完了タスクでは完了日が空白になることが多いため、その場合は関数側で null を返すようにします。

休日マスタを別テーブルで持つ理由

祝日を考慮した営業日計算をPower Queryで行おうとすると、最初はMコードの中に祝日一覧を直接書きたくなるかもしれません。しかし、このやり方は長期運用ではメンテナンスが大変です。

毎年祝日をコード修正しなければならず、別ファイルに流用するときも再編集が必要になり、担当者が変わると属人化しやすくなります。さらに、振替休日や会社独自休日を追加したいときにも柔軟性が落ちます。

そのため、休日はExcel側でマスタ管理し、Power Queryから読み込む構成にしておくのが実務ではかなり扱いやすいです。こうしておけば、Power Queryは「休日を参照して計算する仕組み」に徹し、休日自体のメンテナンスはExcelシート上で完結できます。

休日マスタを作る

Excelシートに休日一覧を作成し、テーブル化して tblHolidayMaster という名前を付けます。最低限、次のような構成で問題ありません。

日付名称
2026/01/01元日
2026/01/12成人の日
2026/02/11建国記念の日

名称 列は必須ではありませんが、後でシートを見返したときに何の休日かが分かるため、入れておくのがおすすめです。

また、ここには一般的な祝日だけでなく、会社独自の休業日、年末年始、創立記念日、一斉休業日なども追加できます。つまりこのテーブルは、単なる祝日一覧ではなく、営業日計算から除外したい日付一覧として扱うと便利です。

休日マスタのクエリを作る

次に、Power Queryで Holidays というクエリを作ります。このクエリの役割は、tblHolidayMaster から有効な日付だけを取り出し、重複のない休日一覧に整えることです。

以下がそのまま使えるMコードです。

PowerShell
let
    Source = Excel.CurrentWorkbook(){[Name="tblHolidayMaster"]}[Content],
    ChangedType = Table.TransformColumnTypes(
        Source,
        {
            {"日付", type date},
            {"名称", type text}
        },
        "ja-JP"
    ),
    RemovedNull = Table.SelectRows(ChangedType, each [日付] <> null),
    DistinctDate = Table.Distinct(Table.SelectColumns(RemovedNull, {"日付"}))
in
    DistinctDate

このクエリでは、まずExcelテーブル tblHolidayMaster を読み込み、日付 を日付型、名称 を文字列型に揃えています。営業日判定では日付型であることが非常に重要なので、ここは必ず明示的に型を合わせておくのが安全です。

その後、日付が空白の行を除外し、最後に 日付 列だけを取り出して重複を削除しています。最終的には「休日の日付だけを持つクエリ」になっていれば十分です。

営業日リードタイム関数を作る

次に、関数クエリ fnBusinessLeadTime を作成します。この関数は、開始日と終了日を受け取り、土日と休日マスタを除外した営業日数を返します。

今回は、同日完了なら0日として扱う版を採用します。タスクのリードタイムとしては、この考え方のほうが自然な場面が多いためです。

以下がそのまま使える関数です。

PowerShell
(startDate as nullable date, endDate as nullable date) as nullable number =>
let
    Result =
        if startDate = null or endDate = null then
            null
        else if endDate < startDate then
            null
        else
            let
                DayCount = Duration.Days(endDate - startDate) + 1,
                DateList = List.Dates(startDate, DayCount, #duration(1, 0, 0, 0)),
                HolidayList = Holidays[日付],
                BusinessDays = List.Select(
                    DateList,
                    each
                        Date.DayOfWeek(_, Day.Monday) < 5
                        and not List.Contains(HolidayList, _)
                ),
                LeadTime = List.Count(BusinessDays) - 1
            in
                if LeadTime < 0 then 0 else LeadTime
in
    Result

関数のロジックを分解して理解する

この関数は短いですが、営業日計算に必要な処理がきれいにまとまっています。ここでは何をしているのかを順番に整理します。

開始日または終了日が空なら null を返す

まず、開始日または終了日が空なら計算できないため null を返しています。未完了タスクでは完了日が空白であることが多いため、この挙動は実務上かなり使いやすいです。

終了日が開始日より前なら null を返す

次に、終了日が開始日より前の場合も null を返しています。これはデータ不整合への保険です。誤入力や変換ミスがあった場合に、マイナス日数を返すより、空値として異常に気づける形のほうが後の確認がしやすくなります。

開始日から終了日までの日付一覧を作る

Duration.Days(endDate - startDate) + 1 で日数を算出し、List.Dates で開始日から終了日までの全日付リストを生成しています。+1 をしているのは、終了日をリストに含めるためです。

たとえば、2026/01/05 から 2026/01/07 であれば、2026/01/052026/01/062026/01/07 の3日分が対象になります。

休日一覧をリストとして取り出す

HolidayList = Holidays[日付] の部分では、先ほど作った Holidays クエリの 日付 列をリストとして取り出しています。これにより、各日付が休日マスタに含まれているかどうかを List.Contains で簡単に判定できます。

土日と休日を除外する

もっとも重要なのがこの部分です。

PowerShell
BusinessDays = List.Select(
    DateList,
    each
        Date.DayOfWeek(_, Day.Monday) < 5
        and not List.Contains(HolidayList, _)
)

Date.DayOfWeek(_, Day.Monday) < 5 は、月曜を起点に曜日番号を振り、0〜4 を平日、5〜6 を土日として扱っています。つまり土日を除外しています。

さらに not List.Contains(HolidayList, _) により、休日マスタに含まれている日付も除外しています。この2つの条件を満たした日だけが営業日として残ります。

件数から1を引いてリードタイム化する

最後に営業日件数を数え、そこから1を引いています。これは今回の定義が「発生日当日は0日目」という考え方だからです。

たとえば、発生日と完了日が同じ営業日であれば、営業日は1日分カウントされますが、リードタイムとしては0日でよい、という扱いになります。

そのため、List.Count(BusinessDays) - 1 としています。さらに、0未満になるケースに備えて、最終的には0未満なら0に補正しています。

タスク表に営業日リードタイム列を追加する

ここまでできたら、tblTasks を読み込むクエリを作り、カスタム列で関数を呼び出します。クエリ名は Tasks にしておくと分かりやすいです。

以下がそのまま使えるMコードです。

PowerShell
let
    Source = Excel.CurrentWorkbook(){[Name="tblTasks"]}[Content],
    ChangedType = Table.TransformColumnTypes(
        Source,
        {
            {"タスク名", type text},
            {"発生日", type date},
            {"分類", type text},
            {"完了期日", type date},
            {"完了日", type date}
        },
        "ja-JP"
    ),
    AddedLeadTime = Table.AddColumn(
        ChangedType,
        "営業日リードタイム",
        each fnBusinessLeadTime([発生日], [完了日]),
        Int64.Type
    )
in
    AddedLeadTime

これで、各タスクごとに 営業日リードタイム 列が追加されます。以降は通常のPower Queryと同様に、この列を使って絞り込み、集計、ピボットなどを行えます。

実際の計算イメージ

たとえば次のようなデータがあるとします。

タスク名発生日完了日
タスクA2026/01/052026/01/05
タスクB2026/01/052026/01/06
タスクC2026/01/092026/01/13

さらに、2026/01/12 が休日マスタに登録されているとします。このとき結果は次のようになります。

  • タスクA
    1/5 の同日完了なので、営業日リードタイムは 0
  • タスクB
    1/5 から 1/6 のため、営業日リードタイムは 1
  • タスクC
    1/9(金) から 1/13(火) の間に、1/10・1/11 の土日と 1/12 の祝日が入るため、営業日は 1/9 と 1/13 の2日だけ。したがって営業日リードタイムは 1

このように、暦日ではなく、実際の業務感覚に近いリードタイムを算出できます。

この構成のメリット

この方法のよいところは、単に営業日計算ができるだけではありません。運用と再利用のしやすさが大きな利点です。

休日の保守が簡単

休日をMコードに埋め込むのではなく、Excelテーブルで管理しているため、翌年分の祝日や会社休日を追加するだけでそのまま継続利用できます。コードを毎年修正する必要がありません。

会社独自休日にも対応しやすい

一般的な祝日だけでなく、年末年始、創立記念日、計画休業日、一斉休暇なども同じ休日マスタに追加できます。つまり、組織ごとの営業日定義に合わせて柔軟に拡張できます。

関数化しているので再利用しやすい

fnBusinessLeadTime を一度作っておけば、タスク管理表だけでなく、問い合わせ一覧、障害一覧、申請一覧など、発生日と完了日を持つあらゆる表で同じロジックを使い回せます。

月次集計や分類分析に繋げやすい

営業日リードタイム列が1つ増えるだけで、分類別の平均対応日数、月別の対応スピード比較、一定営業日を超えた案件の抽出など、分析の幅が一気に広がります。単なるタスク一覧から、意味のある分析データへ進化させやすくなります。

よくある注意点

日付型になっているかを必ず確認する

Excelでは見た目が日付でも、Power Queryでは文字列として読み込まれていることがあります。日付比較や営業日判定を正しく行うには、type date に揃えることが必須です。

休日マスタの空白と重複は除去しておく

今回の Holidays クエリでは、空白行の除去と日付重複の削除を入れています。小さい処理ですが、後々のトラブルを防ぎやすくなります。

同日完了を0日とするか1日とするかを決めておく

今回の記事では、同日完了を0日とする定義で実装しています。ただし、現場によっては「同日でも1日と数えたい」ケースもあります。その場合は次のように変更します。

TEXT
LeadTime = List.Count(BusinessDays)

つまり、ロジックを作る前に、業務上の定義を関係者で合わせておくことが大切です。

未完了タスクの扱いも先に決めておく

今回は完了日が空白なら null を返していますが、残件管理をしたい場合は「今日時点までの営業日経過」を出したいこともあります。その場合は別関数として、完了済み用と未完了用を分けて作ると整理しやすくなります。

応用例

この構成を作っておくと、以前のような月次サマリにもつなげやすくなります。たとえば、発生チケット、完了チケット、残件チケット、翌月完了チケットなどの集計に営業日リードタイム列を追加しておけば、件数だけでなく、対応スピードの観点でも見られるようになります。

さらに、分類ごとの平均営業日リードタイム、5営業日超のチケット件数、月末残件の平均経過営業日などの指標も作りやすくなります。Power Queryは前処理と集計を一箇所にまとめやすいため、こうした「意味のある列」を増やしていくと、レポートの価値がかなり上がります。

まとめ

Power Queryで土日祝を除いた営業日ベースのリードタイムを計算するなら、休日マスタを別テーブルで持ち、関数クエリで営業日数を返す構成がもっとも扱いやすいです。

ポイントを整理すると、次の通りです。

  • 休日一覧は Excel テーブル tblHolidayMaster で管理する
  • Holidays クエリで休日日付だけを整形する
  • fnBusinessLeadTime 関数で土日祝を除外した営業日数を返す
  • Tasks クエリで関数を呼び出し、リードタイム列を追加する
  • 同日完了を0日とするなど、業務ルールに合わせて定義を明確にする

この構成にしておけば、翌年以降も休日マスタを更新するだけで再利用できますし、Excelベースのタスク管理でも分析の精度を大きく上げられます。

「件数だけは見えているけれど、処理スピードまでは見えていない」という状態から一歩進めたい場合、Power Queryで営業日リードタイムを持たせるのはかなり効果的です。月次レポートや運用見える化を強化したいときに、非常に相性の良いパターンだと思います。

掲載コードまとめ

休日マスタクエリ Holidays

PowerShell
let
    Source = Excel.CurrentWorkbook(){[Name="tblHolidayMaster"]}[Content],
    ChangedType = Table.TransformColumnTypes(
        Source,
        {
            {"日付", type date},
            {"名称", type text}
        },
        "ja-JP"
    ),
    RemovedNull = Table.SelectRows(ChangedType, each [日付] <> null),
    DistinctDate = Table.Distinct(Table.SelectColumns(RemovedNull, {"日付"}))
in
    DistinctDate

営業日リードタイム関数 fnBusinessLeadTime

PowerShell
(startDate as nullable date, endDate as nullable date) as nullable number =>
let
    Result =
        if startDate = null or endDate = null then
            null
        else if endDate < startDate then
            null
        else
            let
                DayCount = Duration.Days(endDate - startDate) + 1,
                DateList = List.Dates(startDate, DayCount, #duration(1, 0, 0, 0)),
                HolidayList = Holidays[日付],
                BusinessDays = List.Select(
                    DateList,
                    each
                        Date.DayOfWeek(_, Day.Monday) < 5
                        and not List.Contains(HolidayList, _)
                ),
                LeadTime = List.Count(BusinessDays) - 1
            in
                if LeadTime < 0 then 0 else LeadTime
in
    Result

タスク表クエリ Tasks

PowerShell
let
    Source = Excel.CurrentWorkbook(){[Name="tblTasks"]}[Content],
    ChangedType = Table.TransformColumnTypes(
        Source,
        {
            {"タスク名", type text},
            {"発生日", type date},
            {"分類", type text},
            {"完了期日", type date},
            {"完了日", type date}
        },
        "ja-JP"
    ),
    AddedLeadTime = Table.AddColumn(
        ChangedType,
        "営業日リードタイム",
        each fnBusinessLeadTime([発生日], [完了日]),
        Int64.Type
    )
in
    AddedLeadTime

-IT関連