Excelでメンテナンス性の高い二段階リスト入力規則を作成する

# はじめに
今回やりたいことです。
階層的に整理された、2段階のデータがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/a411de66-e9ff-016d-ce68-adf2e2edb6ab.png)

入力規則としてドロップダウンリストを設定し、1階層目の選択内容に従って、2階層目の入力候補がフィルターされる というシートを作成します。
下の例でいうと、担当部署として「ばら」を選択した行では、「ばら」に属するデータ「サクラ」「イチゴ」「アーモンド」のみが選択できるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/c3099c57-6629-7404-ddda-08a1a2790dc9.png)


# よくあるやり方
まずは、これまでよく使われてきたやり方に触れておきます。
**定義された名前**を使う方法です。

## 下準備
定義用のシートに、以下のような構成でセルに階層構造を定義します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/15764180-d08e-eb55-7fa7-e58a379d9816.png)

範囲を選択し、[数式]タブの定義された名前 [選択範囲から作成]をクリックし、[上端行]にチェックを入れてOKを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/430ad89f-3eb8-11a0-5b9d-8720e34393e2.png)

すると、上端行の値からそれぞれの列で、定義された名前が作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/44acb60a-1760-6db5-ed56-cfc4a0d9fdbc.png)

下準備はこれで完了です。

## 入力規則の設定
次に、シートに入力規則を設定していきます。

### 1階層目
こちらは単純です。
入力規則の[元の値]に、先ほど作成した定義用のシートの1行目を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/931dd11b-df35-b647-6e9d-9f91904d4c23.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/77fba1d0-9c38-fe05-390b-10181cd6830f.png)

これで、1階層目を入力するためのドロップダウンリストができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/478c0eac-1a62-97da-826e-1e0ec6bac387.png)

### 2階層目
2階層目には少しExcel関数の知識が必要です。
入力規則の[元の値]に、```＝INDIRECT(1階層目の入力セル)```を入力します。
この例では```1階層目の入力セル```には、隣の```C2```セルを指定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/6ef9e190-f9b9-29ce-f6d3-76c26a009209.png)

:::note info
入力規則や条件付き書式で、相対的な位置関係を参照したい際には、**R1C1参照形式**にしておくとわかりやすいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/dbfe04d2-10bd-b98b-a646-a891d2839d8d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/cba5c92e-2188-1733-35b0-53788fd7e6ea.png)
:::

これで定義された名前を使用した2段階リストができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/c3099c57-6629-7404-ddda-08a1a2790dc9.png)

### INDIRECT関数
簡単に関数の説明をしておきます。
INDIRECT関数は、引数に指定した文字列から、セル・名前に対する参照を返す関数です。
今回の例では```C2```セルから```さかな```という文字を取り出し、定義された名前である```さかな```のセル範囲を参照しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/568ffb5c-f2d0-3f2e-2dae-77bd9baf1020.png)
本筋ではないので、詳しくは公式ページなどをご参照ください。

https://support.microsoft.com/ja-jp/office/indirect-%E9%96%A2%E6%95%B0-474b3a3a-8a26-4f44-b491-92b6306fa261

## この方法の悪いところ
ひとえにメンテナンス性が悪いです。
例えば、部署名の「ばら」が「バラ科」に変更されたとき、以下の手順で変更が必要になります。

**1. 定義用シートの編集**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/7ac2a3e2-6cef-89e2-c74d-62e062ecc3c5.png)

**2. 定義された名前の編集** 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/8c6dbc67-1923-ba0c-5905-664b47b619f6.png)

他にも所属人員の追加では定義された名前の範囲を編集しなければなりませんし、新しい部署ができれば新規で定義された名前を作成しなければなりません。

**シートと名前の2項目を毎回編集しなくてはならない**のはかなりの手間です。

# 今回説明するやり方
**テーブル形式で管理できればメンテナンス性が高まるのではないか**と考えました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/19c236d6-40ed-8c76-27f5-e9952c916ad2.png)

以降はスピルが使用できるバージョン、M365環境にて動作します。

## 下準備
まずは下準備として、管理用のテーブルを作成します。
テーブルにはわかりやすい名前として、それぞれ```T_部署``` ```T_部員```と名付けておきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/e2989d3d-e192-1182-9c8e-52995c7347a4.png)

### 1階層目のための準備
部署のリストです。
テーブルに項目が増えたときにも範囲が自動的に拡張するように設定したいと思います。
テーブルの範囲を参照する、**構造化参照**という方法で、自動的にテーブルの列全体を指定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/6f878835-e1fc-c107-1393-597471abbb94.png)
しかし、データの入力規則では、この構造化参照は使用できません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/89f8d8b0-4edb-1e49-a525-051e3f513de6.png)

これを解決する方法はいくつかあって、例えば先ほどと同様にINDIRECT関数を使用する方法。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/9c31bbf4-1cb7-ec93-b4a1-f8c7a158af77.png)

もう１つは、いったん構造化参照で別のセルに書き出し、そのセルからのスピル範囲を示す演算子```#```を使う方法です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/1e56a576-2f75-4b9c-0bf4-15d14311f5de.png)

INDIRECTを使う方法では、テーブル名や列名が変わったときに追従しなくなってしまうので、スピル演算子を使う方法を使用します。

ついでに**SORT関数**を追加して、並び替えも行っておきます。
(あいうえお順に並べ替えています)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/d70d3d2f-f20f-5d7d-2b58-2c850aea40e2.png)

```excel:一旦セルに書き出す関数
=SORT(T_部署[所属])
```


### 2階層目のための準備
ところで、入力規則の元の値には、参照(セル範囲)を指定することはできますが、配列(値の集合)を指定することはできません。
| 元の値 | 可否 |
|:-|:-:|
| **参照** (セル範囲)  | :o:  |
| **配列** (値の集合)  | :x:  |

なので、範囲として連続的に指定できるセルの並びを用意してあげる必要があります。
テーブルの並びがバラバラだと、セル範囲として指定することができないので、テーブルの入力状態によらず、並びを整理してあげる仕組みを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/d826100e-b0a7-ca15-23e8-1dc5e8cfc6cd.png)

これにも、**SORT関数**を使用します。
あいうえお順に並べることで、項目ごとに固めて並べることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/0afd5589-e70f-ecc2-7ea7-30540d2a7627.png)
```excel:一旦セルに書き出す関数
=SORT(T_部員)
```

## 入力規則の設定
次に、シートに入力規則を設定していきます。

### 1階層目
1階層目は簡単です。
1階層目のために並べ替えて書き出したセル範囲を、スピル演算子```#```を使って指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/801eeb3c-dd72-94eb-8110-5216b28b93d3.png)

### 2階層目
ここが一番ややこしいところです。入力規則に以下の数式を設定します。
**OFFSET関数**で参照を返すのがポイントです。
ここで、```定義!$K$2```は下準備で2階層目のために書き出したセル、```C2```は入力欄の1階層目のセルです。
```excel:2階層目の入力規則
=LET(
    rng,定義!$K$2#,
    x,C2,
    r,XMATCH(x,CHOOSECOLS(rng,1))-1,
    cnt,SUM(N(CHOOSECOLS(rng,1)=x)),
    OFFSET(rng,r,1,cnt,1)
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/1e4209cf-bf43-d29d-6cc4-b6d6d54c5be2.png)

関数の解説です。
LET関数を使って、手続き的に計算していますが、やってることは以下の通り、目的のセル範囲を抽出する処理です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/5dda1608-5547-c449-d7f1-f66ceda51f85.png)


解説は以上です。

# おまけ
最後にできそうでできない方法を書いておきます。
「テーブルから条件に合う部分だけを抽出したい」といえば**FILTER関数**です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/61b08d17-c00c-4321-7ec1-20559eb5fd9f.png)

しかしながら、入力規則には配列は指定できず、参照で指定する必要があります。
FILTER関数の返り値は参照ではなく配列ですので、直感的なこの方法は使えません。
```excel:2階層目の入力規則(できない)
=FILTER(CHOOSECOLS(定義!$K$2#,2),CHOOSECOLS(定義!$K$2#,1)=C2)
```