
Excel VBAで1行だけのRangeが配列にならない問題と置換処理の安定化
本記事の対象となる事象は、Excel VBAでA列6行目から最終行までを一括取得し、特定の文字列(例:「01」「02」「03」)を別の値に置換する処理において、対象が1行のみの場合に「実行時エラー'13': 型が一致しません。」が発生する点です。結論としては、Rangeの取得結果が「配列」ではなく「単一値」になる条件があり、ループ側が配列前提で書かれていることが原因になります。そのため、取得値が配列かどうかの分岐、またはセルを直接走査する方式に切り替えることが判断材料として重要です。
エラー'13'が出る条件と、Range取得の戻り値の違い
対象範囲が1セルだけになると、Range.Valueは配列ではなく単一のVariant値を返します。
A6から最終行までの範囲を tbl = Range("A6:A" & r) のように受ける構成は、複数セルであれば2次元配列(行×列)としてVariantに格納されます。一方で、最終行 r が6の場合、範囲は A6:A6 の1セルだけになります。このとき Range("A6:A6").Value は「配列」ではなく「文字列(または数値)の単一値」として返ることがあります。
そのため、For i = 1 To UBound(tbl) は「配列の上限」を取得しようとして失敗します。UBound は配列にしか使えないため、単一値であるVariantに対して呼ぶと型不一致として扱われます。この点から、範囲取得を配列前提で処理する場合は、必ず IsArray(tbl) などで配列かどうかを判定する設計が必要になります。
なお、置換対象が「セル内の文字列」で、途中に空白がなく、「01」「02」「03」のように見た目が2桁である場合でも、セルの実体が数値(1,2,3)になっていると一致判定がずれます。つまり、エラー自体は配列問題ですが、置換の一致条件には「文字列として扱う」前提の確認が残ります。以上を踏まえると、まずは「1セル時の戻り値」と「文字列としての比較」を同時に安定させることが要点になります。
置換要件の整理と、実装で崩れやすいポイント
置換要件が単純でも、データ件数とデータ型の差で挙動が変わる点が実務上の確認点となります。
本記事が示す状況における置換要件は次の対応関係です。
-
"01" → "100"
-
"02" → "400"
-
"03" → "100"
このように、複数キーが同じ結果に集約されるため、Select Case は読みやすい構造になります。ただし、崩れやすいポイントが2つあります。
1つ目は、対象範囲が1行のみ(A6のみ)となる場会です。前述のとおり、Rangeの取得結果が配列ではなく単一値となり、UBound 前提のループが成立しません。
2つ目は、比較対象が「文字列」かどうかです。たとえば表示上「01」でも、セルの中身が数値1で書式だけ2桁表示になっている場合、Case "01" は一致しません。この点から、CStr で明示的に文字列化して比較する、または Format を使って2桁化して比較する、といった選択肢が出ます。ただし、置換後の値も文字列でよいのか(100や400を数値として使うのか)は用途に依存します。
つまり、置換ロジック自体は短いのに、範囲の件数(1行か複数行か)とデータ型(文字列か数値か)が違うだけで、エラーと未置換が起きる構造です。次章では、この構造を踏まえて、実装を2系統に分けて整理します。
安定させる実装パターン2つ(配列方式/セル走査方式)
「配列として取得する方式」か「セルを直接走査する方式」かを先に決めると、例外処理が整理できます。
結論から言い換えると、1行問題を吸収するには「配列判定の分岐」か「そもそも配列にしない」かのどちらかになります。比較しやすいように、代表的な選択肢を整理します。
| 方針 | 1行時の強さ | 特徴 |
|---|---|---|
| 配列方式(IsArrayで分岐) | 強い | 一括代入が速い。分岐が必要 |
| セル走査方式(For Each) | 強い | 実装が単純。速度は範囲次第 |
| 強制2次元配列化(ラップ) | 中 | 書き方に癖が出る |
この結果、件数が多い可能性があり、速度面を維持したいなら配列方式を、まず確実に動かしたいならセル走査方式を選ぶ整理になります。以下に、それぞれの形を示します。
配列方式:IsArrayで1セルを吸収する例
Option Explicit
Sub ReplaceCodes_ArraySafe()
Dim r As Long
Dim rng As Range
Dim tbl As Variant
Dim i As Long
Dim v As String
r = Cells(Rows.Count, 1).End(xlUp).Row
If r < 6 Then Exit Sub
Set rng = Range("A6:A" & r)
tbl = rng.Value2
' 1セルだと配列にならない場合があるため分岐
If IsArray(tbl) Then
For i = 1 To UBound(tbl, 1)
v = CStr(tbl(i, 1))
Select Case v
Case "01", "03"
tbl(i, 1) = "100"
Case "02"
tbl(i, 1) = "400"
End Select
Next i
rng.Value2 = tbl
Else
v = CStr(tbl)
Select Case v
Case "01", "03"
rng.Value2 = "100"
Case "02"
rng.Value2 = "400"
End Select
End If
End Sub
この方式では、複数行のときは従来どおり配列で処理し、1行のときだけ単一値として置換します。UBound(tbl, 1) のように次元を指定しておくと、配列の形が明確になります。なお、Value2 を使うのは、日付型などの変換差を減らしやすいことが理由です。
セル走査方式:範囲が1セルでも同じ書き方で動く例
Option Explicit
Sub ReplaceCodes_ForEach()
Dim r As Long
Dim rng As Range
Dim c As Range
Dim v As String
r = Cells(Rows.Count, 1).End(xlUp).Row
If r < 6 Then Exit Sub
Set rng = Range("A6:A" & r)
For Each c In rng.Cells
v = CStr(c.Value2)
Select Case v
Case "01", "03"
c.Value2 = "100"
Case "02"
c.Value2 = "400"
End Select
Next c
End Sub
この方式は、範囲が1セルでも複数セルでも同じ制御で成立します。一方で、セル単位の書き換えになるため、対象件数が極端に多い場合は配列方式に比べて遅くなる可能性があります。ただし、A列の限定範囲で数千行程度なら、運用上は十分なケースもあります。
以上のとおり、同じ置換要件でも「Range取得の戻り値」を前提にすると例外が発生します。そうすることによって、1行時のエラーを構造的に消せます。
仕上げの確認点(データ型・最終行判定・保守性)
最終行の決め方とデータ型の取り扱いを固定すると、未置換と誤置換の条件差が減ります。
最後に、エラーを消した後も残りやすい確認点を整理します。第一に、最終行 r の算出です。Cells(Rows.Count, 1).End(xlUp).Row はA列の最終データ行を取る定番ですが、A列に空白が混ざる場合は意図しない行が最終行になることがあります。この点から、対象データの定義(空白を許容するか)を合わせておく必要があります。
第二に、データ型です。前提として「セルに文字列としてデータがある」条件なら、Case "01" の一致は成立します。ただし、実データに数値が混在する可能性があるなら、CStr で比較側を文字列固定にする運用が分岐を減らします。他方、出力を数値として扱いたい場合は、代入側を "100" ではなく 100 にする整理もあります。つまり、計算に使うのか、コードとして保持するのかで型が変わります。
第三に、保守性です。置換ルールが増える可能性があるなら、Select Case のままでもよい一方で、辞書(Dictionary)で対応表を持つ方法もあります。ただし、辞書は参照設定や遅延バインディングの扱いが増えるため、本記事の対象テーマではまず「1行時に落ちない」ことを優先し、ルールが増えた段階で方式を再検討する流れが整理しやすいです。
要点を整理すると、エラー'13'はVBAのRange取得が「1セル時だけ単一値に変わる」ことが原因であり、配列判定の分岐またはセル走査に切り替えることで再現条件を潰せます。なお、比較が文字列前提である点も同時に固定すると、運用上の未置換を減らす判断材料になります。