Option Explicit

'===========================================================
' 部品_VLOOKUP値取得 / GetVlookupValue
'   ・指定した範囲（表）の 1 列目から、検索値と一致する最初の行を探し、その行の、指定した列の値を返す（完全一致）
'     → Searches the first column of the specified table range for the search value (exact match) and
'        returns the value in the specified column of that row.
'
'   ・見つからない場合は、エラー値 #N/A（CVErr(xlErrNA)）を返す。呼び出し側は、IsError で確認する
'     → If not found, returns the error value #N/A; the caller checks it with IsError.
'
'   ※ この部品は、見つからない場合も、エラーでは止まらず、#N/A の値を返す（見つからないのは、結果のひとつ）
'     → ※ Not found is a normal result: the component returns #N/A as a value instead of raising an error.
'===========================================================

'===========================================================
' ■区分 / Classification
'   ・基礎区分：土台 ／ 用途区分：取得 ／ 操作対象区分：範囲
'     → Base: foundation / Purpose: get / Target: range
'
' ■バージョン / Version
'   ・Version 1.0（2026-10-29）
'===========================================================

'===========================================================
' ■URL（WordPress部品ページ）
'   https://feelcodelab.com/vba_parts/getvlookupvalue/
'===========================================================

'===========================================================
' ■前提条件（AI誤動作防止の最重要項目） / Preconditions
'   ・【最重要】表の範囲（vTableRng）は、呼び出し側が、範囲設定の部品で作って、変数に入れて渡す
'     → 範囲の 1 列目が、検索する列になる。取得する列も、範囲の中に含める
'     → 最終行が可変の場合は、範囲設定_行可変で作る（見つからない場合は Nothing になるので、先に確認する）
'     → [MOST IMPORTANT] The caller builds the table range with a range-setting component and passes the
'        variable. The first column of the range is the search column; the return column must be inside it.
'
'   ・【最重要】戻り値が、エラー値（#N/A）の場合は「見つからなかった」ことを表す。
'     呼び出し側で、IsError で必ず確認してから使う
'     → 確認せずに、戻り値を文字列や数値として使うと、型が合わない実行時エラーになる
'     → [MOST IMPORTANT] An error value (#N/A) means "not found". The caller must check it with IsError
'        before using the value.
'
'   ・取得する列の位置（vReturnCol）は、範囲の左端の列を 1 とした番号（Excel の VLOOKUP 関数の列番号と同じ）
'     → 検索する列（1 列目）の値を返したい場合は、1 を指定する
'     → The return column is a number counted with the left-most column of the range as 1 (same as Excel's VLOOKUP).
'
'   ・検索は「完全一致」で、英字の大文字・小文字は区別する（"a-001" と "A-001" は別の値）
'     ※ Excel の VLOOKUP 関数は区別しないが、この部品は、コードや ID の取り違えを防ぐため、区別する
'     → Exact match; uppercase/lowercase ARE distinguished (unlike Excel's VLOOKUP).
'
'   ・文字列の「1」と、数値の 1 は、一致しない（Excel の VLOOKUP 関数と同じ）
'     数値と日付は、値が同じなら一致する。検索する列の、セルのエラー値（#N/A など）は、検索の対象外
'     → The text "1" and the number 1 do not match. Cells with error values in the search column are skipped.
'
'   ・同じ値が複数ある場合は、最初に見つかった（最も上の）行の値を返す
'     → If the value appears more than once, the value of the first (top-most) row is returned.
'
'   ・取得するセルが、空白の場合は、Empty を返す。セルがエラー値の場合は、そのエラー値を返す
'     → An empty cell returns Empty; a cell with an error value returns that error value.
'
'   ・検索値が空（Empty・空文字・Null・エラー値）の場合は、呼び出し側の誤りとして、エラーで止まる
'     → An empty search value (Empty, "", Null, error value) stops with an error as a caller mistake.
'
'   ・設定の誤りは、#N/A ではなく、原因を示すエラーで止まる（Err.Raise）
'       - 表の範囲（vTableRng）が Nothing（vbObjectError + 1014）
'       - 取得する列の位置が、範囲の列数の外（vbObjectError + 1015）
'       - 検索値が空（vbObjectError + 1016）
'     → Setting mistakes stop with an error naming the cause instead of returning #N/A.
'
'   ・Offset / Find / Resize / With は禁止（検索は、配列に取り込んで比較する）
'     → Offset, Find, Resize, and With statements are prohibited.
'===========================================================

'===========================================================
' ■依存関係（部品名明記） / Component Dependencies
'   ・他の部品を参照しない（土台）
'     → References no other component (foundation).
'
'   ・組み合わせ：範囲設定_固定（または範囲設定_行可変）→ VLOOKUP値取得
'     （呼び出し側が、範囲を作って変数に入れ、その変数を渡す）
'     → Typical combination: [SetRange_Fixed / SetRange_VariableRows] -> [GetVlookupValue]
'
'   ・見つかった「行」の位置が必要な場合は、MATCH検索位置取得を使う（別の部品）
'     → Use GetMatchPosition (a separate component) when the position of the found row is needed.
'===========================================================

'===========================================================
' ■依存関係（シート・表） / Sheet Dependencies
'   ・特定のシートや表の名前には、依存しない（呼び出し側が渡した範囲だけを使う）
'     → Does not depend on any specific sheet or table name; it uses only the range passed by the caller.
'
'   ・表の範囲（vTableRng）の 1 列目が、検索する列になる。取得する列も、範囲の中に含める
'     → The first column of the table range (vTableRng) is the search column; the return column must also be inside the range.
'
'   ・取得する列の位置（vReturnCol）は、範囲の左端の列を 1 とした番号
'     → The return column position (vReturnCol) counts from the left-most column of the range as 1.
'===========================================================

'===========================================================
' ■技術解説 / Technical Notes
'
'   ・範囲（Range）で受け取る理由
'       → 範囲消去・範囲コピーと同じく、範囲は、自分のブック・シートを持っているため、ブックやシート名の
'          引数が不要で、部品が他の部品（範囲設定）を参照しなくて済む。
'       → A Range carries its own workbook and sheet, so no other component needs to be referenced.
'
'   ・見つからない場合に、#N/A（エラー値）を返す理由
'       → 値を返す部品なので、「見つからない」を、戻り値の中で区別できる必要がある。Empty や空文字を返すと、
'          「空白のセルの値」と区別できない。Excel の VLOOKUP と同じ #N/A にして、IsError で確認する形にする。
'          見つからないことは、実行する前から起こりうる結果なので、エラーで止めずに、呼び出し側が選べるようにしている。
'       → The caller must be able to tell "not found" from an empty cell value, so #N/A (as in Excel) is returned.
'
'   ・大文字・小文字を区別する理由
'       → MATCH検索位置取得と同じ。コードや ID の取り違えを防ぐ。区別しない検索が必要な場合は、別の部品にする。
'       → Same as GetMatchPosition: avoids mixing up codes and IDs.
'
'   ・On Error を使わない理由
'       → 設定の誤りは、実行の前に確認してエラーで止める。検索の途中で、実行時に失敗する処理はない。
'       → Setting mistakes are checked beforehand, and nothing here fails unpredictably at run time.
'===========================================================

'---------------------------------------------
' ■引数（型＋意味） / Arguments
'   vTableRng    : 表の範囲（Range）。1 列目が、検索する列。範囲設定の部品で作る
'                  → Table range (Range); its first column is searched. Made by a range-setting component.
'
'   vSearchValue : 検索値（Variant）。文字列・数値・日付
'                  → Search value (Variant): text, number, or date.
'
'   vReturnCol   : 取得する列の位置（Long）。範囲の左端の列を 1 とした番号
'                  → Position of the column to return (Long); left-most column of the range = 1.
'---------------------------------------------

'---------------------------------------------
' ■戻り値（型＋意味） / Return Value
'   Variant : 見つかった場合は、その行の、取得する列の値。見つからない場合は、エラー値 #N/A
'             → The value found, or the error value #N/A if not found.
'---------------------------------------------

'---------------------------------------------
' ■エラー / Errors (Err.Raise)
'   ・vbObjectError + 1014：表の範囲（vTableRng）が Nothing
'   ・vbObjectError + 1015：取得する列の位置が、範囲の列数の外（1 未満、または列数より大きい）
'   ・vbObjectError + 1016：検索値が空（Empty・空文字・Null・エラー値）
'---------------------------------------------

'===========================================================
' ■禁止事項（AI向け） / Prohibitions
'   ・戻り値が #N/A（見つからない）の場合を、IsError で確認せずに使うことの禁止
'     → Do not use the value without checking IsError (not found).
'
'   ・取得する列の位置を、シート上の列番号（A 列＝1 など）と取り違えることの禁止
'     （範囲の左端の列を 1 とした番号で指定する）
'     → Do not confuse the return column with a sheet column number; count from the left-most column of the range.
'
'   ・表の範囲の、1 列目以外で、検索しようとすることの禁止（検索は、必ず 1 列目）
'     → Do not expect a search in any column other than the first.
'
'   ・セル参照の直書き禁止（Range("A1") など）
'     → Hard-coded cell references are prohibited.
'
'   ・Offset禁止 / Find禁止 / Resize禁止 / With禁止
'     → Offset, Find, Resize, and With blocks are prohibited.
'===========================================================

'===========================================================
' ■使用例（サンプルコード） / Example
'   ◆例1：顧客コードから、顧客名（3 列目）を取得する / Example 1◆
'   Dim lastRow As Long
'   Dim rng     As Range
'   Dim v       As Variant
'
'   lastRow = 部品_最終行取得(ThisWorkbook, "顧客表", 1)                      '1列目の最終行
'   Set rng = 部品_範囲設定_固定(ThisWorkbook, "顧客表", 2, 1, lastRow, 3)    '2行目〜最終行の、1〜3列目
'
'   v = 部品_VLOOKUP値取得(rng, "A-001", 3)                    '1列目で検索し、範囲の3列目の値を取得
'   If IsError(v) Then
'       MsgBox "A-001 は、見つかりませんでした"
'       Exit Sub
'   End If
'
'   MsgBox v
'
'   ※ 設定の誤り（Nothing、取得する列の位置の誤り、検索値が空）の場合は、#N/A ではなく、
'      原因を示すエラーで止まる（起点で On Error で受けて、Err.Description を表示する）
'===========================================================

'===========================================================
' ■ラッパー関数（日本語名） / Wrapper Function
'   ・辞書の日本語名で呼び出すための窓口
'     → Entry point callable by the Japanese dictionary name.
'
'   ・内部では英語名の本体処理を呼び出し、結果（Variant）をそのまま返す
'     → Internally calls the English-named core function and returns its result (Variant) as it is.
'===========================================================
Function 部品_VLOOKUP値取得( _
        ByVal vTableRng As Range, _
        ByVal vSearchValue As Variant, _
        ByVal vReturnCol As Long) As Variant

    '本体処理（英語名）を呼び出し、結果をそのまま返す / Call core function and return its result
    部品_VLOOKUP値取得 = f_GetVlookupValue(vTableRng, vSearchValue, vReturnCol)

End Function

'===========================================================
' ■本体処理（英語名） / Core Function
'   ・表の 1 列目から、検索値と一致する最初の行を探し、その行の、指定した列の値を返す
'     → Returns the value in the specified column of the first row whose first column equals the search value.
'
'   ・見つからない場合は、エラー値 #N/A を返す
'     → Returns the error value #N/A if not found.
'===========================================================
Function f_GetVlookupValue( _
        ByVal vTableRng As Range, _
        ByVal vSearchValue As Variant, _
        ByVal vReturnCol As Long) As Variant

    Dim arr As Variant
    Dim cellValue As Variant
    Dim rowCount As Long
    Dim i As Long
    Dim isSearchText As Boolean

    f_GetVlookupValue = CVErr(xlErrNA)

    '---------------------------------------------
    ' ■範囲・取得する列の位置・検索値を確認（誤りの場合はエラーで止める）
    ' / Check the range, the return column and the search value (stop with an error if wrong)
    '---------------------------------------------
    If vTableRng Is Nothing Then
        Err.Raise vbObjectError + 1014, "f_GetVlookupValue", _
                  "表の範囲（vTableRng）が設定されていません（Nothing）。"
    End If

    If vReturnCol < 1 Or vReturnCol > vTableRng.Columns.Count Then
        Err.Raise vbObjectError + 1015, "f_GetVlookupValue", _
                  "取得する列の位置は、表の範囲の列数（" & vTableRng.Columns.Count & "列）の中で、" & _
                  "左端を 1 として指定してください（取得する列の位置=" & vReturnCol & "）。"
    End If

    If IsError(vSearchValue) Then
        Err.Raise vbObjectError + 1016, "f_GetVlookupValue", _
                  "検索値（vSearchValue）が、エラー値です。"
    End If

    If IsNull(vSearchValue) Or IsEmpty(vSearchValue) Then
        Err.Raise vbObjectError + 1016, "f_GetVlookupValue", _
                  "検索値（vSearchValue）が空です（Empty または Null）。"
    End If

    If VarType(vSearchValue) = vbString Then
        If Len(vSearchValue) = 0 Then
            Err.Raise vbObjectError + 1016, "f_GetVlookupValue", _
                      "検索値（vSearchValue）が空です（空文字）。"
        End If
    End If

    '---------------------------------------------
    ' ■値を配列に取り込む（1 セルだけの場合は、配列にならないため、配列を作って入れる）
    '   / Load values into an array (a single cell returns a scalar, so build a 1x1 array)
    '---------------------------------------------
    rowCount = vTableRng.Rows.Count

    If rowCount = 1 And vTableRng.Columns.Count = 1 Then
        ReDim arr(1 To 1, 1 To 1)
        arr(1, 1) = vTableRng.Value
    Else
        arr = vTableRng.Value
    End If

    isSearchText = (VarType(vSearchValue) = vbString)

    '---------------------------------------------
    ' ■上から順に、1 列目を比較（最初に一致した行の値を返す） / Compare the first column from the top
    '---------------------------------------------
    For i = 1 To rowCount

        cellValue = arr(i, 1)

        If IsError(cellValue) = False Then

            If VarType(cellValue) = vbString Then

                '文字列同士：大文字・小文字を区別して比較 / text vs text: case-sensitive
                If isSearchText Then
                    If StrComp(cellValue, vSearchValue, vbBinaryCompare) = 0 Then
                        f_GetVlookupValue = arr(i, vReturnCol)
                        Exit Function
                    End If
                End If

            ElseIf IsEmpty(cellValue) = False Then

                '数値・日付同士：値が同じなら一致 / number/date: equal if the values are the same
                If isSearchText = False Then
                    If cellValue = vSearchValue Then
                        f_GetVlookupValue = arr(i, vReturnCol)
                        Exit Function
                    End If
                End If

            End If

        End If

    Next i

End Function

