Claris FileMaker Pro で置換リスト変数を用いて複数フィールド内容のメンテナンス（+ちょっと ChatGPT を使う）

# はじめに
## 前置き
* 本記事は [Qiita Advent Calendar 2023](https://qiita.com/advent-calendar/2023) の [FileMaker Advent Calendar 2023](https://qiita.com/advent-calendar/2023/filemaker)  3 日目記事です。

https://qiita.com/advent-calendar/2023

https://qiita.com/advent-calendar/2023/filemaker

## 対象読者
* [Claris FileMaker](https://www.claris.com/ja/) ユーザ
* 複数のフィールドにおいて、複数の文字列を置換するようなメンテナンスが必要になる方
* 愚直に長いスクリプトステップを書きたくない方

## FileMaker とは？
https://www.claris.com/ja/

> モダンで幅広い業務に対応できる拡張性の高いカスタム App を作成

することが可能なもの、とのことです。

## FileMaker 動作環境
* FileMaker Pro 20.0.x
  * 19 以前でも動作するはずです

## 自己紹介
* 株式会社きみよりで代表をしています 野口啓之 / Hiroyuki Noguchi / Hi-Noguchi です
    * IT を中心とした DX コンサル／顧問などやっております
    * FileMaker については、内製化支援・トレーニングや 1on1 でのペアプロなど対応しています

https://kimi-yori.co.jp/

## 本記事でやること
まず本記事でやることは、ひとことで言うと以下となります。

- 複数のフィールドに対して、それぞれの内容に不正な値がある場合、置換処理をおこなう

この際に、愚直に処理を書いていくと、長々としたスクリプトとなってしまいます。
よって、以下のような工夫を凝らして作成することにします。

- 置換リスト変数を用意し、リスト行数分だけループ処理をおこなう
- モジュール処理用のスクリプトを切り分けて、汎用的に呼び出せるようにする


## 出来上がりのイメージ
とはいえ、このような抽象的な書き方だとイメージがつかないと思いますので、百聞は一見にしかず、先にキャプチャ画像を貼っておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/79aba11a-7724-bf47-d3f4-465b79eb10ed.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/e5a622dc-5f9c-8975-107d-cbaa2b798dc9.png)

これが出来上がりのイメージです。
さて、順を追ってやっていきましょう。


# 前準備
## ファイルとテーブルの準備
### ファイル
まず fmp12 ファイルを用意します。
`置換メンテナンス.fmp12` というファイル名にしました。

### テーブル
続いてテーブルの準備です。
`users` というユーザ情報のためのテーブルを作ります。
そして `id` `name` `address` `address_for_shipping` という、最低限 4 つのフィールドを用意しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/380642c1-c531-2a0a-3c39-1d56b1293403.png)

今回、 `address` と `address_for_shipping` の二つのフィールドをメンテナンス対象とすることにします。
ユーザ情報において、通常の `住所` と `発送先住所` が異なることは、よくありますからね。

:::note warn
もちろん、今回は簡易にするため同じテーブルにしているだけで、しっかりと正規形で設計する場合、発送先住所は複数に分かれうるものなので、発送先住所は別テーブルに格納するようにした方がよいです。
:::

## レイアウト
レイアウトは、デフォルトのままで特に弄りません。
……ラベルとフィールドを少しだけ距離あけました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/1518b136-86cf-c698-1a53-27455b9fcdd9.png)

## 「不正な値」の定義
さて、ダミーデータの準備をおこなう前に、そもそも今回、どのような「不正な値」を `address` `address_for_shipping` に入れることにするか、という定義をしておきます。

これについては、本当に直近で経験したことで、

- 都道府県が重複で入力されている

ということにします。
つまり、

- `東京都東京都中央区`
- `北海道北海道北海道札幌市`

みたいな状態のフィールド内容となっているレコードが複数存在してしまっている、という想定です。

:::note warn
これくらいなら、いったんフィールド内容をエクスポートして、外部ツールでメンテナンスしてから、照合一致でインポートし直せばよいのでは？🤔　と思われる方もいらっしゃるでしょう。

はい、それでも大丈夫です。

外部ツールでメンテナンスをおこなえるリテラシーを持っている人が、利用者であり続けるという前提であれば……
:::


## ChatGPT を用いてダミーデータの準備
`name` `address` `address_for_shipping` の三つについて、ダミーデータを投入していきましょう。

一昔前は、ダミーデータ用の csv を自由配付してくれているところから拝借したり、自前で用意したりしていたものです。

しかし今や便利な生成 AI くんがいますね。
以下のような指示を書いてみましょう。

```
あなたはデータサイエンティストです。
以下の要件で csv 形式のダミーデータを出力してください。

- Python コードの出力などではなく、この場であなた自身が csv 形式で出力してください。
- 出力行数は `100` でお願いします。
- カラムは `user` `address` `address_for_shipping` です。
  - カラム `user` は、日本人の姓名です。
  - カラム `address` は、都道府県市区町村までで、番地は不要です。
    - 都道府県と市区町村の間はスペースを含めないでください。
      - たとえば `東京都中央区` `北海道札幌市` のようにします。
    - ランダムで都道府県を重複出力されるようにして、エラーデータが含まれるようにしてください。
      - たとえば `東京都東京都中央区` `北海道北海道札幌市` のようになります。これはあくまで一例です。
      - 割合は全体の `20%` 未満にしてください。
  - カラム `address_for_shipping` は `address` と同じ要件です。
    - `address` と同じ住所と、異なる住所とを、混在させて出力してください。
    - エラーデータについても `address` と同じ要件です。

出力後、結果を自己参照して、要件と適合できているか確認してください。
```

これくらいの依頼内容であれば ChatGPT の GPT-3.5-turbo できっと対応できるのではないかな……

https://chat.openai.com/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/7555d2f2-676e-db2f-dc40-bc6700ff6133.png)

……と思いきや、全然ダメでした。
`都道府県が重複できていないよ` `100 行ないよ` など指摘しても、再出力で改善されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/933d725a-e55f-94ed-ea3d-08e3eecc2dcb.png)

なるべく汎用的に皆さんにお試しいただけるようにしたいので、有料版となる GPT-4 の利用は控えたいところです。

ということで、別の子に依頼してみましょう。

……（中略）……

……と、色々試してみましたが、全然ダメでした。
その惨憺たる結果は、末尾の [#おまけ](https://qiita.com/Hi_Noguchi/items/fb4f1af179126403c84a#%E3%81%8A%E3%81%BE%E3%81%91) に記録しておきます。
もう少しプロンプトエンジニアリングをがんばれば、何とかなる……のか……？

仕方ないので GPT-4 くんにお願いすることにします。
GPTs で弊社用にカスタマイズした `きみより-GPT` です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/fe747945-1157-5a32-7ef5-4a8bf47e2117.png)

```
以下の要件で csv 形式のダミーデータを出力してください。

- Python コードを構築し、csv 形式でダウンロードできるようにしてください。
- 出力行数は `100` でお願いします。
- カラムは `user` `address` `address_for_shipping` です。
  - カラム `user` は、日本人の姓名です。
  - カラム `address` は、都道府県市区町村までで、番地は不要です。
    - 都道府県と市区町村の間はスペースを含めないでください。
      - たとえば `東京都中央区` `北海道札幌市` のようにします。
    - ランダムで都道府県を重複出力されるようにして、エラーデータが含まれるようにしてください。
      - たとえば `東京都東京都中央区` `北海道北海道札幌市` のようになります。これはあくまで一例です。
      - 割合は全体の `20%` 未満にしてください。
  - カラム `address_for_shipping` は `address` と同じ要件です。
    - `address` と同じ住所と、異なる住所とを、混在させて出力してください。
    - エラーデータについても `address` と同じ要件です。

出力後、結果を自己参照して、要件と適合できているか確認してください。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/61b275ed-c60c-8232-c193-028a7b9e640a.png)

お、いい感じに csv がダウンロードできるようになりました！🎉
さすがです。

ただ、ちょっと `都道府県の重複` について解釈ミスがあったので、追加修正依頼を出してみます。

```
以下の点について修正された上で、再度の csv 出力をお願いできますでしょうか。

- 都道府県の重複出力について。
  - 都道府県名が三文字の場合にも対応できるようにする。
    - たとえば `神奈川` `鹿児島` など。
  - 重複させる際には `都` `道` `府` `県` も含めて重複させる。
    - たとえば `東京都東京都中央区` `北海道北海道札幌市` `大阪府大阪府大阪市` `山梨県山梨県甲府市` のようにする。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/8668658a-33fa-0f03-8f73-3f2eefb87477.png)

まだ `都道府県の重複` について解釈ミス……というか Python コードのミスなのかな……があったので、また追加修正依頼を出してみます。

```
以下の点について修正された上で、再度の csv 出力をお願いできますでしょうか。

- 都道府県の重複出力について。
  - 重複させる際には `都` `道` `府` `県` も含めて重複させるが、市区町村については重複させない。
    - たとえば `東京都東京都中央区` `北海道北海道札幌市` `大阪府大阪府大阪市` `山梨県山梨県甲府市` のようにする。
    - `北海道札幌市都北海道札幌市` のような市区町村を含んだ重複はしないようにする。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/220826eb-375b-aea8-ae12-5b6cf9449976.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/2d24ec99-d156-2d7d-f148-b073e51fb69a.png)

要件は良い感じに満たされるようになりました。
都道府県と市区町村とが `福岡県那覇市` などありえない組み合わせになっていますが、むしろダミーデータらしくて良い感じじゃないでしょうか。

## ダミーデータのインポート
それでは出力された csv ファイルをインポートしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/6ceb6a1d-be78-89b3-d447-544def8fbca4.png)

ヘッダ行を除外していないけれど、ダミーデータなので特に問題なし。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/61e7c5ed-de39-56c1-535b-8869974cf8e0.png)

良い感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/9ec64b52-913e-bd9c-c391-e61a9e08b13e.png)

さて、これにて準備は完了しました。
準備が長いんですよねー、こういうのって。

いよいよ本番の、スクリプト作成に入りましょう。

# スクリプト作成
## ハコを作る
ひとまずスクリプトステップを記述していくハコとして `メンテナンス` ` - モジュール_メンテナンス_フィールドを名前で設定` という 2 つを作っておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/67745618-9ada-2647-5a59-46de625f8fb5.png)

そして `メンテナンス` スクリプトの中身から作っていきましょう。

## 置換リスト変数の設定とエラー処理
置換リストとして、置換前／後の 2 つの変数が必要になります。
それぞれ `$before_pref_list` `$after_pref_list` としましょう。
リストの中身は以下のようになります。

```:$before_pref_list
"北海道北海道" & ¶ &
"青森県青森県" & ¶ &
"岩手県岩手県" & ¶ &
"宮城県宮城県" & ¶ &
"秋田県秋田県" & ¶ &
"山形県山形県" & ¶ &
"福島県福島県" & ¶ &
"茨城県茨城県" & ¶ &
"栃木県栃木県" & ¶ &
"群馬県群馬県" & ¶ &
"埼玉県埼玉県" & ¶ &
"千葉県千葉県" & ¶ &
"東京都東京都" & ¶ &
"神奈川県神奈川県" & ¶ &

（後略）
```

```:$after_pref_list
"北海道" & ¶ &
"青森県" & ¶ &
"岩手県" & ¶ &
"宮城県" & ¶ &
"秋田県" & ¶ &
"山形県" & ¶ &
"福島県" & ¶ &
"茨城県" & ¶ &
"栃木県" & ¶ &
"群馬県" & ¶ &
"埼玉県" & ¶ &
"千葉県" & ¶ &
"東京都" & ¶ &
"神奈川県" & ¶ &

（後略）
```

:::note info
この before / after の組み合わせを JSON で表現することも、もちろんできます。
お好みで。
:::

このようなリストも ChatGPT を用いると楽に用意できます。
一度、都道府県 47 を出力してもらった後で、 `"都道府県名" & ¶ &` の形式にしてください、だとか、都道府県名を重複表示させてください、だとか、お願いすればよいわけですね。

で、このリストの行数が before / after とで異なっているとよろしくないので、行数が一致しない場合のエラー処理を入れてあげます。

まずスクリプトの冒頭は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/379ecb40-bdc8-555d-d6d2-237eb1fb418e.png)

試しにスクリプトデバッガで走らせてみると、データビューアでも変数に正しく値が入っていることが確認できますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/9c93921d-eab5-1639-a542-6a67a1d385b8.png)

## 対象のフィールドに不正な値が含まれているレコードを検索
では次に、対象となるフィールドに `$before_pref_list` で定義した不正な値が含まれているレコードを検索する処理を作ります。

ここで、検索対象のフィールドに対して OR 検索を汎用的に実行できるようにしたいため、 ` - モジュール_メンテナンス_フィールドを名前で設定` スクリプトを用います。

先にスクリプト内容を見せてから、解説していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/d3f313d0-185d-01ce-6a79-c85499ad128d.png)

まず、モジュールスクリプトへ引数を渡すために、対象となるフィールドに移動して、取得関数でテーブル名とフィールド名を変数に格納します。

:::note warn
このとき、`フィールドへ移動` に失敗しないようにするために、必ず、以下の点に注意してください。

- レイアウト上に対象フィールドが存在していること
- 対象フィールドにカーソルが入る状態になっていること
:::

次に `$before_pref_list` 内の各行の不正値を OR 検索するので、ループ処理。
ループ回数の上限はリストの行数となります。

モジュールスクリプトへ渡す引数は `$target_table_name` `$target_field_name` `$pref` の三つです。

:::note info
もちろん引数の受け渡しには JSON を用いても構いません。
お好みで。
:::

引数を受け取るモジュールスクリプト側の処理は、以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/afd6e7b1-ac5b-3c45-5504-00183e1c1c34.png)

受け取った引数を `フィールドを名前で設定` の中で組み合わせます。
検索条件を新規で設定するのはこちらでおこないます。

また、引数が何も渡されていない場合のエラー処理も忘れないようしっかりと。

このモジュールスクリプトは、これで完成です。
この後でいじることはありません。

ということで、また `メンテナンス` 親元の方に戻ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/d3f313d0-185d-01ce-6a79-c85499ad128d.png)

ループでリスト行数分の OR 条件を設定し終えたら、検索実行です。
検索した結果の対象レコード数を `$target_count` 変数に格納しておきます。

## 全置換でメンテナンス実行
さて、基本となるスクリプトの最後の部分です。
以下に提示した上で、解説をしていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/2712c2b3-5d1f-0d9a-e318-fd02ab936ab4.png)

まずカスタムダイアログで表示するためのメッセージを変数に組み立てます。
対象レコード数が 0 だった場合には、その旨を `Else` 節で表示させましょう。

対象レコードが存在する場合、カスタムダイアログ内で実行するかどうかを確認するようにします。

`フィールド内容の全置換` はデバッグしづらいので、単一レコード処理のための `フィールド設定` も実行できるようにします。
最初にうまくいくかどうか試すときは、以下のようにしておくとよいでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/1a2b9ddd-de78-7b9a-c247-da4859b33f5e.png)

それでは `フィールド設定` `フィールド内容の全置換` で指定する計算式の中身も見てみます。

```java
While(
  [
    beforeList = $before_pref_list;
    afterList = $after_pref_list;
    originalText = GetField ( $target_table_name & "::" & $target_field_name );
    count = ValueCount ( beforeList );
    i = 0;
    text = originalText
  ];

  i < count;

  [
    i = i + 1;
    text = Substitute (
             text;
               GetValue ( beforeList ; i ); 
               GetValue ( afterList ; i )
           )
    ];

    text
)
```

`While` を用いて、リスト内の全ての値を置換処理するようにしています。

置換前のフィールド内容にあたるテキストは `GetField ( $target_table_name & "::" & $target_field_name )` として `GetField ()` 関数を用いることで、変数に格納されている値に応じて動的に取得することができます。

:::note info
ちなみに `Let` や `While` 内の変数について、識別できるようにするために特殊な記号を接頭辞とするというお作法も、あります。

ただ、そもそも関数内では、関数内変数しか用いないということにした方がよいのではないかという思想のもとで、関数外の変数は直接使わず、一度、関数内の変数に閉じ込めるようにしています。
そのため、関数内外の変数を区別する必要がなく、接頭辞はつけていません。

何だかこの注釈だけで一つの記事にできるくらい、様々な意見が出そうな内容だと思いますが……
:::

さて、これでスクリプトの基本部分はできました。
一度、テスト実行へと移ってみましょう。

# テスト実行
記事で書くのは面倒なので、スクリプトデバッガでひとつひとつ追うことはしませんが、本当はしっかりやった方がよいです。

さて、えいや、とスクリプトを動かしてみると、正しくカスタムダイアログが表示されてくれました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/d80a3b85-53ac-e5a1-b8da-0888694880d8.png)

実行すると `address` フィールドの値が正しくなりました！🎉

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/7e6fb9cc-4678-dc16-ada8-24d7bd993f59.png)

# スクリプト作成の仕上げ
## 単一処理から全置換へ
While の動作も正常に動いていましたので、コメントアウトの入れ替えをしておきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/6a812991-b10c-73b9-6598-ee506e147666.png)

これで対象レコードに対して実行されるようになりました。

## address_for_shipping への対応
続けてもう一つのフィールド `address_for_shipping` にも対応できるようにしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/94f0485c-0181-8eef-383e-faecc539b26d.png)

処理全体をコピーします！
そして以下の `フィールドへ移動` を `address_for_shipping` に変更します！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/a26b9bd0-12fc-83b5-a75a-efeceb5032e8.png)

さらに `フィールド設定` `フィールド内容の全置換` のフィールド指定を変更します！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/e643c626-df42-c9c7-65bc-beb3b81ea85f.png)


以上！

# 完成したスクリプト実行
それでは、全体を通した実行をしてみます。
いや、本当はもうちょっとデバッガ使った方がよいですけれども、割愛します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/74e57534-a9c1-f7bd-5f13-d7d72dd8e980.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/6083acbf-6d69-01ea-47ab-e67c14e70d8e.png)

実行完了すると、以下のように正常化されました！🎉

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/2444fb83-4de3-ce0d-73d4-0aef9843687b.png)

念入りに複数回実行しても大丈夫です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/98222bb1-b67f-ccaa-19bb-cc6964ca7cf6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/42c47e0b-c4d2-f72d-c456-692ffb0d7b55.png)

お疲れ様でした！
こちらをベースに、自身でカスタマイズして使ってみてください😀

# 予告
……え？

「フィールドがもっと多い場合、都度、スクリプトステップをコピーしていったら、とんでもなく長くなるんじゃないか？」　ですって？

「コピーするというのが冗長では」　ですって？

……はい！　その通りです！😂

いやでも、ほら、そこの共通化まで、一本の記事でやろうとすると、理解が追いつきづらくなるというか、記事が長くなりすぎてしまって誰にも読まれなくなってしまうじゃないですか……

……ということで、ですね……

次回の記事で、複数フィールドの処理の共通化をしてみます。
お楽しみに！

### 2023/12/22 追記
続編記事は以下です！

[Claris FileMaker Pro で置換リスト変数を用いて複数フィールド内容のメンテナンスを「汎用的」にする](https://qiita.com/Hi_Noguchi/items/5521eb3c2007e2f349b1)

https://qiita.com/Hi_Noguchi/items/5521eb3c2007e2f349b1


# おわりに
## 雑感
- FileMaker のアドカレにおいて「いつも小難しい内容の記事を投稿する人」みたいなイメージがついてしまってもイヤだなあと思って、少し一般ウケを狙った内容にしてみたのですが、どうでしたでしょう🤔
- 記事の量が分厚くなりがちで、執筆カロリーが高いので、もう少しサクッと短い内容の記事にしたらどうかとも思うのですが、それだとわざわざ自分が書く必要もないのでは、とかなんとか……
- 次回記事書いたら、またリンク貼ります！

## PR
- FileMaker についてお困りのことがありましたら気軽にお声がけください🙌

https://kimi-yori.co.jp

- FileMake Casual という Discord サーバも主催していますので、お気兼ねなくご参加ください！
  - https://discord.com/invite/PWWcYK8 ( 招待リンク )

https://discord.com/invite/PWWcYK8

# おまけ
## 各生成 AI サービスのダミーデータ出力結果
### Claude
Claude ではどうでしょうか。

https://claude.ai/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/f858936c-88c8-8daa-04c4-f77d7676cda9.png)

CSV データがどこにも出力されていない！
Claude はこういった謎のハルシネーションを起こすんですよね……

指摘すると、一応出力してくれますが、都道府県が重複されるところでは市区町村が含まれないなどの、期待通りでない結果が見られます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/924a673c-2efb-4b57-0ddb-5cba6dd44a8c.png)

### Bard
それでは Bard さんではどうでしょう。

https://bard.google.com/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/80894307-3144-a3c7-422a-d260ea714cdc.png)

だから 100 行ないってば。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/c221ec7f-0038-f04b-b4cb-f1b6384f1315.png)

指摘してもダメでした。

### Edge
最後に Edge ブラウザで使える Copilot with Bing Chat で試してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/d210fdb9-e9e2-09fd-4670-eb701ded9467.png)

略しすぎ……
そして指摘してもダメでした。