Office Scriptsで大規模データを読み取る|一括取得で落ちる原因とバッチ読み取り

Office Scriptsで大規模データを読み取る|一括取得で落ちる原因とバッチ読み取り

Office Scriptsで数万行のExcelシートを読み込もうとして、「テスト環境では動くのに本番データで止まる」「コードを書き直しても同じところで落ちる」という状況に当たっていないでしょうか。

原因はOffice Scriptsのデータ量上限です。

1回のgetValues()で取得できるセル数は最大500万セル、返せるデータサイズは最大5MBで、この上限を超えた時点でスクリプトが停止します。

この記事では、一括取得で落ちるパターンと、それを回避するバッチ読み取りの設計をコード付きで整理しています。

Office Scripts設計を無料相談する

結論:バッチで1万行ずつ読む

シートを1万行ずつ分割して読み込めば、データ量の上限に引っかからずに処理できます。

バッチ読み取りの実行サイクルを示すフロー図。最終行取得→1万行ずつ取得→行ごとに判定→次のバッチへの4ステップで、全行完了まで繰り返すループ構造
function main(workbook: ExcelScript.Workbook): string[] {
  const sheet = workbook.getActiveWorksheet();
  const BATCH_SIZE = 10000;   // 1回に読む行数
  const COLUMN_COUNT = 26;    // 読み取る列数(実データに合わせる)

  const used = sheet.getUsedRange(true);
  if (!used) { return []; }   // 空シートのときは undefined が返る
  const lastRow = used.getRowCount();

  const hits: string[] = [];
  for (let start = 0; start < lastRow; start += BATCH_SIZE) {
    const rowCount = Math.min(BATCH_SIZE, lastRow - start);
    const batch = sheet
      .getRangeByIndexes(start, 0, rowCount, COLUMN_COUNT)
      .getValues();

    for (const row of batch) {
      if (String(row[0]).includes("申請")) {
        hits.push(String(row[1]));
      }
    }
  }

  return hits;
}

このコードが何をしているか

シートを上から1万行ずつ読み進め、その1万行の中の各行が条件に合うかを確かめ、合った行の値だけを集めて、最後にまとめて返します。

中心にある for 文が、この『1万行ずつ』を先頭から最後まで繰り返す部分です。

start を1万ずつ増やしながら、シートの最後の行にたどり着くまで読み進めます。

読み終わった1万行は、内側の for でさらに1行ずつ見て、条件に合えば hits に貯めていきます。

自分のデータに合わせて変える場所

書き換えるのは、三か所だけです。

  • 読み取る列数(COLUMN_COUNT) 取得したいデータが何列目まであるかに合わせます。
    A~Zの26列ぶんを読むなら、26のままで構いません。
  • 絞り込みの条件(if の中) row[0]0 が『見る列』、"申請" が『探す文字』です。
    たとえばB列で『済』という文字を探すなら、row[1]"済" に書き換えます。
  • 取り出す値(hits.push の中) row[1]1 が『結果として持ち帰る列』です。
    別の列の値がほしいときは、この番号を変えます。

列の番号が0から始まる点には注意してください。

1列目が row[0]、2列目が row[1] になります。

残る BATCH_SIZE は1回に読む行数で、これは1万のままで問題ありません。

なぜ1万行ずつに区切るのかというと、Office Scriptsには一度に扱えるデータ量の上限があるからです。

上限は二つあり、1回のgetValues()で取得できるセル数は最大500万セル、返せるデータサイズは最大5MBです。

数万行を一度に読もうとすると、この上限と、次に説明する『書式の罠』に引っかかります。

だから読み取りを小分けにして、1回あたりの量を上限の内側に収めます。

Office Scriptsの一括読み取りが落ちる原因

一括取得のコードには罠が二つあります。

getUsedRange()の挙動と、500万セルの上限です。

どちらもテストデータの規模では表面化しないため、本番で初めて当たります。

失敗①:getUsedRangeで一括取得する

最も素直な書き方はこうです。

function main(workbook: ExcelScript.Workbook) {
  const sheet = workbook.getActiveWorksheet();
  const values = sheet.getUsedRange().getValues();
}

この書き方には、罠が二つあります。

一つ目は、getUsedRange() が『値の入ったセル』だけを返さないことです。

このメソッドが返すのは、値または書式が設定されたセルを囲む最小の範囲だからです。

たとえ実データが3行でも、書式だけが1万行目まで設定されていれば、範囲は1万行に広がります。

その結果、空の行を9,997行も無駄に読み込んでしまいます。

これは getUsedRange(true) と引数を付ければ、値の入ったセルだけに絞れます。

二つ目は、範囲を正しても、規模そのものの問題が残ることです。

というのも、1回の getValues() で読めるのは最大500万セルまでだからです。

500万と聞くと大きく感じますが、書式の罠で範囲がふくらむと、数万行でも容易に超えます。

仮に書式の罠を消しても、データが本当に大きければ、やはりこの上限で落ちます。

失敗②:全行を配列に抱えたまま処理する

書式の罠を回避してgetUsedRange(true)にしても、全行を配列に持ったまま処理する構成では別の壁に当たります。

function main(workbook: ExcelScript.Workbook) {
  const sheet = workbook.getActiveWorksheet();
  const rows = sheet.getUsedRange(true).getValues();
  for (const row of rows) {
    // 数万行ぶんをここで処理する
  }
}

データが大きければ読み取りの時点で500万セルの壁に当たり、そこまで大きくなくてもgetValues()の応答が5MBを超えれば止まります。

仮に読み取れたとしても、数万行ぶんの配列を一度に保持するため処理がどんどん重くなります。

読み取りとメモリ保持の両方で上限に引っかかる構成です。

一度に全部を読む方法はここで行き止まりです。

残るのは、範囲を小分けにして読む方法だけです。

列単位やクエリでの読み取りは使えるか?

範囲を小分けにする方法には行単位と列単位の二通りがあります。

冒頭で示したバッチ読み取りは行単位ですが、列単位やクエリ型にもそれぞれ問題があります。

列単位での取得

必要な列だけを読めば1回あたりの取得量は減ります。

10列ぶん必要なら、列を指定した読み取りを10回繰り返す形です。

ただし、あとから読みたい列が一つ増えるたびに読み取り回数とコードの両方を直す必要があり、拡張性が低くなります。

行単位で必要な範囲をまとめて取得し、欲しい列のインデックスだけをスクリプト内で抜き出すほうが変更に強くなります。

クエリでの絞り込み(Google Apps ScriptのQUERY関数との比較)

Google Apps Script(Googleスプレッドシートのスクリプト環境)には QUERY 関数があり、SQLに近い書き方でシート内の絞り込みを完結できます。

Office Scriptsにはこの仕組みがありません。

ExcelのワークシートにはFILTER関数やXLOOKUPがありますが、Office ScriptsのコードからSQLのようなクエリを発行する仕組みは用意されていません。

取得した配列にJavaScriptの .filter() をかける方法もありますが、一括取得と同じです。

.filter() は配列に対する処理なので、かける前に getValues() で全行をメモリへ読み込まなければなりません。

つまり失敗①・失敗②と同じ壁にぶつかり、絞り込みのコードをどれだけきれいに書いても読み込みの壁は越えられません。

補足:Office Scriptsの書き込みが遅い場合の対策

ここまでは読み取りの話でしたが、書き込みが絡むと同じ行数でも数倍の時間がかかります。

書き込みを速くするには、スクリプトの最初と最後で計算モードを切り替えて自動再計算を止めます。

workbook.getApplication().setCalculation(ExcelScript.CalculationMode.manual);
// 書き込み処理...
workbook.getApplication().setCalculation(ExcelScript.CalculationMode.automatic);

なお、このスクリプトをPower Automateから呼び出す場合は、同期実行が120秒で打ち切られる制限が加わります。

絞り込んだ結果だけを返す構成にしておけば、この制限にも当たりにくくなります。

バッチ読み取りの設計原則

Office Scriptsで大規模データを読むときの原則は、一度に全部を読もうとしないことです。

500万セルにせよ、getValues()の5MB応答上限にせよ、一括で読もうとすれば必ずどこかで落ちます。

最初から範囲を区切って読み、判定もスクリプト内で済ませる形を決めておけば、データが何万行に増えても処理が止まることはなくなります。


Office Scriptsでの業務自動化について相談してみませんか?

「バッチ設計に直したいがコードを書き直す時間がない」「本番のデータ量で動き切るか確信が持てない」「社内にOffice Scriptsを設計できる人材がいない」

少しでもお心当たりがあれば、お気軽にご相談ください。

現在、Office Scriptsを使った業務自動化のお悩みをお伺いする 無料の個別相談 を実施しています。


よくある質問

Q. バッチサイズは1万行でなければいけませんか?

データの列数が少なければ2万~5万行に増やせます。

1回のgetValues()が500万セル・5MBを超えなければ動作するため、「列数 × バッチサイズ」が500万を下回る範囲で調整してください。

Q. getUsedRange(true)を使えばバッチ読み取りは不要ですか?

書式の罠は回避できますが、データ自体が大きければ500万セルや5MBの上限で止まります。

大規模データを扱う場合はバッチ読み取りが必須です。

Q. Power Automateから呼び出す場合に注意点はありますか?

同期実行は120秒で打ち切られます。

バッチ読み取りで絞り込んだ結果だけを返す構成にしておけば、この制限にも当たりにくくなります。