背景
対象レコードのフィールドをList関数のように改行区切りで取りたいときに、
カスタム関数のGetFieldValuesを使用しています。
で、あるときに、レコード数がなかなか多い件数を取得したときに、
「?」が返ってきたので、調べてみました。
GetFieldValuesの説明
中身はこんな感じです。
ifをつかうよりcaseの方が実行速度が速いそうなので、
fmpro.jpに登録されている内容から少し変更しています。
(かなり前の情報だけど、今もそうなのかな?)
説明
Case (
//最終行かどうかチェック
Cnt < Get ( 対象レコード数 );
//最終行でなければ、Cnt番目の内容を取得、改行を追加して、同じカスタム関数を呼び出す(再帰する)
//このとき、Cntに+1して、次の行をとってくる
GetNthRecord ( Field ; Cnt ) & "¶" & GetFieldValues ( Field ; Cnt + 1 );
//最終行の時はCnt番目の内容を返す
GetNthRecord ( Field ; Cnt )
)
ザ・再帰処理って感じですね!
で、なんで「?」で返ってくるか調べたら、
対象レコード見てみると一万数十件。。。
再帰処理の上限回数に引っかかっていたみたいでした。
ちょっと多いけど、いっとけ!で実行してもダメですねっ!!
再帰処理の上限回数
書き方によって上限が、10,000回か50,000回になるらしいです。
ふむふむ
GetFildValuesの書き方だと、10,000回が制限で怒られたと。
じゃあ50,000回の書き方ですれば、
残りの数十件のためにスクリプト作らんでもいいなーと思ったのですが、
そこから理解するまでに時間がかかった。。。
で、結局 50,000回を上限にするには?
ここで質問されている内容を参考にさせて頂きました。
キーワードは「末尾最適化」
対象レコードが2行のとき、GetFieldValuesの動きを順に見ていくと、
1. 1レコード目をGetNthRecordで取得する。2レコード目の結果を後ろに付けるため、再帰する(関数1回目の呼び出し)
2. GetNthRecordで2レコード目を取得して、結果を返す。(関数2回目の呼び出し)
3. 関数1回目の呼び出しが、2回目の結果を元に、結果を返す。
となります。
つまり、関数1回目の呼び出しは、再帰する全ての処理が終わらないと終わらないんです。
で、この時は上限が10,000回まで。
末尾最適化を行うには、
1. 関数1回目の呼び出しで結果を引数だったりグローバル変数に入れて再帰する。
2. 2回目の呼び出しのときに、1回目の内容を含めて結果を返す。
ようにすると、関数1回目は2回目以降の処理を待たずに終われるようになり、
上限が50,000回になるそうです。
サンプル
このことを踏まえ、以下のようにしてみました。
- 1回ずつ終了するように、結果をグローバル変数に入れていく。
- 最後にグローバル変数の内容を返す。
- 使ったグローバル変数のクリアを忘れずに!
ついでに元の処理を見て思った、
- 引数に必ず1を入れなくてもよくない?
- 関数の中で「Get ( 対象レコード数 )」が再帰する度に呼ばれているのが気になる。。。
というのも対応してみました。
/*==============================================================
名 称:GetFieldValues ( Field )
概 要:現在の対象レコードのフィールドをList関数のように改行区切りで取得する。
引数:
Field: 改行区切りで取得したいフィールド
計算式例:GetFieldValues( Current::Field )
備考 :このバージョンは末尾最適化を行っているので、制限50000回
=============================================================*/
Case($$#Cnt = "" ;
//初回は初期値をセット
Let( [$$#Cnt = 1 ;
$$#RecodeNum = Get ( 対象レコード数 ) ;
$$#Result = ""
];
GetFieldValues( Field )
) ;
//最終条件
$$#Cnt > $$#RecodeNum ;
//最終処理
Let( //使用したグローバル変数をクリアする
[$$#Cnt = "" ;
$$#RecodeNum = "";
#Result = Left($$#Result ; Length($$#Result) - 1) ;
$$#Result = ""
];
#Result
) ;
//一覧取得処理
Let( [ $$#Result = $$#Result & GetNthRecord ( Field ; $$#Cnt ) &"¶" ;
$$#Cnt = $$#Cnt + 1
];
GetFieldValues( Field )
)
)
#はLet関数内で使用する変数名の先頭に付けています。
$$#は、関数内で使用するグローバル変数名の先頭に付けています。
注意としては、グローバル変数を指定するときに、スクリプトなどで使用している
変数名を使用するとクリアされてしまうので、使用しない名前で定義してください。
まとめ
上記カスタム関数で、無事に一万数十件のフィールドの内容が取得できました。
最後かけ足でしたが、いかがでしょうか??
サンプルは、初回と最終条件の時はGetNthRecordしていないので、
実際は49,998レコードが限界かと思いますし、
(ただ、そこまでの件数だとなかなか返ってこないです。。。)
恐らくもっといい書き方があると思うので、
ご指摘頂けるとありがたいです!
P.S. 初Qiita投稿&初Advent Calendarでした!
文章で書くのは難しいけど楽しかった!