複数セルの高速処理(getValues/setValues)

複数セルの高速処理(getValues-setValues)を図解する男性
目次

業務自動化における処理速度の壁とAPIコールの本質的理解

前章では、単一のセルに対して値を読み書きする方法を学びました。これはプログラミングの基礎であり、直感的に理解しやすい操作ですが、実務で扱うデータ量が数百、数千行に増えた途端に、致命的なパフォーマンスの問題に直面することになります。例えば、1000行ある顧客リストのデータを一行ずつ読み取り、加工して書き込む処理を実行したとします。もし1セルあたりの処理に0.2秒かかるとすれば、1000行で200秒、つまり3分以上も待たされることになります。さらにデータが増えれば、Google Apps Script(GAS)の実行時間制限である6分(360秒)の壁に突き当たり、処理が途中で強制終了してしまう「タイムアウトエラー」が発生します。なぜこれほど時間がかかるのでしょうか。それは、GASのスクリプトが動いているサーバーと、スプレッドシートのデータが保管されているサーバーが物理的に離れているためです。getValue()setValue()といったメソッドを実行するたびに、インターネットを経由して「データをください」「データを書き込んで」という通信(APIコール)が発生します。これを1000回繰り返すということは、1000回の往復通信を行うことを意味し、その通信待ち時間の積み重ねが処理の遅延を生み出しています。

真の開発者として効率的なシステムを構築するためには、この通信回数を極限まで減らす設計思想が必要です。具体的には、「1000回の通信で1個ずつ荷物を運ぶ」のではなく、「1回の通信でトラックに1000個の荷物を積んで運ぶ」というアプローチをとります。GASにおいてこれを実現するのが、今回学ぶgetValues()(複数形のsがつく)とsetValues()メソッドです。これらを使用することで、範囲指定したすべてのデータを一回の通信で配列として取得し、メモリ上で高速に処理を行い、最後に一回の通信でまとめて書き戻すことが可能になります。この手法を取り入れるだけで、数分かかっていた処理がわずか数秒で完了することも珍しくありません。本章では、この劇的な速度向上を実現するための技術と、それを扱うためのデータ構造について深く掘り下げていきます。

単数形と複数形の違いが生む劇的なパフォーマンス改善

GASのメソッドには、非常によく似た名前でありながら、その挙動と返却値が大きく異なるものが存在します。それが単数形のgetValue/setValueと、複数形のgetValues/setValuesです。初心者のうちは、単に「一度にたくさん取れる便利な機能」程度の認識で使ってしまいがちですが、開発者としてはその内部的なデータ型の違いを明確に理解しておく必要があります。getValue()は、単一のセルを対象とし、そのセルに入っている値そのもの(文字列、数値、日付など)を返します。一方、getValues()は、指定された範囲(Range)に含まれるすべてのセルの値を、「二次元配列」という特殊な構造で返します。書き込みを行うsetValues()も同様に、書き込みたいデータを二次元配列の形式で用意する必要があります。

この「配列でやり取りする」という点が、Excel VBAの経験者にとっても、プログラミング未経験者にとっても、最初の大きな躓きポイントとなります。VBAではセル範囲をオブジェクトとして扱い、ループ処理の中でセルプロパティを直接操作してもそれなりの速度が出ますが、クラウドベースのGASではその手法は通用しません。GASにおける高速化の鉄則は、「スプレッドシート(ディスク)へのアクセスを最小限にし、可能な限りGAS(メモリ)上で計算を完結させること」です。getValues()を使うということは、スプレッドシート上のデータを一旦すべてGAS側のメモリ空間にコピーすることを意味します。メモリ上でのデータの読み書きは、ネットワーク通信を介さないため、比較にならないほど高速です。つまり、getValues()でデータをメモリに吸い上げ、プログラム内部で計算や加工を済ませ、最後にsetValues()で結果を一気に書き戻す。この「入力→メモリ処理→出力」というサンドイッチ構造こそが、GAS開発における最も重要なデザインパターンの一つなのです。この構造を習得することで、あなたは「動くだけのスクリプト」から「実務に耐えうる高速なシステム」を作る開発者へとステップアップできます。

二次元配列の概念的理解とスプレッドシートとの対応関係

getValues()を使用する上で避けて通れないのが、「二次元配列」というデータ構造の理解です。配列とは、複数のデータを一つの箱にまとめて管理する仕組みですが、二次元配列は「配列の中に配列が入っている」入れ子構造になっています。言葉だけではイメージしづらいため、スプレッドシートの見た目とリンクさせて考えましょう。スプレッドシートは「行(横の並び)」と「列(縦の並び)」で構成されたグリッド状のデータです。二次元配列もこれと全く同じ構造をプログラムの世界で表現したものです。

具体的にイメージしてください。スプレッドシートの1行分のデータ(A列、B列、C列…)が、一つの配列(一次元配列)になります。そして、その「1行分の配列」が、行の数だけ集まって大きな配列に格納されている状態が二次元配列です。これをテキストで表現すると以下のようになります。

[ [1行目A列の値, 1行目B列の値, 1行目C列の値], [2行目A列の値, 2行目B列の値, 2行目C列の値], [3行目A列の値, 3行目B列の値, 3行目C列の値] ]

全体を囲む大括弧 [ ] がスプレッドシート全体(指定範囲全体)を表し、その中にある内側の大括弧 [ ] が各行を表しています。そして、カンマ , で区切られた中身が各セルの値です。この構造を頭の中で変換できるようになることが、GASを使いこなすための必須条件です。例えば、AIに「データを二次元配列で作成して」と指示した場合、AIはこの形式でコードを生成します。データの取得結果がどのような形になっているかを理解していなければ、AIが生成したコードの中身を検証することも、微修正することもできません。スプレッドシートの「見た目」と、プログラム上の「データ構造」がイコールで結ばれた時、あなたは自由自在にデータを操れるようになります。

二次元配列におけるインデックス操作と座標の特定

二次元配列に格納されたデータから、特定の値を取り出す方法を学びましょう。前章までで、スプレッドシートのセル番地は「行番号」と「列番号」で管理されていることを学びました(例:getRange(1, 1))。しかし、配列の世界では「インデックス(添字)」という番号で管理され、このインデックスは「0」から始まります。これが非常に混乱しやすいポイントです。スプレッドシートの「1行目」は、配列の世界では「0番目の要素」となります。「2行目」は「1番目の要素」です。列も同様に、A列(1列目)は「0番目」、B列(2列目)は「1番目」となります。

例えば、data という変数に二次元配列が格納されているとします。この中から、スプレッドシートでいう「2行目のC列(3列目)」の値を取得したい場合、プログラムでは data と記述します。最初の が「行のインデックス(2行目マイナス1)」を表し、次の が「列のインデックス(3列目マイナス1)」を表しています。 data[行インデックス][列インデックス] という構文です。

もし data と書けば、それは範囲内の左上のセル(1行目1列目)の値を指します。この「0から始まる」というルールは、プログラミングの世界では絶対的なルールです。スプレッドシートの行番号(1始まり)と配列のインデックス(0始まり)の間には、常に「マイナス1」の関係があることを意識してください。開発中に「データが1行ずれて取得される」「意図しない列が書き換わった」というバグが発生した場合、その原因の9割はこのインデックスの数え間違いにあります。AIにコードを書かせる際も、AIは基本的に配列処理(0始まり)でコードを生成するため、スプレッドシートの行番号と混同しないよう、コードを読む目を養う必要があります。

AIを活用した一括処理コードの生成とプロンプトエンジニアリング

理屈を理解したところで、実際にAI(生成AI)を使って、複数セルを高速処理するコードを作成してみましょう。ここでは、単に「処理して」と頼むのではなく、「高速化のために配列処理を行ってください」という意図を明確に伝えるプロンプトを作成します。これにより、AIはセルごとに setValue を繰り返す非効率なコードではなく、メモリ上で計算して setValues で一括書き込みを行う、プロフェッショナルなコードを生成します。

以下のプロンプトをAIに入力してみてください。

「あなたはGoogle Apps Scriptのエキスパートです。 現在開いているシートのA列(1行目からデータあり)にある数値をすべて取得し、 それぞれの数値を2倍にした計算結果を、隣のB列に一括で書き込む関数を作成してください。 【要件】 ・処理速度を考慮し、getValues()setValues()を使用して、APIコールを最小限に抑えてください。 ・ループ処理内ではスプレッドシートへの書き込みを行わず、配列上でデータを操作してください。 ・最終行を自動的に判定して範囲を指定してください。 ・各行に詳細なコメントを入れてください。」

このプロンプトのポイントは、「APIコールを最小限に抑える」「配列上でデータを操作する」と明示している点です。これにより、AIはあなたがパフォーマンスを重視する開発者であることを認識し、forループやmapメソッドを使ってメモリ内で計算を行い、最後にまとめて出力するコードを提示してくれます。生成されたコードを確認し、sheet.getRange(row, col).setValue() ではなく、配列操作が行われていることを確認してください。

Rangeオブジェクトの範囲指定とgetValuesの挙動

AIが生成したコードの中で、特に注目すべきは getRange() メソッドの引数です。単一セルの場合は getRange("A1")getRange(1, 1) で済みましたが、複数範囲を取得する場合は4つの引数を持つ getRange(開始行, 開始列, 行数, 列数) が使われます。例えば、A列のデータが100行ある場合、sheet.getRange(1, 1, 100, 1) と指定することで、A1からA100までの範囲オブジェクトを取得できます。

ここで重要なのが、第3引数(行数)と第4引数(列数)の指定です。データが何行あるか分からない場合、手動で「100」と書くわけにはいきません。そこで登場するのが getLastRow() メソッドです。const lastRow = sheet.getLastRow(); のように最終行数を取得し、sheet.getRange(1, 1, lastRow, 1) とすることで、データが増減しても自動的に範囲が調整される動的なコードになります。

getValues() は、この指定された範囲の形状そのままにデータを配列化します。もし getRange(1, 1, 100, 1) (縦長の範囲)を指定した場合、返ってくる配列は [,,, ...] のように、各要素の中に要素が1つだけ入った二次元配列になります。逆に getRange(1, 1, 1, 5) (横長の範囲)を指定した場合は、[] のように、1つの要素の中に5つの値が入った二次元配列になります。取得したい範囲の形と、返ってくる配列の構造が密接に関係していることを理解しておくと、配列からデータを取り出す際のミスが激減します。

メモリ上でのデータ加工処理と二次元配列の操作

getValues() で取得したデータ(二次元配列)は、変数に格納されています。ここからはスプレッドシートとは関係のない、純粋なJavaScriptの配列操作の世界になります。AIが生成したコードでは、多くの場合 for 文や map メソッドを使ってこの配列をループ処理しています。

例えば、A列の値を2倍にする処理であれば、配列の各要素(各行)に対してアクセスし、その中の0番目の要素(A列の値)を取り出して2倍にし、新しい配列に格納していくという手順を踏みます。ここで注意が必要なのは、最終的に setValues() で書き戻すためのデータも、必ず「二次元配列」の形に整形しておかなければならないという点です。

単なる数値のリスト (一次元配列)を作成してしまうと、スプレッドシートはこれを「横一列のデータ」として認識してしまい、縦方向に書き込むことができません。縦方向に書き込みたい場合は、[,,,] のように、各数値をさらに大括弧で囲み、縦並びの構造を持った二次元配列を作成する必要があります。AIにコードを書かせる際も、「出力用の配列は二次元配列として構成してください」と意識しておくだけで、エラーの発生を防ぐことができます。この「データの形状を合わせる」という感覚は、GAS開発において非常に重要です。

setValuesによる一括書き込みとエラーの回避

メモリ上で加工したデータをスプレッドシートに戻すのが setValues() メソッドです。このメソッドは非常に強力ですが、同時に非常に厳格でもあります。setValues(配列データ) を実行する際、書き込み先の Range(範囲)の大きさと、渡す 配列データ の大きさが、縦横ともに完全に一致していなければなりません。

例えば、100行1列のデータを書き込みたいのに、指定した範囲が getRange(1, 2, 50, 1) (50行分)だった場合、「範囲のサイズとデータのサイズが一致しません」というエラーが発生して止まります。また、配列の中身が [,] (2行1列)なのに、範囲が getRange(1, 2, 2, 2) (2行2列)だった場合もエラーになります。

このエラーを防ぐためのベストプラクティスは、書き込み先の範囲指定にも配列の length プロパティを使用することです。sheet.getRange(1, 2, outputData.length, outputData.length).setValues(outputData); のように記述します。outputData.length は配列の行数(縦の長さ)、outputData.length は配列の列数(横の長さ)を自動的に返してくれます。これにより、加工後のデータが何行何列になっても、自動的にピッタリのサイズの範囲が確保され、エラーなく書き込みが行われます。AIにコード生成を依頼する場合も、このような動的な範囲指定を行っているか確認することが、開発者としてのチェックポイントになります。

処理速度の計測とパフォーマンスの違いの体感

実際に getValues/setValues を使ったコードを実行したら、以前の単一セル処理と比較してどれくらい速くなったかを体感してみましょう。データ量が少ない(数十行程度)場合は一瞬で終わってしまい違いが分からないかもしれませんが、1000行、5000行とデータを増やしていくと、その差は歴然とします。

開発者として、客観的に速度を計測する方法も知っておくと便利です。console.time('処理名')console.timeEnd('処理名') というコードで、計測したい処理を挟むことで、実行ログに「処理名: 154ms」のように所要時間を表示させることができます。AIに対して「処理時間を計測するコードも追加してください」と指示すれば、簡単に実装してくれます。

「1セルずつ書き込んだ場合は300秒かかった処理が、一括処理なら0.5秒で終わった」というような劇的な改善を目の当たりにすることで、配列処理の重要性が腹落ちするはずです。この高速化技術は、単なる時短テクニックではなく、システムリソースを節約し、他の処理への悪影響(スプレッドシートの同時編集ロックなど)を防ぐための必須マナーでもあります。

スケーラビリティを考慮した開発者としての視点

本章で学んだ「配列による一括処理」は、GAS開発において最も基本的かつ効果的なパフォーマンス・チューニングの手法です。しかし、これは単に「速くする」ためだけの技術ではありません。将来的にデータ量が増加しても安定して稼働し続ける「スケーラビリティ(拡張性)」を確保するための技術です。

ビジネスの現場では、データは日々増え続けます。最初は10行だった管理表が、1年後には1万行になっていることは珍しくありません。もし getValue をループするコードを書いていたら、ある日突然「タイムアウトエラー」でシステムが止まり、業務が停止してしまいます。開発者であるあなたは、そのような未来を予見し、今のうちから「データが増えても耐えられる構造」でコードを書いておく責任があります。

AIを使えば、複雑な配列操作のコードも簡単に生成できます。しかし、「なぜ配列を使うのか」「どのような構造でデータを渡すべきか」を理解していなければ、AIに正しい指示を出すことはできませんし、トラブルシューティングもできません。今回学んだ getValues/setValues の概念は、スプレッドシート操作だけでなく、今後学ぶ外部API連携やデータベース操作など、あらゆる大量データ処理に通じる基礎となります。一括処理の思考回路(GAS脳)を定着させ、次のステップである「変数」の理解へと進んでいきましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次