GASでスプレッドシートの指定列を基準にソート(並び替え)する方法

GAS

※本記事はプロモーションを含みます

目次

Googleフォームの送信連携や外部APIからの定期データ取り込みや複数メンバーによる日々の手動入力があるので、スプレッドシートにデータが次々と追記されていきます。なので、シート上の行が時系列やID順でバラバラになってしまう場面が頻繁に発生します。業務の締め処理や定期レポート作成の前には、特定の列をキーにして行全体を自動で整列させておきたいという要望が非常に多いです。今回は、GASでスプレッドシートの指定列を基準にソートを行う際の実践的なアプローチを説明します。ただし、スクリプト実行時にデータが予期せぬ挙動を示さないための注意点も一緒に整理しましょう。

データが随時追加されるシートで行を整理したい場面

スプレッドシートを行単位で並び替える作業は手動で行えます。というのは、手動ならメニューバーから対象列を選んでソート操作を行うだけだからです。ただし、データ連携バッチやトリガー実行による自動処理の中に組み込む場合は、スクリプト側で明示的にソート処理を実行する必要があります。特に、受発注管理シートで注文日時順に並べ直したいケースがあります。なので、タスク管理シートでステータス列や優先度列を基準にして上から順に処理対象を並べたいケースも存在します。あるいは、顧客マスタで会員番号を昇順に整頓したいケースもあります。だからこそ、GAS シート ソート 列基準 の指定による自動化が求められる状況は多岐にわたります。

手動運用を残していると、誰かが新しい行を末尾に追記したあとにソートを忘れてしまいます。なので、後続の集計関数や別シートへの参照が狂ったり未処理タスクが埋もれたりする人的ミスが起こりがちです。トリガーを用いた定期実行スクリプトの末尾やデータ書き込み処理の完了直後にソート処理を1行挟んでおきましょう。というのは、処理を挟むことでシートを常に整然とした状態に保てるからです。ただし、スプレッドシートのソートには「シート全体を対象とするソート」と「特定のセル範囲(Range)のみを対象とするソート」の2種類が存在します。なので、目的に合致したアプローチを選択しないと、タイトル行がデータ行の中に紛れ込んでしまうなどのトラブルに繋がります。

シート全体を直接並び替えるsheet.sortの基本記述

スプレッドシート上の行全体を特定の列をキーにして並び替える最も簡潔な記述は、Sheetオブジェクトが持っているsortメソッドを使用する方法です。というのは、このメソッドが指定した列のインデックス番号と昇順か降順かを示すブール値を渡すだけで全データ行をダイレクトに再配置してくれるからです。

以下に、アクティブなシートの1列目(A列)を基準として、シート全体の行を昇順に並び替えるコードを示します。

function sortCurrentSheetByColumn() {
  const sheet = SpreadsheetApp.getActiveSheet();
  
  // 1列目(A列)を基準に昇順(true)でシート全体をソート
  sheet.sort(1, true);
}

このコードを実行すると、シート内の全行がA列の値に従って小さい順に並び替わります。ただし、第1引数には基準としたい列番号を1始まりの整数値で指定します。A列なら1でB列なら2でC列なら3となります。JavaScriptの配列インデックスのように0始まりではありません。なので、列番号の指定ミスには注意しましょう。第2引数にはソート順をブール値で指定します。ただし、trueを指定した場合は昇順となり、falseを指定した場合は降順となります。

この記述の最大のメリットは、データが何行何列あるのかを事前に取得してRangeオブジェクトを生成する手間が一切かからない点にあります。というのは、シート全体に対して一気にソート命令が発行されるからです。なので、コードが非常にシンプルになり、実行速度面でも軽量です。単一のテーブルのみが配置されているシートや蓄積されたログデータを日付順に素早く並べ直したいだけの場合には、このsheet.sortが最も手軽で実用的な選択肢になります。

ヘッダー行がデータと一緒に巻き込まれてしまう現象と回避策

sheet.sortメソッドを利用する際に最も直面しやすい落とし穴は、1行目に設置しているヘッダーがデータ行の一部とみなされる問題です。なので、値の大小比較に巻き込まれてシートの中腹や末尾に移動してしまいます。手動でスプレッドシートのメニューからソートを行う場合は、ヘッダーとして認識されている行が自動的に除外されることがあります。ただし、GASのsheet.sortメソッドは容赦なくシート全体の全行を対象として並び替えを実行します。

例えば、1列目が「ID」という見出しでデータ行に「1001」「1002」といった数値が入っている場合があります。降順ソートをかけると文字列としての「ID」が数値よりも大きいと判定されます。なので、最上部に残ることがあります。ただし、昇順ソートを実行すると「ID」という文字列が数値行のずっと後ろに追いやられます。というのは、文字列が数値より大きいと扱われるからです。だから、見出し行がデータの最下部に沈んでしまうという現象が起こります。これが発生すると、人間が見たときにどの列が何を表しているのか分からなくなります。なので、ヘッダー行の文字列を検索して列番号を特定するような他の自動化ツールが即座に停止してしまいます。

このヘッダー巻き込み現象を回避するための確実な方法は2通りあります。1つ目は、スプレッドシートの「行の固定(フリーズ)」機能を利用することです。GASのsheet.sortメソッドには、固定された行(Frozen Rows)はソート対象から自動的に除外されるという重要な仕様が存在します。なので、スクリプト内でソートを実行する前に1行目を固定しておきましょう。あるいは、事前にスプレッドシート側で手動設定しておきましょう。というのは、事前設定によってヘッダー行を動かさずにデータ行だけを並び替えられるからです。

function sortSheetKeepHeader() {
  const sheet = SpreadsheetApp.getActiveSheet();
  
  // 1行目が固定されていない場合は固定行として設定
  if (sheet.getFrozenRows() === 0) {
    sheet.setFrozenRows(1);
  }
  
  // 1列目(A列)を基準に昇順ソート(固定された1行目は除外される)
  sheet.sort(1, true);
}

このようにsetFrozenRows(1)を併用しましょう。あるいは、運用上あらかじめ1行目を固定行にしておきましょう。というのは、設定をしておけばsheet.sortを呼び出してもヘッダーが移動しなくなるからです。複数行にわたる見出しがある場合は、その行数分を固定しておきましょう。なので、その全行がソート対象外として保護されます。

Rangeに対するソート処理へ切り替えるべきケース

シート全体をソートするsheet.sortは非常に簡潔です。ただし、シートのレイアウトによっては適用できない場合があります。典型的な例は、シートの上部にサマリー情報や集計用のカードが配置されているケースです。あるいは、シートの右側に別のメモ用テーブルが併設されているケースです。sheet.sortを実行すると、シートに存在するすべての列とすべての非固定行が同時に並び替わります。なので、無関係な集計エリアやメモ欄まで行単位でシャッフルされて崩壊してしまいます。

このような複数の独立したブロックが存在するシートでは、Range.sortを使う方針へ切り替えましょう。あるいは、特定のデータテーブル領域だけを安全に並び替えたい場合も、シート全体ではなくRangeオブジェクトを取得してRange.sortメソッドを使用する方針に切り替えましょう。というのは、範囲指定をすれば対象外のブロックを破壊せずに済むからです。

function sortSpecificRange() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const startRow = 2; // ヘッダーを除いた2行目から
  const startCol = 1;
  const numRows = sheet.getLastRow() - startRow + 1;
  const numCols = sheet.getLastColumn();
  
  if (numRows <= 0) return;
  
  // データが存在する矩形範囲のみを取得
  const range = sheet.getRange(startRow, startCol, numRows, numCols);
  
  // 範囲内の1列目(シート全体から見て1列目)を基準に昇順ソート
  range.sort({ column: 1, ascending: true });
}

Range.sortを使用する場合、引数の渡し方がsheet.sortとは異なります。なので、オブジェクト形式で指定するのが一般的です。Range.sortを使うことで、シートの上部に数行の固定見出しや集計欄があったとしても完全に無視できます。だからこそ、データ本体の範囲だけを安全にソートできます。どちらの手法を採用するかはシートの設計思想によります。ただし、1シート=1テーブルの純粋なデータ蓄積シートであれば「固定行+sheet.sort」が最もコード量が少なくメンテナンス性に優れています。なので、画面としてのレイアウト要素が混在しているシートであれば「Range.sort」を選択しましょう。

結合セルや数式を含むシートで発生する予期せぬズレへの対策

ソート対象のシート内に結合セルや相対参照を含む数式が存在する場合があります。なので、スクリプト実行時にエラーが発生したりデータの整合性が破綻したりするリスクがあります。これらの問題はコード自体の文法エラーではなくスプレッドシートのデータ構造に起因します。だから、あらかじめ性質を把握しておきましょう。

まず結合セルについて説明します。ソート対象となる行の内部に縦方向のセル結合が含まれている場合、sheet.sortを実行した瞬間にスクリプトが例外を投げて異常終了します。あるいは、結合が強制的に解除されて意図しない配置になってしまいます。横方向の結合であれば行単位のソートで崩れないこともあります。ただし、原則としてGAS シート ソート 列基準 を用いて機械的に並び替えるシートにおいてはデータ行の内部にセル結合を残さないテーブル設計を徹底しましょう。というのは、結合セルがエラーの直接的な原因になるからです。もし表示上の見栄えを整えるために結合を行っている場合は、ソート処理を行うデータ蓄積用シートと表示専用の閲覧シートを分離しましょう。

次に注意すべきなのは数式と相対参照のズレです。各行の計算列に「=A2*B2」のような相対参照の計算式が入っている場合、行がソートされて上下に移動します。なので、参照先のセル番号が自動で追従して正しく維持される場合と参照関係が交差して崩れてしまう場合があります。特に、ソート対象範囲外のセルを参照しているケースがあります。あるいは、OFFSET関数やINDEX関数など行位置に依存する数式を埋め込んでいるケースもあります。だから、並び替えの瞬間に意図しない行の値を参照してしまい計算結果が狂う原因になります。ソートを頻繁に行うデータテーブルでは数式を極力シンプルにしましょう。あるいは、ArrayFormulaを用いてヘッダーから一括展開する設計にしておきましょう。というのは、一括展開にしておけば行の入れ替わりによる数式破損を回避できるからです。

最後に、基準列に含まれる空白セルの扱いです。GASのソート処理において、空白セルは昇順ソートの場合は末尾に配置されます。なので、降順ソートの場合でも通常は最下部に送られます。ただし、データの途中に完全に空の行が紛れ込んでいると、データ全体が空白行によって分断されたり意図した順序と異なるグループ化が起きたりすることがあります。追記処理のバグなどで空白行が生じる可能性がある場合は、ソートを走らせる前に空白行の削除処理を挟みましょう。あるいは、データが途切れない運用ルールを設けておきましょう。というのは、ルールを決めておくことが長期間安定して稼働するスクリプトを構築するためのポイントだからです。

同じシート操作の続きは、スプレッドシートの表を、指定列で並べ替える に書いてあります。

タイトルとURLをコピーしました