GAS(Google Apps Script)でシートの値を扱うときは、getValues で1回読み、二次元配列のまま加工し、setValues で1回書くのが基本です。 二次元配列とは「行の配列を、さらに配列に入れたもの」で、data[行][列] の形で値を取り出します。シートの表がそのまま入る形なので、map や filter といった JavaScript の配列メソッドで列の取り出しも行の絞り込みも1行で書けます。

この記事は配列の加工に絞り、やりたいことからコードを引ける早見表、範囲と配列の大きさを合わせる書き方、読み書きメソッドの選び方をまとめます。

確認環境(確認日 2026-10-06):配列を加工するコードは Node.js 22.22 で実行し、その出力を JSON 形式で載せています。シートを読み書きするメソッドの名前・引数・戻り値は、Google 公式の Apps Script リファレンスと型定義パッケージ @types/google-apps-script 2.0.13 で確認しました。呼び出し回数は、シートを二次元配列でまねた簡易モックで数えた値です。

この記事の結論

  • 読むのは1回、書くのも1回。その間の加工はすべて配列の上で行います。
  • 列を取り出すのは map、行を絞り込むのは filter、集計は reduce です。
  • 書き込む範囲は getRange(行, 列, data.length, data[0].length) で配列の大きさから決めます。
  • 1行ずつ足すなら appendRow、複数行なら setValues にまとめます。

GAS二次元配列の操作早見表

data はヘッダー行つきの二次元配列、rows はヘッダーを除いた部分(const [header, ...rows] = data;)です。

やりたいこと コード 結果の形
シートを全部読む sheet.getDataRange().getValues() 二次元配列
ヘッダーと本体に分ける const [header, ...rows] = data; 一次元と二次元
1列を取り出す(一次元) rows.map(r => r[1]) 一次元配列
1列を取り出す(書き込み用) rows.map(r => [r[1]]) 1列の二次元配列
複数の列を取り出す rows.map(r => [r[1], r[3]]) 二次元配列
行を絞り込む rows.filter(r => r[2] === '機器') 二次元配列
1行を探す rows.find(r => r[0] === 3) 一次元配列
行を追加する [...rows, [6, '付箋', '文具', 20, 90]] 二次元配列
列を追加する(計算列) rows.map(r => [...r, r[3] * r[4]]) 二次元配列
列を削除する rows.map(r => r.filter((_, i) => i !== 2)) 二次元配列
行と列を入れ替える(転置) data[0].map((_, c) => data.map(r => r[c])) 二次元配列
一次元を縦1列にする list.map(v => [v]) 1列の二次元配列
一次元を横1行にする [list] 1行の二次元配列
二次元を一次元にする data.flat() 一次元配列
ヘッダーをキーにオブジェクト化 rows.map(r => Object.fromEntries(header.map((h, i) => [h, r[i]]))) オブジェクトの配列
重複する行を除く Set でキーを覚えて filter(後述) 二次元配列
並べ替える(数値・降順) [...rows].sort((a, b) => b[4] - a[4]) 二次元配列
合計を求める rows.reduce((acc, r) => acc + r[3] * r[4], 0) 数値
まとめて書き込む sheet.getRange(1, 1, out.length, out[0].length).setValues(out) ―

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「GAS二次元配列の操作一覧|getValuesからmap・filterまで」

基本形:1回読んで、配列で加工して、1回書く

Google 公式のベストプラクティスは、スクリプトの中で済む JavaScript の処理は、シートなど他のサービスを呼び出すより速いとして、サービスの呼び出しを減らすことと、読み書きをまとめること(バッチ処理)を勧めています。同じページには、セルを1つずつ書く例が約70秒、配列にためて1回で書く例が約1秒という比較も載っています。

次の2つは、B列の単価に 1.1 を掛けて C列に書く処理です。

// セルを1つずつ読み書きする書き方
function updateOneByOne() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const last = sheet.getLastRow();
  for (let r = 1; r <= last; r++) {
    const price = sheet.getRange(r, 2).getValue();
    sheet.getRange(r, 3).setValue(Math.round(price * 1.1));
  }
}

// まとめて読み、配列で計算し、まとめて書く書き方
function updateInBatch() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const last = sheet.getLastRow();
  const values = sheet.getRange(1, 2, last, 1).getValues();
  const out = values.map(([price]) => [Math.round(price * 1.1)]);
  sheet.getRange(1, 3, out.length, out[0].length).setValues(out);
}

1,000行のデータを想定し、値を読むメソッドと書くメソッドの呼び出しを簡易モックで数えると、次のようになりました。

1セルずつ: 呼び出し 2001 回
まとめて  : 呼び出し 3 回
書き方 読み書きの呼び出し 行数が増えたとき
セルを1つずつ 行数 × 2 + 1 回 行数に比例して増える
まとめて 3 回 変わらない

行数が10倍になっても、まとめて読む書き方の呼び出しは3回のままです。処理時間の差は、ほぼこの呼び出し回数の差から生まれます。

サンプルの表と二次元配列の形

以降のコードは、次の表を getDataRange().getValues() で読んだ配列を使います。

ID 品名 分類 数量 単価
1 ノート 文具 12 180
2 ボールペン 文具 30 120
3 マウス 機器 5 1500
4 ノート 文具 12 180
5 キーボード 機器 3 4200
const data = [
  ['ID', '品名', '分類', '数量', '単価'],
  [1, 'ノート', '文具', 12, 180],
  [2, 'ボールペン', '文具', 30, 120],
  [3, 'マウス', '機器', 5, 1500],
  [4, 'ノート', '文具', 12, 180],
  [5, 'キーボード', '機器', 3, 4200],
];
const [header, ...rows] = data;
知りたいこと 書き方 この表での値
行数 data.length 6
列数 data[0].length 5
3行目・2列目の値 data[2][1] ボールペン

シートの行番号・列番号は1から、配列の添字は0から始まります。シートの3行目は data[2] です。この1つのずれが、最初につまずきやすい点です。

列を取り出す・行を絞り込む

rows.map(r => r[1]);                    // 品名の列を一次元で
rows.map(r => [r[1]]);                  // 品名の列を書き込み用の形で
rows.map(r => [r[1], r[3]]);            // 品名と数量だけ
rows.filter(r => r[2] === '機器');      // 分類が「機器」の行
rows.find(r => r[0] === 3);             // ID が 3 の行
["ノート","ボールペン","マウス","ノート","キーボード"]
[["ノート"],["ボールペン"],["マウス"],["ノート"],["キーボード"]]
[["ノート",12],["ボールペン",30],["マウス",5],["ノート",12],["キーボード",3]]
[[3,"マウス","機器",5,1500],[5,"キーボード","機器",3,4200]]
[3,"マウス","機器",5,1500]

1行目と2行目の違いが大切です。setValues に渡せるのは二次元配列だけなので、1列を書き戻すときは [r[1]] と角かっこで包み、「1列の二次元配列」にします。

行・列を追加する、削除する

const added = [...rows, [6, '付箋', '文具', 20, 90]];     // 行を末尾に追加
const withTotal = rows.map(r => [...r, r[3] * r[4]]);    // 金額の列を追加
const dropped = rows.map(r => r.filter((_, i) => i !== 2)); // 分類の列を削除
added.length → 6
withTotal → [[1,"ノート","文具",12,180,2160],[2,"ボールペン","文具",30,120,3600],[3,"マウス","機器",5,1500,7500],[4,"ノート","文具",12,180,2160],[5,"キーボード","機器",3,4200,12600]]
dropped → [[1,"ノート",12,180],[2,"ボールペン",30,120],[3,"マウス",5,1500],[4,"ノート",12,180],[5,"キーボード",3,4200]]

push や unshift で元の配列を直接書き換えることもできますが、スプレッド構文(...)で新しい配列を作ると、元のデータが残るので後から見直しやすくなります。

行と列を入れ替える(転置)

const transposed = data[0].map((_, c) => data.map(r => r[c]));
[["ID",1,2,3,4,5],["品名","ノート","ボールペン","マウス","ノート","キーボード"], …(省略)…]

外側の map が「列番号」を、内側の map が「各行のその列の値」を取り出します。6行5列の配列が5行6列になります。

一次元配列と二次元配列を変換する

const list = ['A', 'B', 'C'];
list.map(v => [v]);            // 縦1列に
[list];                        // 横1行に
[['A'], ['B'], ['C']].flat();  // 一次元に戻す

// 一次元を n 個ずつに区切って二次元にする
const chunk = (arr, n) =>
  Array.from({ length: Math.ceil(arr.length / n) }, (_, i) => arr.slice(i * n, i * n + n));
chunk([1, 2, 3, 4, 5, 6], 3);
[["A"],["B"],["C"]]
[["A","B","C"]]
["A","B","C"]
[[1,2,3],[4,5,6]]
書き込みたい形 配列の形 範囲の取り方
縦1列(3行1列) [['A'], ['B'], ['C']] getRange(1, 1, 3, 1)
横1行(1行3列) [['A', 'B', 'C']] getRange(1, 1, 1, 3)

ヘッダーをキーにしてオブジェクトの配列にする

列番号の代わりに列名で値を扱えるようにする書き方です。列の並びが変わってもコードを直さずに済みます。

const objs = rows.map(r => Object.fromEntries(header.map((h, i) => [h, r[i]])));
// 書き込むときは二次元配列に戻す
const back = objs.map(o => header.map(h => o[h]));
objs[0] → {"ID":1,"品名":"ノート","分類":"文具","数量":12,"単価":180}
back[0] → [1,"ノート","文具",12,180]

重複する行を除く

// 品名と分類が同じ行は、最初の1行だけ残す
const seen = new Set();
const unique = rows.filter(r => {
  const key = JSON.stringify([r[1], r[2]]);
  if (seen.has(key)) return false;
  seen.add(key);
  return true;
});

// 1列の値の種類だけを知りたいとき
const categories = [...new Set(rows.map(r => r[2]))];
unique の ID → [1,2,3,5]
categories → ["文具","機器"]

配列どうしは中身が同じでも === では一致しないため、比べたい列を文字列のキーにしてから Set で覚えるのがポイントです。

並べ替える

[...rows].sort((a, b) => b[4] - a[4]);                          // 単価の高い順
[...rows].sort((a, b) => a[1].localeCompare(b[1], 'ja'));       // 品名の順
[...rows].sort((a, b) => a[2].localeCompare(b[2], 'ja') || b[3] - a[3]); // 分類→数量の多い順
単価の高い順 → ["キーボード","マウス","ノート","ノート","ボールペン"]
品名の順     → ["キーボード","ノート","ノート","ボールペン","マウス"]
分類→数量 の ID → [3,5,2,1,4]

sort は元の配列を並べ替えるメソッドなので、[...rows] でコピーしてから呼ぶと元の順序を保てます。また、比較関数を省くと文字列として比べられます。

[10, 9, 100, 1].sort();
[1,10,100,9]

数値の列は必ず (a, b) => a - b の形で比較関数を渡します。

集計する

const total = rows.reduce((acc, r) => acc + r[3] * r[4], 0);
const byCategory = rows.reduce((m, r) => {
  m[r[2]] = (m[r[2]] || 0) + r[3];
  return m;
}, {});
Object.entries(byCategory);   // そのまま setValues に渡せる二次元配列
total → 28020
Object.entries(byCategory) → [["文具",54],["機器",8]]

Object.entries の結果は「キーと値の2列」の二次元配列になるので、集計表としてそのまま書き込めます。

setValuesで範囲と配列の大きさを合わせる

公式リファレンスは、setValues に渡す二次元配列について 「配列の大きさは範囲の大きさと一致していなければならない」 と説明しています。行数か列数が1つでも違うと例外が発生し、スクリプトはそこで止まります。

// エラーになる例:範囲は3行2列、配列は2行2列
sheet.getRange('A1:B3').setValues([[1, 2], [3, 4]]);

// 配列の大きさから範囲を決める書き方
const out = [[1, 2], [3, 4]];
sheet.getRange(1, 1, out.length, out[0].length).setValues(out);

範囲を文字列(A1:B3)で固定せず、out.length と out[0].length から決めると、データの件数が変わっても大きさがずれません。合わせて次の3点を確かめます。

確かめること 起きること 対応
配列が空(0行)でないか out[0] が undefined になり、out[0].length でエラーになる if (out.length === 0) return; を先に書く
行ごとの長さがそろっているか 列数が合わない行があると例外になる 短い行を空文字で埋める(下のコード)
一次元配列を渡していないか 二次元配列ではないため例外になる list.map(v => [v]) か [list] にする
// 行の長さを、いちばん長い行にそろえる
const ragged = [[1, 2, 3], [4], [5, 6]];
const width = Math.max(...ragged.map(r => r.length));
const padded = ragged.map(r => [...r, ...Array(width - r.length).fill('')]);
[[1,2,3],[4,"",""],[5,6,""]]

絞り込みの結果を書き戻すときは、前より行数が減ることがあります。先に clearContent() で以前の値を消してから書くと、下に残った行が混ざりません。

function writeFiltered() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const [header, ...rows] = sheet.getDataRange().getValues();
  const out = [header, ...rows.filter(r => r[2] === '機器')];
  sheet.getDataRange().clearContent();
  sheet.getRange(1, 1, out.length, out[0].length).setValues(out);
}

getValue・getValues・getDisplayValuesの違いと選び方

メソッド 戻り値 入っている値
getValue() 値1つ 範囲の左上のセルの値
getValues() 二次元配列 数値・真偽値・日付(Date)・文字列
getDisplayValue() 文字列1つ 左上のセルに表示されている文字列
getDisplayValues() 文字列の二次元配列 セルに表示されている文字列
getFormulas() 文字列の二次元配列 セルの数式
条件 選ぶもの
計算や比較に使う getValues
画面に見えているとおりの文字(書式つきの日付、桁区切りの数値)がほしい getDisplayValues
設定用のセルを1つだけ読む getValue
数式そのものを読みたい getFormulas

日付のセルは getValues では Date オブジェクトで返ります。日付どうしを比べるときは、getTime() で数値にしてから比べると確実です。

appendRowとsetValuesの使い分け

条件 選ぶもの 理由
記録を1行だけ足す appendRow([…]) データのいちばん下に1行足す。渡すのは一次元配列
複数の利用者やフォームから同時に行が足される appendRow([…]) 公式リファレンスが、この操作は分割されずに行われる(atomic)と説明している
複数行をまとめて足す getRange(最終行+1, 1, 行数, 列数).setValues(…) 呼び出しが1回で済む
既存の行を書き換える setValues(…) appendRow は末尾への追加専用
function appendRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const newRows = [
    [6, '付箋', '文具', 20, 90],
    [7, 'モニター', '機器', 2, 19800],
  ];
  sheet.getRange(sheet.getLastRow() + 1, 1, newRows.length, newRows[0].length).setValues(newRows);
}

ループの中で appendRow を何度も呼ぶ形は、行数ぶんの呼び出しになります。 足す行を配列にためて、最後に setValues を1回呼ぶ形に直せます。

確かめて分かったこと

  • Array(2).fill(Array(2).fill('')) で作った配列は、全部の行が同じ配列を指します。 1か所に代入すると全行が変わりました。空の表は Array.from で行ごとに作ります。
const shared = Array(2).fill(Array(2).fill(''));
shared[0][0] = 'x';
const separate = Array.from({ length: 2 }, () => Array(2).fill(''));
separate[0][0] = 'x';
shared   → [["x",""],["x",""]]
separate → [["x",""],["",""]]
  • [...rows] は行の中身まではコピーしません。 コピーした側で copy[0][1] = '変更' とすると、元の rows[0][1] も「変更」になりました。行の中身も書き換えるときは rows.map(r => [...r]) で行ごとにコピーします。
  • [[]] は「1行0列」です。 length は 1、[0].length は 0 でした。空かどうかは out.length === 0 だけでなく、列数も確かめると安心です。
  • 比較関数なしの sort は数値を文字列として並べます。 [10, 9, 100, 1] が [1,10,100,9] になりました。
  • 1,000行の処理で、読み書きの呼び出しは 2,001回から3回に減りました。

よくある質問

Q. GASの二次元配列とは何ですか? A. 行を表す配列を、さらに配列に入れたものです。getValues はシートの範囲をこの形で返し、data[行][列] で値を取り出します。添字は0から始まるので、シートの行番号・列番号より1つ小さくなります。

Q. getValuesで1列だけ読んでも二次元配列になるのはなぜですか? A. getValues は範囲の形にかかわらず、常に二次元配列を返すためです。1列なら [['A'], ['B']] の形になるので、一次元で扱いたいときは values.map(r => r[0]) か values.flat() を使います。

Q. setValuesでエラーになるときは何を確かめますか? A. 範囲の行数・列数と配列の大きさが一致しているかを確かめます。getRange(行, 列, out.length, out[0].length) と書けば、配列の大きさから範囲が決まります。一次元配列を渡していないか、行ごとの長さがそろっているかも見てください。

Q. mapとforEachの違いは何ですか? A. map は新しい配列を返し、forEach は何も返しません。加工した結果を setValues に渡したいときは map、ログ出力のように結果が要らない処理には forEach を使います。

まとめ

  • getValues で1回読み、配列で加工し、setValues で1回書くのが基本形です。
  • 列は map、行は filter、集計は reduceで書けます。
  • 1列を書き戻すときは [r[1]] と包んで二次元配列にします。
  • 書き込む範囲は getRange(行, 列, out.length, out[0].length) で配列から決めます。
  • 表示どおりの文字がほしいときは getDisplayValues、計算には getValues を使います。
  • 1行なら appendRow、複数行なら setValues にまとめます。
  • 空の表は Array.from で作り、数値の並べ替えには比較関数を渡します。

参考・出典

  • Google Apps Script「Best Practices」 https://developers.google.com/apps-script/guides/support/best-practices
  • Google Apps Script リファレンス「Class Range」 https://developers.google.com/apps-script/reference/spreadsheet/range
  • Google Apps Script リファレンス「Class Sheet」 https://developers.google.com/apps-script/reference/spreadsheet/sheet

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「GAS二次元配列の操作一覧|getValuesからmap・filterまで」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?