Article

【Excel】XLOOKUPの「縦横同時にスピルしない」問題を HSTACK で解決する

この記事で分かること

ExcelのXLOOKUPで複数列の戻り値を配列検索すると先頭1列しかスピルしない問題の原因と、HSTACKで解決する手順を解説します。BYROW・CHOOSECOLS・戻り範囲ラップなどのNG例とパフォーマンス比較も掲載。

ExcelのXLOOKUP関数で検索値を配列、戻り範囲を複数列にすると、戻り範囲を2列以上指定してもスピルは先頭の1列分しか展開されません。日本語圏では「縦横同時にスピルしない問題」と呼ばれており、Microsoft公式XLOOKUPドキュメントには明記されていない動作ですが、Excel 2024(ビルド19929)で再現します。本記事では、原因とHSTACK関数による本命解、MAKEARRAY/REDUCEなどの別解、そしてやってはいけない5つのパターンを実機スクショ付きで解説します。

本記事はExcel 2024(ビルド19929)で検証しています。Microsoft 365のExcelでも同等の挙動を想定していますが、Excel 2021以前ではHSTACK・MAKEARRAYなど一部の新しい関数が動かない、もしくは挙動が異なる場合があります。
XLOOKUPで配列検索値と多列戻りを同時に指定すると先頭1列しかスピルしない失敗例と、HSTACKで2列まとめてスピルさせた成功例の比較
配列検索 × 多列戻りの罠 → HSTACKで列ごとに分けて水平結合

結論: 多列戻りはHSTACKで個別XLOOKUPを横に並べる

XLOOKUPで配列の検索値と複数列の戻り範囲を同時に指定すると、戻り範囲がどれだけ広くても1列しかスピルしません。

=XLOOKUP(G2:G6, A2:A16, B2:C16)
→ 5行 × 1列のみ(C列データは展開されない)

本命の解決策は、列ごとに個別のXLOOKUPを書いてHSTACK関数で水平結合することです。

=HSTACK(
  XLOOKUP(G2:G6, A2:A16, B2:B16),
  XLOOKUP(G2:G6, A2:A16, C2:C16)
)
→ 5行 × 2列で正しくスピル

問題の再現: 検索値の渡し方で結果が変わる

同じXLOOKUP関数でも、検索値が単一のときと配列のときで、戻り範囲の扱いが変わります。書籍マスタを例にパターン別に動作を比較します。検索値はG2:G6、マスタはA2:E16です。

A2:E16の書籍マスタ表(書籍番号・在庫・書名・発刊日・金額の5列15行)とG2:G6の検索値配列(1001、999、1008、888、1015)を並べた画面
サンプル: 書籍マスタ + 検索値配列(マッチと未マッチ混在)

パターンA 単一検索値 × 多列戻り(正常)

=XLOOKUP(G2, A2:A16, B2:E16)
→ 1行 × 4列で正常スピル(在庫・書名・発刊日・金額が横並び)
単一検索値G2(1001)とB2:E16の戻り範囲を指定したXLOOKUPの結果が1行×4列(有・ケーススタディ民法・2016/10/28・1400)で正しく横並びにスピルしている画面
単一検索値なら多列戻りは1行×4列で正常スピル

パターンB 配列検索値 × 単列戻り(正常)

=XLOOKUP(G2:G6, A2:A16, B2:B16)
→ 5行スピル(検索値ごとに在庫値が縦に並ぶ)
配列検索値G2:G6と単列戻りB2:B16を指定したXLOOKUPの結果が5行スピル(有・#N/A・無・#N/A・無)で縦に並んでいる画面
配列検索値なら単列戻りは5行で正常スピル

パターンC 配列検索値 × 多列戻り(問題のケース)

=XLOOKUP(G2:G6, A2:A16, B2:C16)
→ 5行 × 1列のみ(B列の在庫だけが展開され、C列の書名は失われる)
配列検索値G2:G6と多列戻りB2:C16を指定したXLOOKUPの結果がI2:I6の5行×1列だけスピルし、隣のJ列が空のまま書名が失われている画面
配列検索値+多列戻りは先頭1列しかスピルしない(C列の書名は失われている)

戻り範囲を3列(B2:D16)、4列(B2:E16)に広げても結果は同じで、常に先頭1列だけがスピルします。

検索値戻り範囲結果
単一(G2)単列(B2:B16)1セル(普通の検索)
単一(G2)多列(B2:E16)1行 × 4列スピル ◯
配列(G2:G6)単列(B2:B16)5行 × 1列スピル ◯
配列(G2:G6)多列(B2:E16)5行 × 1列のみ ✕(縦横同時にスピルしない)

なぜ1列しかスピルしないのか

XLOOKUPの公式ドキュメント(Microsoftサポート)には、この制約は明記されていません。サードパーティの解説では、XLOOKUPに配列検索値と多列の戻り範囲を同時に渡すと縦横の2方向には展開せず先頭列だけになる挙動があり、英語圏では「array of arrays problem」として知られています。

具体的には、検索値が配列(N行)で戻り範囲が多列(K列)のとき、出力は本来N×Kの2次元になります。XLOOKUPはこの組み合わせでは縦横2方向に展開せず、戻り範囲の先頭1列だけを採用してN行 × 1列にして返します。横方向の検索値配列でも同様で、1行 × N列(先頭1行のみ)に折り畳まれます。SEQUENCEやMAKEARRAYは普通に2次元スピルできるため、これはExcel全体の制約ではなくXLOOKUPと配列検索値の組み合わせ固有の挙動です。

本命の解決策: HSTACKで個別XLOOKUPを水平結合する

「複数列が必要なら、列ごとに別々のXLOOKUPを書いて、HSTACKで横に並べる」のが本命解です。

=HSTACK(
  XLOOKUP(G2:G6, A2:A16, B2:B16),
  XLOOKUP(G2:G6, A2:A16, C2:C16)
)
HSTACKで在庫(B列)用XLOOKUPと書名(C列)用XLOOKUPを水平結合した結果が5行×2列で正しくスピル(有・ケーススタディ民法、#N/A・#N/A、無・やさしい経済法など)
HSTACK分解で5行×2列が正しくスピル(I列とJ列の両方に値が入る)

列を増やしたいときは、HSTACKの引数にXLOOKUPを追加するだけです。発刊日・金額まで含めるなら次のようになります。

=HSTACK(
  XLOOKUP(G2:G6, A2:A16, B2:B16),
  XLOOKUP(G2:G6, A2:A16, C2:C16),
  XLOOKUP(G2:G6, A2:A16, D2:D16),
  XLOOKUP(G2:G6, A2:A16, E2:E16)
)
HSTACKで4つのXLOOKUPを水平結合した結果が5行×4列で正しくスピルし、在庫・書名・発刊日(シリアル値)・金額のすべてが並んでいる画面
列を4つに増やしてもHSTACKに引数を増やすだけで5行×4列スピル

未マッチ行の#N/Aを「該当なし」などに置き換えるなら、外側にIFNAを1個だけ付けます。HSTACK全体に効くため、各XLOOKUPに第4引数を書く必要はありません。

=IFNA(HSTACK(
  XLOOKUP(G2:G6, A2:A16, B2:B16),
  XLOOKUP(G2:G6, A2:A16, C2:C16),
  XLOOKUP(G2:G6, A2:A16, D2:D16),
  XLOOKUP(G2:G6, A2:A16, E2:E16)
), "該当なし")
IFNAでHSTACK全体を外包し、未マッチ行(999と888)の#N/Aがすべて該当なしに置き換わって5行×4列の結果が綺麗に並んでいる画面
IFNAを外側に1つ付けるだけで未マッチ行をすべて該当なしに置換
HSTACK関数の基本的な使い方はHSTACK関数で配列を水平結合する方法を参照してください。

別解1: MAKEARRAY + LAMBDA(列数を変数で扱える)

戻り列数を引数で動的に変えたい場合は、MAKEARRAYとLAMBDAの組み合わせが便利です。第2引数の4を変えるだけで戻り列数を切り替えられます。

=MAKEARRAY(ROWS(G2:G6), 4, LAMBDA(r,c,
  XLOOKUP(INDEX(G2:G6,r), A2:A16, INDEX(B2:E16,,c))
))
MAKEARRAYとLAMBDAでXLOOKUPを行と列ごとに呼んで5行×4列スピルさせた結果(在庫・書名・発刊日・金額)
MAKEARRAYなら戻り列数を引数(4)で動的に切り替えられる

パフォーマンス上はHSTACKと差がありません(後述のH2-9の表を参照)。式の見た目はやや複雑ですが、戻り列数を変動させたいケースではこちらが扱いやすくなります。

別解2: REDUCE + VSTACK(動くが遅く、未マッチ処理に注意)

古くから紹介されている回避策にREDUCE + VSTACK + LAMBDAの組み合わせがあります。各検索値ごとにXLOOKUPを呼び、VSTACKで縦に積み上げる方式です。

=DROP(REDUCE("", G2:G6, LAMBDA(a,b,
  VSTACK(a, XLOOKUP(b, A2:A16, B2:E16, "該当なし"))
)), 1)
REDUCE+VSTACKで5行×4列スピルさせた結果、未マッチ行(999と888)では1列目に該当なしが入るが2-4列目は#N/Aのまま残っている画面
REDUCE+VSTACK: 第4引数のスカラ「該当なし」は1列目だけに入り、2-4列目は#N/Aのまま
REDUCE + VSTACKでXLOOKUP第4引数に「該当なし」を指定すると、未マッチ行では「該当なし」が1列目だけに入って、2列目以降は#N/Aのままになります。第4引数はスカラ専用で、列方向に伝搬しないためです。未マッチ行をすべて「該当なし」で揃えたい場合は、=IFNA(...)で外包するか、HSTACK分解を選んでください。

パフォーマンスは10,000行クラスでHSTACK分解の約25倍遅くなります(H2-9の計測表)。動くは動くものの、HSTACK分解の方がシンプルで速いため、新規に書くなら本命解(H2-4)を推奨します。

やってはいけない 5パターン

XLOOKUPの戻りが1列しか出ないとき、ネット上で見かける「これで直りそう」な書き方の多くは実機では動きません。代表的な5つのNGパターンを示します。

NG1: BYROW + LAMBDA で多列戻り → #CALC!

=BYROW(G2:G6, LAMBDA(x, XLOOKUP(x, A2:A16, B2:E16)))
→ #CALC!
BYROWとLAMBDAで多列戻りを試みた結果が#CALC!エラーになり、下に「BYROWはLAMBDAがスカラ返しを要求するため配列を返すXLOOKUPでは#CALC!」と注意書き、さらに下に単列戻りなら動く例
BYROW + 多列戻りXLOOKUPは#CALC!で動かない

BYROWのLAMBDAは各行ごとに1つのスカラ値を返すことを要求しています。XLOOKUPが1×4の行ベクトルを返すと、BYROWは結果配列を組み立てられず#CALC!になります。多列戻りにはMAKEARRAY(H2-5)を使ってください。BYROWは戻りが単列のときだけ使える関数です。

NG2: 戻り範囲をHSTACKで包む → 1列のまま

=XLOOKUP(G2:G6, A2:A16, HSTACK(B2:B16, C2:C16))
→ 5行 × 1列のみ(改善しない)
XLOOKUPの戻り範囲をHSTACKで包んでも結果が5行×1列のままで、隣の列が空である画面
戻り範囲側をHSTACKで束ねても1列制約は変わらない

戻り範囲をHSTACKで束ねただけでは、Excelから見れば「2列ある戻り範囲」というだけで、配列検索値 × 多列戻りの1列制約は変わりません。HSTACKは戻り範囲を包むのではなく、外側で個別XLOOKUPの結果を結合する位置で使ってください(H2-4)。

NG3: CHOOSECOLSで後置きラップ → #VALUE!

=CHOOSECOLS(XLOOKUP(G2:G6, A2:A16, B2:E16), 1, 2)
→ #VALUE!
XLOOKUPの結果をCHOOSECOLSで後置きラップしても、内側のXLOOKUPがそもそも1列しか返さないため#VALUE!エラーになる画面
CHOOSECOLS後置きラップは#VALUE! — 内側XLOOKUPが1列しか返さないため

内側のXLOOKUPはそもそも1列しか返さないため、CHOOSECOLSが2列目を選ぼうとして#VALUE!になります。後置きの整形では救出できません。

NG4: CHOOSECOLSで前置きラップ → 1列のまま

=XLOOKUP(G2:G6, A2:A16, CHOOSECOLS(B2:E16, 1, 2))
→ 5行 × 1列のみ

戻り範囲側をCHOOSECOLSで2列に整えても、配列検索値の1列制約はそのままです。

NG5: TOROW / TRANSPOSEで後置きラップ → 失われたデータは戻らない

=TOROW(XLOOKUP(G2:G6, A2:A16, B2:C16))
→ 5セル横並び(C列データは元から無い)
TOROWで後置きラップしてもC列のデータが戻らず、結果は5セル分のみ横に並ぶだけの画面
TOROW後置きラップ: 失われたC列データは復元されない(元から無い)

失われた列のデータは「セルの中に隠れている」のではなく、そもそも返されていません。TOROWやTRANSPOSEで配置を変えても新しいデータは現れません。

補足: =@XLOOKUP(G2:G6, A2:A16, B2:E16)のように@(暗黙的交差)を付けると、配列検索値が1セル分(G2)に絞られて結果も1セルになります。多列救出にはなりません。

補足: スピル妨害は通常通り #SPILL!、隣列の障害物は干渉しない

本記事の問題のケース(配列検索値 × 多列戻り)でも、スピル先のセル(1列スピル範囲内)に別の値があれば、通常通り#SPILL!エラーが出ます。一方、戻り範囲の2列目以降が本来スピルしてくる位置に障害物があっても2列目はそもそもスピルしないため干渉しません

対処は通常スピルと同じで、スピル先のセルをクリアして再計算するだけです。スピル系エラーの全パターンはエラー値「#スピル!」の発生原因と解決方法を参照してください。

パフォーマンス比較(参考、体感差はほぼない)

3つの正解パターン(HSTACK / MAKEARRAY / REDUCE+VSTACK)で計算時間を測りました。3試行の中央値です。

検索範囲検索キーHSTACKMAKEARRAYREDUCE + VSTACK
1,000行1000.006秒0.003秒0.004秒
10,000行1,0000.011秒0.017秒0.270秒
50,000行1,0000.048秒0.052秒0.223秒

10,000行クラスでHSTACKが最速、REDUCE+VSTACKは約25倍遅いという差はあるものの、絶対時間はどれも0.3秒未満で体感差はほぼありません。可読性で選ぶならHSTACK分解が最も素直です。

IFNA / 第4引数 / IFERROR の使い分け

HSTACK分解した結果に未マッチ#N/Aが混ざるときは、外側にIFNAを1個付けるのが最短です。

=IFNA(HSTACK(XLOOKUP(...), XLOOKUP(...)), "該当なし")

行・列ごとに違う代替値を出したいときは、IFNAの第2引数に同じ大きさの配列を渡せます。

XLOOKUPの第4引数("該当なし")に配列を渡すと、未マッチ行の値が#VALUE!になります(マッチ行は通常通りスカラが返ります)。配列代替値が必要な場合はIFNA外包に切り替えてください。

IFERRORは#N/A以外の#DIV/0!#REF!まで握り潰してしまうため、本来気付きたい別のエラーが見えなくなります。意図が明確なIFNAを優先してください。

スピル配列で#N/Aを消す書き方の使い分けはスピル#N/Aを消す4選に詳しくまとめています。

参考: VLOOKUPもExcel 2024では配列でスピルする

Excel 2024やMicrosoft 365のExcelでは、VLOOKUPに配列の検索値を渡しても自然にスピルします。XLOOKUPに乗り換えなくても、IFNA外包で同じことができます。

=IFNA(VLOOKUP(G2:G6, A2:C16, 2, FALSE), "該当なし")

ただし、Excel 2019やExcel 2016など動的配列非対応の環境で開かれるファイルでは旧形式に戻してください。

まとめ

  • XLOOKUPで配列検索値 × 多列戻りを指定しても、スピルは先頭1列分しか展開されない
  • 本命解はHSTACKで個別XLOOKUPを水平結合すること
  • 列数を変動させたい場合はMAKEARRAY + LAMBDAが便利
  • REDUCE + VSTACKは動くが10,000行規模でHSTACKの約25倍遅い
  • BYROW + LAMBDAは多列戻りで#CALC!になるので使えない(BYROWは単列戻り専用)
  • 戻り範囲をHSTACK・CHOOSECOLSで包む / TOROW / TRANSPOSE / @ などはどれも回避策にならない
  • 未マッチ#N/AはHSTACK全体に対してIFNAを1個外側に付けるのが最短

配布サンプル

本記事の主な式と失敗例・回避策を全8シートに整理した配布サンプルです。本命HSTACK・MAKEARRAY・REDUCE+VSTACKの3つの正解と、BYROW・戻り範囲HSTACK・CHOOSECOLS・TOROW/TRANSPOSEの4つのNGパターンが入っています。

関連記事

Next Read

このあと読む記事

今の内容に近い記事から、次の1本と補助記事を続けて見つけられるようにしています。

Keep Exploring

このテーマをさらに探す

同じテーマの入口記事と更新記事を、一覧の形でまとめています。

コメント