スプレッドシートの表を、指定列で並べ替える

GAS

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

目次

日々の業務でGoogle Apps Script(GAS)を使ってスプレッドシートを操作していると、集計データや受発注リストを一定のルールで整理したくなる場面が頻繁にあります。特に外部システムからcsv形式で出力されたデータや、Googleフォーム経由で追加された回答一覧などは、送信された順番や不揃いなデータ順でシートに書き込まれます。なので、そのままでは集計作業や人間が閲覧するレポートとして使いものになりません。GASを使ってスプレッドシートの範囲を指定し、特定の列をキーにして並べ替えを自動化できれば、毎日の手作業によるソート作業から解放されます。なので、並べ替え忘れや操作ミスを根本から防ぐことができます。ただし、いざスクリプトを組んで運用を始めると、表の見出し行(ヘッダー)まで一緒に並べ替えられて表の途中に沈んでしまう問題が多発します。というのは、参照している数式のセルが壊れてエラーになったり、列番号の指定ミスによって意図しない列でソートされたりするからです。この記事では、指定した列を基準にスプレッドシートの表を整列させるためのコードを提示します。なので、実務で直面しやすいデータ崩壊や設定のズレを回避するための注意点について詳しく解説します。

2列目の値で昇順ソートをかける最小のコード

まず、対象となるシートのデータ範囲を指定し、特定の列をキーにして昇順で並べ替える処理のコードを示します。

const sheet = SpreadsheetApp.getActiveSheet();
const range = sheet.getRange("A2:D100");
range.sort({column: 2, ascending: true});

上記のコードは、現在アクティブになっているシートのA2セルからD100セルまでの範囲を取得します。なので、その中の2列目(B列)に入っている値を基準として昇順に並べ替える処理を行っています。GASで「GAS スプレッドシート 範囲 並べ替え」を実現する場合、操作の対象となるのはSheetオブジェクト全体ではありません。というのは、 getRange メソッドによって取得した Range オブジェクトだからです。その Range オブジェクトに対して sort メソッドを実行します。引数にはオブジェクト形式で、並べ替えのキーとなる列番号を column に、昇順(true)か降順(false)かを ascending に指定します。

このコードをいつ使うかというと、あらかじめデータが収まるセル範囲の上限が確定している定期レポートの整理などです。ただし、固定のフォーマットで運用されている明細シートの整理に使うこともあります。たとえば、B列に顧客コードや担当者名、日付などのキーデータが入っています。なので、それを毎朝自動的に五十音順や日付順に揃えたいといったケースで有効に機能します。

ただし、タイトルの状況である「スプレッドシートの表を、指定列で並べ替える」という処理をGASで実行する際、ただコードを書いただけで運用に乗せようとすると、さまざまなトラブルが発生します。最も頻繁に起きるのが画面上で人間が操作する感覚と、GASがプログラムとして実行する処理のギャップです。なので、そこから生じるデータ崩壊に注意しましょう。人間がスプレッドシートのメニューから「範囲を並べ替え」を実行する場合、無意識に見出し行を除外して選択します。ただし、選択範囲に見出しが含まれていることを示すチェックボックスをONにしたりして調整することもあります。一方、GASの sort メソッドは、渡された Range オブジェクトの領域内部を完全に均一なデータ行の集まりとして扱います。なので、範囲の指定方法や対象データの構造を正しく理解していないと、表の見た目が深刻なレベルで崩れてしまいます。

ヘッダー行を巻き込んで並べ替えてしまう現象と見落としがちな設定

タイトルの状況で処理を実行した際に、真っ先に遭遇する失敗が「見出し行(ヘッダー)までデータと一緒に並べ替えられてしまう」という現象です。たとえば、1行目に「日付」「担当者」「商品名」「売上額」といった見出しが書かれている表が存在します。なので、誤って `sheet.getRange(“A1:D100”)` のように1行目から範囲を取得して sort メソッドを実行してしまうケースが該当します。

GASの sort メソッドは、指定された範囲の1行目であっても他のデータ行と同等に並べ替えの対象として計算します。ただし、2列目(担当者名)で昇順ソートを行った場合、「担当者」という見出し文字列自体が文字コード上の順序に従って評価されます。というのは、データ行の途中や最下部に移動してしまうからです。出社してスプレッドシートを開いたときに、1行目にあるはずの見出しが表の真ん中に沈んでいることがあります。なので、元々1行目だった場所に別のデータ行が繰り上がっているという状態は、GASによる自動化を導入した直後に誰もが一度は踏む失敗です。

この現象を回避するための基本は、コード例で示したように `sheet.getRange(“A2:D100”)` のごとく、最初から見出し行である1行目を除外して Range を取得することです。ただし、実務ではデータ件数が日々増減します。なので、`”A2:D100″` のように範囲を文字列で固定指定する運用は危険を伴います。データが101行目以降に増えた場合に並べ替えの漏れが発生します。なので、逆にデータが5行しかない場合に100行目まで範囲を広げてソートすると、空白セルとの兼ね合いで挙動が不安定になることがあります。

そのため、実務ではシートの最終行を動的に取得する getLastRow メソッドを組み合わせて範囲を決定するのが一般的です。ただし、ここで留意すべき点は、最終行を取得した後の範囲組み立ての計算です。たとえば以下のように記述します。

`const lastRow = sheet.getLastRow();`
`if (lastRow > 1) {`
` const range = sheet.getRange(2, 1, lastRow – 1, 4);`
` range.sort({column: 2, ascending: true});`
`}`

ここで `getRange(2, 1, lastRow – 1, 4)` としている部分に注目してください。第1引数の「2」は開始行(2行目)を意味します。なので、第2引数の「1」は開始列(A列)を意味します。ただし、第3引数の「lastRow – 1」は取得する行数(全体の行数から見出し行の1行分を引いた数)を意味します。というのは、第4引数の「4」が取得する列数(A列からD列の4列分)を意味するからです。この「行数」の計算において `- 1` を忘れると、存在しない余分な空白行まで範囲に含まれてしまいます。なので、データが存在しない状態で実行した際にエラーが発生したりします。見出し行を確実に除外しつつ、データが存在する領域だけを動的に計算してソート対象にしましょう。

並べ替えでデータや書式がズレる・崩れるパターンの回避策

スプレッドシートの表を、指定列で並べ替える処理を自動化した際、見出しの巻き込み以外にも重篤なトラブルが発生することがあります。というのは、「データ行の一部だけがソートされて他の列とずれてしまう事故」や「数式セルによる参照先の破壊」が起きるからです。

まず、データ行の一部だけがソートされる問題について説明します。これは手作業でスプレッドシートを操作している時にも起きがちなミスです。なので、GASのコード内でも範囲指定の列数を間違えることで容易に発生します。たとえば、A列からD列までが一連の顧客データ(A列:ID、B列:氏名、C列:購入日、D列:金額)として紐付いている表があるとします。ここで、B列の氏名だけを並べ替えようとして、誤って `sheet.getRange(“B2:B100”)` という1列だけの範囲に対して sort メソッドを実行してしまうことがあります。ただし、この操作を行うとB列の氏名データだけが上下に入れ替わり、A列のIDやD列の金額との対応関係が完全に破綻します。一度この状態で保存・処理が進んでしまうと、誰がいくら支払ったのかという原番データが支離滅裂になります。なので、バックアップから復元しない限りデータの復旧が不可能になります。これを回避するためには、並べ替えを行う際は必ず「表全体の列を含んだ範囲」を取得しましょう。というのは、その範囲に対して「どの列をキーにしてソートするか(column指定)」を指示するという設計原則を徹底しなければならないからです。

次に深刻なのが、数式が入っている表での並べ替えです。表内に `=VLOOKUP(A2, マスタ!A:B, 2, FALSE)` や `=C2*1.1` といった相対参照の数式が含まれている場合があります。なので、 sort メソッドによって行の物理的な並び順が入れ替わると、数式内のセル参照が意図しない行を指すように自動更新されます。ただし、参照関係がねじれて `#REF!` エラーを引き起こしたりすることもあります。GASの sort メソッドは、セルの値だけを移動させるわけではありません。というのは、セルに設定されている数式や背景色、罫線などの書式情報も含めて行ごと移動させるからです。なので、相対参照で組み立てられた複雑な数式が存在するシートに対して直接 sort を実行するのは非常にリスクが高い行為です。

数式崩れを回避するための実務的なテクニックとしては、並べ替えを実行する前段階で、数式による計算結果を「値」として確定させておく処理をGAS側で挟む手法があります。具体的には、対象範囲のデータを getValues メソッドで2次元配列として取得します。なので、それをそのまま setValues メソッドで同じ範囲に書き戻すことで、数式を純粋なテキストや数値に変換してから sort を実行します。あるいは、GAS上でデータを並べ替えるのではなく、あらかじめGASの処理内で配列としてデータをソート(JavaScriptの `Array.prototype.sort()` を使用)する方法もあります。ただし、その場合は最終的な結果だけをシートに一括書き込みするという設計に切り替えることも有効な回避策となります。

複数条件による並べ替えや動的な列指定での実践的な処理

業務が複雑化してくると、「単一の列で並べ替える」だけでは要件を満たせないケースが増えてきます。たとえば、「まずは第1条件として『部署(A列)』ごとに昇順でまとめたい」という要望があります。なので、「同じ部署内では第2条件として『売上額(D列)』の多い順(降順)に並べ替えたい」といった複合的なソート要件が発生します。

GASの sort メソッドは、引数にオブジェクトの配列を渡すことができます。なので、複数条件による優先順位付きの並べ替えにも標準で対応しています。先ほどのコードを発展させて複数列の指定を行う場合は、以下のように記述します。

const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
if (lastRow > 1) {
  const range = sheet.getRange(2, 1, lastRow - 1, 4);
  range.sort([
    {column: 1, ascending: true},
    {column: 4, ascending: false}
  ]);
}

このように、配列の中に優先したい順にオブジェクトを並べます。なので、第1条件で同値だった行に対して第2条件のソート条件が適用されます。ただし、ここで注意しなければならないのは、GASの sort メソッドにおける `column` の指定は「1始まりの列番号(インデックス)」である点です。JavaScriptの一般的な配列インデックス(0始まり)に慣れているプログラマーがよく陥る罠が存在します。というのは、A列を0列目、B列を1列目と勘違いして `column: 0` と指定してしまうからです。なので、実行時エラー(Exception: The column index must be greater than 0.)が発生してしまいます。GASのスプレッドシート関連メソッドにおいて、列番号や行番号を指定する引数は基本的に「1から始まるシート上の位置」であることを強く意識しておきましょう。

さらに、実務運用においてもう一つ大きな壁となるのが「表のレイアウト変更」です。運用を続けているうちに、現場の都合でスプレッドシートに新しい列が挿入されることがあります。ただし、列の位置が入れ替えられたりすることも日常茶飯事です。もしコード内で `{column: 2}` のように列番号を直接数値でハードコーディングしていると、ある日突然B列にあった「担当者」がC列に移動します。なので、まったく関係のない別の列を基準にして並べ替えが実行されるようになります。

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