【VBA】ユーザー定義関数(UDF)で独自のワークシート関数を作る方法(コピペOK)

VBA
スポンサーリンク
スポンサーリンク

この記事でわかること

  • VBAで自作のワークシート関数(UDF)を作り、セルに =関数名(引数) で使えるようになる
  • Functionプロシージャの基本構文と、Subとの違いがわかる
  • エラー処理付きの実務向けUDFがコピペで動く

対象: Excel 2016以降 / Microsoft 365、Windows 10/11

所要時間: コピペ → 動作確認まで約5分

どんな場面で使う?

  • 税込価格の計算 — 税率変更があっても関数の中身を1箇所直すだけで、すべてのセルに反映させたい
  • 売上ランク・評価ランクの自動判定 — VLOOKUP+IFの複雑な数式を、=売上ランク(B2) のようにシンプルに書きたい
  • 営業日・休日の判定 — 土日判定や独自の休日カレンダーに基づく判定ロジックをセル関数として使いたい
  • 独自の文字列加工 — 半角カナの全角変換、電話番号のハイフン除去など、Excel標準関数では面倒な処理を1つの関数にまとめたい
  • チーム共通の業務ロジック — 部門固有の計算ルール(手当計算・送料計算など)をUDFとして共有し、数式のバラつきをなくしたい

完成イメージ(Before / After)

Before(IF文のネストで税込計算+端数処理):


=ROUNDDOWN(B2*(1+IF(C2="食品",8,10)/100),0)

数式バーに長い関数が並び、修正のたびにカッコの対応を探すことになる。

After(UDFで1つの関数にまとめる):


=税込価格(B2, C2="食品")

やっていることは同じだが、数式バーがスッキリする。税率の変更もVBA側で1箇所直すだけ。

自分も以前、税込価格の計算や独自の区分判定を毎回IF文のネストで書いていた。IF関数が4〜5段になると、数式バーを見ても何をやっているのか分からなくなるし、別シートにコピペすると参照がずれて正直しんどかった。VBAでUDFを作ってからは、セルに =税込価格(B2) と書くだけで済むようになった。数式バーがスッキリして、修正も1箇所で済む。同じようにIF文のネストで苦しんでいる人が、この記事でサクッとUDFを作れるようになればうれしい。

VBAのFunctionプロシージャを標準モジュールに書けば、セルに =関数名(引数) と入力して使える独自のワークシート関数(UDF)が作れる。

なお、SubプロシージャとFunctionプロシージャの使い分けは マクロから別のマクロを呼び出して処理を分割する方法 で詳しく解説している。

実行前の準備

バックアップを取る

UDF自体はセルの値を読み取って計算するだけなので、データを壊すリスクは低い。ただし、VBEでコードを編集する作業が入るため、念のためファイルのコピーを別フォルダに保存してから作業することを推奨する。

Excelをマクロ有効ブック(.xlsm)で保存する

拡張子が .xlsx のままだとVBAコードが保存できない。

  1. 「ファイル」→「名前を付けて保存」
  2. ファイルの種類を「Excelマクロ有効ブック (*.xlsm)」に変更
  3. 保存

手順(コピペ → 動作確認まで約5分)

VBE(コードを書く画面)を開く

  1. Excelで Alt + F11 を押す

標準モジュールを挿入する

  1. VBEのメニュー →「挿入」→「標準モジュール」

重要: UDFは必ず標準モジュールに書く。シートモジュール(Sheet1など)やThisWorkbookモジュールに書くと、セルから =関数名() で呼び出せない(#NAME? エラーになる)。

コードを貼り付けて動作確認する

  1. コードウィンドウに、下のコードをそのままコピペする
  2. Excelに戻り(Alt + F11 で切り替え)、任意のセルに =税込価格(1000) と入力
  3. 1100 が表示されれば成功

コード(最小版)– 税込価格を返すVBA UDF

セルに =税込価格(1000) と入力すると税込価格を返す。まずはこれで動きを確認する。


'============================================================
' ■ 税込価格を返すUDF(最小版)
'   → セルに =税込価格(価格) で使える
'   → 税率はデフォルト10%。=税込価格(価格, 8) で8%指定も可
'   → 貼り付け場所: 標準モジュール
'============================================================
Function 税込価格(価格 As Double, Optional 税率 As Double = 10) As Double

    税込価格 = Int(価格 * (1 + 税率 / 100))

End Function

使い方

セルに入力する数式 結果 説明
=税込価格(1000) 1100 税率10%(デフォルト)
=税込価格(1000, 8) 1080 税率8%を指定
=税込価格(B2) B2の税込価格 セル参照もOK

書き換えポイント

要素 説明 初期値
価格 税抜価格(引数) セルの値を渡す
税率 消費税率(%)。省略可 10(10%)
Int() 小数点以下切り捨て 切り上げや四捨五入にしたい場合は下記参照

ポイント: Function 関数名(...) As 型 で宣言し、関数名に値を代入する(税込価格 = 計算結果)。VBAには Return 文がないので、この書き方がルール。

なぜSubではなくFunctionで書くのかというと、セルに =関数名() と入力して使えるのはFunctionプロシージャだけだからだ。Subプロシージャはマクロとして実行するもので、セルの数式としては呼び出せない。Optional 税率 As Double = 10 の部分は「省略可能な引数」を意味していて、=税込価格(1000) と書けば税率10%が自動で適用され、=税込価格(1000, 8) と書けば8%で計算される。この仕組みのおかげで、普段は引数1つで使えて、軽減税率が必要なときだけ2つ目の引数を指定する柔軟な関数になっている。

端数処理の詳細(Round / Int / Fix の使い分け)は 端数処理(四捨五入・切り上げ・切り捨て)を正しく行う方法 を参照。

コード(実務版)– エラー処理+複数のVBA UDFセット

自作UDFをまとめた「共通関数モジュール」を作ってからは、新しいブックでもモジュールをインポートするだけで同じ関数が使えるようになった。チームで共有したら「これ標準関数にあればいいのに」と言われるようになった。

以下のコードには3つのUDFが入っている。必要なものだけコピペしてもOK。

※ コードを貼り付ける場所は標準モジュールです。シートモジュールでは動きません。


Option Explicit

'============================================================
' ■ 共通UDFモジュール(実務版)
'   → 税込価格V2 / 売上ランク / 営業日か の3関数
'   → エラー処理付き。不正な引数にはエラー値を返す
'   → 貼り付け場所: 標準モジュール
'============================================================

'------------------------------------------------------------
' UDF1: 税込価格(エラー処理+端数処理付き)
'   → =税込価格V2(B2)         税率10%で小数切り捨て
'   → =税込価格V2(B2, 8)      税率8%
'   → =税込価格V2(B2, 10, 1)  四捨五入
'------------------------------------------------------------
Function 税込価格V2(価格 As Variant, _
                     Optional 税率 As Double = 10, _
                     Optional 端数処理 As Long = 0) As Variant

    '--- 引数チェック: 数値でなければエラー値を返す ---
    If Not IsNumeric(価格) Then
        税込価格V2 = CVErr(xlErrValue)  '← セルに #VALUE! を返す
        Exit Function
    End If

    If 税率 < 0 Or 税率 > 100 Then
        税込価格V2 = CVErr(xlErrValue)
        Exit Function
    End If

    Dim result As Double
    result = CDbl(価格) * (1 + 税率 / 100)

    '--- 端数処理の分岐 ---
    Select Case 端数処理
        Case 0  '← 切り捨て(デフォルト)
            税込価格V2 = Int(result)
        Case 1  '← 四捨五入(WorksheetFunction.Roundで銀行丸めを回避)
            税込価格V2 = WorksheetFunction.Round(result, 0)
        Case 2  '← 切り上げ
            税込価格V2 = WorksheetFunction.RoundUp(result, 0)
        Case Else
            税込価格V2 = CVErr(xlErrValue)
    End Select

End Function

'------------------------------------------------------------
' UDF2: 区分判定(売上額→ランク振り分け)
'   → =売上ランク(B2)  セルの売上額からランクを返す
'------------------------------------------------------------
Function 売上ランク(売上 As Variant) As Variant

    '--- 引数チェック ---
    If Not IsNumeric(売上) Then
        売上ランク = CVErr(xlErrValue)
        Exit Function
    End If

    Dim amount As Double
    amount = CDbl(売上)

    Select Case amount
        Case Is >= 10000000
            売上ランク = "S"
        Case Is >= 5000000
            売上ランク = "A"
        Case Is >= 1000000
            売上ランク = "B"
        Case Is >= 500000
            売上ランク = "C"
        Case Is > 0
            売上ランク = "D"
        Case 0
            売上ランク = "(売上なし)"
        Case Else
            売上ランク = CVErr(xlErrValue)  '← 負の値はエラー
    End Select

End Function

'------------------------------------------------------------
' UDF3: 営業日判定(日付→営業日かどうか)
'   → =営業日か(A2)    True/False を返す
'   → 土日のみ判定(祝日は含まない簡易版)
'------------------------------------------------------------
Function 営業日か(日付 As Variant) As Variant

    '--- 引数チェック ---
    If Not IsDate(日付) Then
        営業日か = CVErr(xlErrValue)
        Exit Function
    End If

    Dim wDay As Long
    wDay = Weekday(CDate(日付), vbSunday)  '← vbSunday: 日曜=1(デフォルト)

    '--- 土(7)・日(1) → False、それ以外 → True ---
    Select Case wDay
        Case 1, 7
            営業日か = False
        Case Else
            営業日か = True
    End Select

End Function

使い方一覧

数式 結果 説明
=税込価格V2(1000) 1100 税率10%・切り捨て
=税込価格V2(1000, 8, 1) 1080 税率8%・四捨五入
=税込価格V2("abc") #VALUE! 数値でないためエラー
=売上ランク(5000000) A 500万以上
=売上ランク(B2) セルB2の売上ランク セル参照
=営業日か(A2) TRUE / FALSE 土日判定

書き換えポイント

要素 説明 カスタマイズ例
売上ランクの閾値 各ランクの境界値 自社の基準に合わせて変更
端数処理の種類 0=切捨, 1=四捨五入, 2=切上 必要に応じて追加
営業日判定 土日のみ判定 祝日配列を追加して拡張可能

コード解説:

実務版のポイントは3つある。まず、引数の型を Variant にしている点。セルから渡される値は数値・文字列・空白・エラー値など様々な型がありえるため、DoubleLong で受けると型不一致でエラーになることがある。Variant で受けてから IsNumericIsDate でチェックすれば安全だ。次に、エラー時に CVErr(xlErrValue) を返している点。これはセルに #VALUE! を表示させる書き方で、Excel標準関数と同じ挙動になる。MsgBoxでエラーを表示すると再計算のたびにダイアログが出て操作不能になるので、UDFでは必ず CVErr を使う。最後に、戻り値の型も Variant にしている点。数値を返す場合とエラー値を返す場合があるため、Double ではなく Variant にしておく必要がある。

Select Caseの書き方の詳細は Select Caseで複数条件の分岐をスッキリ書く方法 を参照。

日付の差分・加算が必要なUDFを作りたい場合は 2つの日付の差分・加算・比較を計算する方法 が参考になる。

よくある落とし穴5選

1. セルに #NAME? エラーが出る

原因: Functionプロシージャがシートモジュールに書かれている。UDFは標準モジュールに書かないとセルから呼び出せない。

対策: VBEの「挿入」→「標準モジュール」で追加したモジュールにコードを貼り付ける。Sheet1やThisWorkbookモジュールに書いている場合は、標準モジュールに移動する。

2. UDFからセルの値を書き換えようとしても何も起きない

自分もこれで30分ほど原因を探した。UDFの中で Range("A1").Value = 100 と書いたのに、セルA1が一切変わらない。デバッグしてもエラーは出ない。結局、「ワークシート関数として呼ばれたFunctionはセルの値を変更できない」というVBAのルールを知らなかっただけだった。もっと早く知りたかった。

原因: ワークシート関数として呼ばれたUDFは、セルの値変更・書式変更・シート操作ができない。エラーも出ずに無視される。

対策: セルを書き換えたい処理はSubプロシージャで書く。UDFは「値を返す」ことに集中させる。

3. 戻り値が常に0(または空)になる

原因: 戻り値を 関数名 = 値 の形で代入していない。VBAのFunctionプロシージャには Return 文がない。

対策: 関数名に計算結果を代入する。


' NG: Return は使えない(コンパイルエラーになる)
Function 税込(価格 As Double) As Double
    Return 価格 * 1.1
End Function

' OK: 関数名に代入する
Function 税込(価格 As Double) As Double
    税込 = 価格 * 1.1
End Function

4. 参照先セルを変えてもUDFの結果が更新されない

原因: 引数に渡していないセルをUDF内部で参照している場合、Excelはそのセルの変更を検知できず再計算されない。

対策: 参照したいセルはすべて引数として渡す。どうしても引数にできない場合は、関数の先頭に Application.Volatile を追加する(ただし計算負荷が上がる点に注意)。


Function 現在時刻表示() As String
    Application.Volatile  '← 毎回再計算される(負荷注意)
    現在時刻表示 = Format(Now, "hh:mm:ss")
End Function

表示形式の詳細は 日付や数値の表示形式をFormatで自由に変換する方法 を参照。

5. 引数を省略するとエラーになる

原因: 引数に Optional キーワードを付けていない。

対策: 省略可能な引数には Optional とデフォルト値を設定する。


' NG: 税率を省略するとエラー
Function 税込(価格 As Double, 税率 As Double) As Double

' OK: Optional + デフォルト値で省略可能に
Function 税込(価格 As Double, Optional 税率 As Double = 10) As Double

VBAのユーザー定義関数がセルに表示されないときの対処法

「Functionを書いたのに、セルに =関数名() と入力すると #NAME? エラーになる」という場合、原因はコードをシートモジュール(Sheet1など)やThisWorkbookモジュールに書いていることだ。UDFは必ず標準モジュールに書かないと、セルから呼び出せないというVBAのルールがある。対策として、VBEの「挿入」→「標準モジュール」で追加したモジュールにコードを移動する。自分もこれで1時間ほど原因を探したことがある。コードの中身が正しくても、貼り付け場所が違うだけで動かない。

VBAのUDFで#VALUEエラーが出るときの対処法

「自作関数をセルで使うと #VALUE! エラーが返る」という場合、原因は引数に渡された値の型が関数の想定と合っていないことが多い。たとえば引数を As Double で宣言しているのに、セルに空白や文字列が入っていると型変換に失敗してエラーになる。対策として、引数の型を Variant にして、関数の先頭で IsNumeric()IsDate() のチェックを入れ、不正な値には CVErr(xlErrValue) を返すようにする。戻り値の型も Variant にしておかないと、CVErr を返す際にエラーになるので注意が必要だ。

FAQ

Q1: SubとFunctionの違いは?UDFにはどちらを使う?

Function: 値を返せる。セルに =関数名() と入力して使える(UDFになる)。

Sub: 処理を実行するだけで値を返さない。セルからは呼び出せない。

セルで使いたいならFunction。ボタンやマクロ実行で使いたいならSub。

Q2: UDFにセル範囲を引数として渡せる?

渡せる。引数を As Range で宣言すればよい。


Function 条件カウント(rng As Range, 条件値 As Variant) As Long
    Dim c As Range
    Dim cnt As Long
    cnt = 0
    For Each c In rng
        If c.Value = 条件値 Then cnt = cnt + 1
    Next c
    条件カウント = cnt
End Function

セルに =条件カウント(A1:A100, "完了") のように使える。

Q3: UDFで作った関数を他のブックでも使いたい場合は?

UDFが入っているブックを開いた状態で、他のブックから =ブック名!関数名(引数) で呼べる。常に使いたい場合は、個人用マクロブック(PERSONAL.XLSB)に保存すると、Excel起動時に自動で読み込まれる。

Q4: UDFの中でMsgBoxを使ったら操作不能になった

ワークシート関数として呼ばれたUDFでMsgBoxを入れると、再計算のたびにダイアログが表示されて操作不能になる。デバッグには Debug.Print を使い、イミディエイトウィンドウ(Ctrl + G)で確認する。

Q5: 自作UDFがExcelの関数候補(オートコンプリート)に出てこない

VBAで作ったUDFは、Excel標準関数のようにセル入力時の関数候補リストには表示されない。ただし =関数名( まで入力すれば正常に動作する。関数の説明を「関数の挿入」ダイアログに表示したい場合は、以下のSubを一度だけ実行する。


Sub UDF説明を登録()
    Application.MacroOptions Macro:="税込価格", _
        Description:="税抜価格から税込価格を計算します。", _
        Category:=1  '← 1=財務カテゴリに表示される
End Sub

このSubはUDFの説明を登録するだけで、データの変更は一切行わない。

まとめ

  • UDF(ユーザー定義関数): VBAのFunctionプロシージャを標準モジュールに書けば、セルに =関数名() で使える
  • 戻り値の返し方: 関数名 = 値 の形で代入する(Returnは使えない)
  • 貼り付け場所: 必ず標準モジュール(シートモジュールだと #NAME? エラー)
  • エラー処理: 不正な引数には CVErr(xlErrValue) でエラー値を返す。戻り値の型は Variant にする
  • Optional引数: 省略可能な引数にはデフォルト値を設定する

UDFの最大の価値は「ロジックを1箇所に集約できる」ことだ。IF文のネストを100個のセルにコピーしていると、ロジックを変更するときに100箇所を修正する必要がある。UDFなら関数の中身を1箇所直すだけで、全セルに自動で反映される。自分はチームで共有するブックにUDFを導入してから、「数式の書き方がバラバラで集計結果が合わない」という問題がなくなった。まずは最小版の税込価格で動きを確認し、使い勝手がわかったら実務版のエラー処理付きバージョンに進むのがおすすめだ。

関連記事

次にやりたくなること

コメント

タイトルとURLをコピーしました