GASを「業務アプリ」にする5つのUIパターン
ここは第3回の後半にあたる内容です。時間の都合でお伝えしきれなかったため、第4回の冒頭でまるごとすべて扱います。
第3回で作った処理(evaluateAll / judgePpm / recommendAction など)を部品として、同僚が「ボタンやメニューで使える」業務アプリに仕上げていきます。
1GASを「業務アプリ」にする5つのUIパターン
この前半パートは、第3回でお伝えしきれなかった内容です。第3回では、作ったコードを「スクリプトエディタを開いて ▷ 実行」していました。 しかし、同僚に渡して使ってもらうには、それでは不便です。GASには、スプレッドシートを 「ボタンやメニューで動く業務アプリ」に変える仕組みが用意されています。代表的な5つを、ひとつずつ 「簡単な例」と「実務で使う複雑な例」の両方で学びます。
| パターン | どんな時に使う | キーになる関数 |
|---|---|---|
| ① 独自メニュー | 機能を整理して「いつでも呼べる場所」に置きたい | onOpen() / createMenu |
| ② 図形ボタン | シート上に「押せばすぐ動く」大きなボタンが欲しい | 図形にスクリプトを割り当て |
| ③ セル編集トリガー | 値を直したら自動で再計算・更新したい | onEdit(e) |
| ④ サイドバー | 入力フォームや操作パネルを画面横に常駐させたい | showSidebar / HtmlService |
| ⑤ ダイアログ | 確認・入力・結果サマリーをポップアップで見せたい | ui.alert / showModalDialog |
共通の入口は SpreadsheetApp.getUi()。メニュー・アラート・サイドバー・ダイアログはすべて、この getUi()(UI=ユーザーインターフェース)から作ります。「画面に何かを出す系はまず getUi()」と覚えておきましょう。以降、第3回で作った evaluateAll / colorByRank / judgePpm / recommendAction を部品として再利用します。
2パターン①:上部に独自メニューを作る
スプレッドシートの上部メニュー(ファイル・編集…の並び)に、自分専用のメニューを追加できます。
鍵は onOpen() という特別な名前の関数。ファイルを開いた瞬間に自動で実行されるため、
ここでメニューを組み立てます。
🟢 簡単な例 ── メニューから挨拶を出すだけ
// ファイルを開くと自動で実行される特別な関数
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("リサーチツール") // メニュー名
.addItem("ごあいさつ", "sayHello") // 表示名, 実行する関数名
.addToUi(); // 画面に追加
}
function sayHello() {
SpreadsheetApp.getUi().alert("リサーチツールへようこそ!");
}
コードを保存したら、いったんスプレッドシートを再読み込み(ブラウザの更新)してください。 上部に「リサーチツール」メニューが現れ、「ごあいさつ」を選ぶとポップアップが出ます。
関数名は文字列で渡します。addItem("ごあいさつ", "sayHello") の2つ目は、かっこなしの文字列で関数名を書きます。sayHello() のようにかっこをつけるとその場で実行されてしまい、正しく登録できません。また、メニューはファイルを開いた時だけ作られるので、追加直後は再読み込みが必要です。
🔵 実務的な例 ── ツール一式をメニューに集約する
第3回で作った機能を、メニューから呼べるように整理します。addSeparator(区切り線)や
addSubMenu(サブメニュー)を使うと、本格的なツールバーになります。
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("📊 リサーチ自動評価")
.addItem("▶ 全件をPPM判定する", "runEvaluate")
.addItem("📈 判定サマリーを表示", "showSummaryDialog")
.addSeparator() // 区切り線
.addItem("➕ セグメント追加フォーム", "showSidebar")
.addSubMenu( // 入れ子のサブメニュー
ui.createMenu("⚙ 設定")
.addItem("判定結果をクリア", "clearResults")
.addItem("使い方を見る", "showHelp")
)
.addToUi();
}
// メニューから呼ぶ:判定して色をつけ、完了通知まで出す
function runEvaluate() {
evaluateAll(); // 第3回で作ったメイン処理
colorByRank(); // 第3回で作った色分け
SpreadsheetApp.getActiveSpreadsheet()
.toast("全セグメントの判定が完了しました", "✅ 完了", 3);
}
// 判定結果(E:F列)を消す
function clearResults() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const numRows = sheet.getLastRow() - 1;
sheet.getRange(2, 5, numRows, 2).clearContent().setBackground(null);
}
function showHelp() {
SpreadsheetApp.getUi().alert(
"使い方\n\n1) A〜D列に市場データを入力\n2)「▶ 全件をPPM判定する」を実行\n3) E・F列に判定とアクションが入ります"
);
}
toast() は控えめな完了通知。画面右下にスッと出て自動で消える通知です(toast(本文, タイトル, 表示秒数))。アラート(OKを押すまで消えない)より邪魔にならないので、「処理が終わった」合図に最適です。これでメニュー1つから、判定→色分け→通知まで一気通貫の業務ツールになりました。
3パターン②:図形を実行ボタンにする
メニューよりもっと直感的なのが、シート上に置いた図形(ボタン)です。 「ここを押せば動く」と一目でわかるので、ITが苦手な同僚にも渡しやすくなります。 ポイントは、図形に「スクリプトを割り当てる」という操作です。
図形にスクリプトを割り当てる手順
図形を挿入
「挿入」→「図形描画」で角丸四角などを描き、「保存して終了」でシートに配置。
メニューを開く
配置した図形をクリックで選択し、右上に出る「︙」(3点)をクリック。
関数名を割り当て
「スクリプトを割り当て」を選び、実行したい関数名だけ(例 runEvaluate)を入力。
割り当てるのは「関数名だけ」。かっこ () は付けません(runEvaluate ○ / runEvaluate() ✗)。また、図形に割り当てる関数は引数を取れません。「ボタン用の入口関数」を1つ用意し、その中から本体を呼ぶ形にします。初回クリック時は承認(権限の許可)を求められるので、許可してください。
🟢 簡単な例 ── 押したらメッセージが出るボタン
// 図形に「buttonHello」を割り当てる
function buttonHello() {
SpreadsheetApp.getActiveSpreadsheet()
.toast("ボタンが押されました!", "👆 クリック検知", 3);
}
🔵 実務的な例 ── 「判定実行」「クリア」の2ボタン運用
シートに 「▶ 判定実行」と 「🧹 結果クリア」の2つの図形を置き、それぞれに関数を割り当てます。 ユーザーはコードを一切触らず、ボタンだけで操作できます。
// 「▶ 判定実行」ボタンに割り当てる
function onClickEvaluate() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
ss.toast("判定中…", "⏳", 2);
evaluateAll();
colorByRank();
ss.toast("判定が完了しました", "✅ 完了", 3);
}
// 「🧹 結果クリア」ボタンに割り当てる(確認してから消す)
function onClickClear() {
const ui = SpreadsheetApp.getUi();
const res = ui.alert("確認", "判定結果(E・F列)を消しますか?", ui.ButtonSet.YES_NO);
if (res === ui.Button.YES) {
clearResults(); // 前半①の独自メニューで作った関数を再利用
SpreadsheetApp.getActiveSpreadsheet().toast("クリアしました");
}
}
「入口関数」という考え方。ボタンには onClickEvaluate のような専用の入口関数を割り当て、その中で evaluateAll など本体を呼びます。こうすると、トースト通知や確認ダイアログといった「ボタンならではの気配り」を、本体ロジックを汚さずに足せます。メニュー・ボタンの両方から同じ本体を呼べるのも、関数化(第3回)のおかげです。
4パターン③:セル編集トリガー(onEdit)
ボタンすら押さず、「値を直したら、その場で自動実行」を実現するのが
onEdit(e) です。これも onOpen と同じ特別な名前の関数で、
誰かがセルを編集するたびに自動で呼ばれます。引数 e には「どこを、何に編集したか」の情報が入っています。
| e の中身 | 意味 |
|---|---|
e.range | 編集されたセル範囲(.getRow() / .getColumn() で行・列番号) |
e.value | 編集後の新しい値 |
e.oldValue | 編集前の古い値 |
e.source | 対象のスプレッドシート本体 |
🟢 簡単な例 ── 編集したセルに色をつける
// セルを編集するたびに自動実行される
function onEdit(e) {
e.range.setBackground("#fff3cd"); // 編集されたセルを黄色に
}
どこかのセルに何か入力してみてください。そのセルが自動で黄色くなれば成功です。
🔵 実務的な例 ── 数字を直すと、その行だけ即・再判定
いよいよ実務レベル。「リサーチ一覧」シートで、成長率(C列)かシェア(D列)を編集したら、その行だけPPM判定をやり直す仕組みです。
全件再計算せず、触った1行だけを更新するのがポイント。「関係ない編集は無視する」ガード(早期 return)が肝になります。
function onEdit(e) {
const sheet = e.range.getSheet();
const row = e.range.getRow();
const col = e.range.getColumn();
// --- 関係ない編集は無視する(ガード) ---
if (sheet.getName() !== "リサーチ一覧") return; // 別シートなら何もしない
if (row < 2) return; // 見出し行なら無視
if (col !== 3 && col !== 4) return; // C・D列以外は無視
// --- その行の成長率・シェアを読んで再判定 ---
const growth = Number(sheet.getRange(row, 3).getValue());
const share = Number(sheet.getRange(row, 4).getValue());
const rank = judgePpm(growth, share);
const action = recommendAction(rank);
// --- E・F列に書き込み、色もつける ---
sheet.getRange(row, 5).setValue(rank);
sheet.getRange(row, 6).setValue(action);
let color = "#fecaca";
if (rank === "花形") color = "#fde68a";
else if (rank === "問題児") color = "#bfdbfe";
else if (rank === "金のなる木") color = "#bbf7d0";
sheet.getRange(row, 5).setBackground(color);
}
シンプルな onEdit では「画面に出す系」が使えません。自動で動く onEdit(シンプルトリガー)は、権限の要る操作 ──
ui.alert() やメール送信など ── を呼べません。セルの読み書き・色付けはOKです。アラートを出したい等の高度なことをしたい場合は、「インストール型トリガー」という別の仕組みが必要になります(次回以降のトリガー回で扱います)。
ガード(早期 return)が読みやすさのコツ。「関係ない編集なら、すぐ return で抜ける」を先頭に並べると、本処理が深いインデントに埋もれず、意図が明確になります。onEdit はあらゆる編集で毎回動くので、「自分が反応すべき編集か?」を最初に判定するのが定石です。
5パターン④:サイドバーを作る
サイドバーは、画面の右側に常駐する操作パネルです。入力フォームやダッシュボードを置くのに向いています。
中身は HTML(Webページの書き方)で作り、HtmlService 経由で表示します。
GAS(サーバー側)とHTML(画面側)が、google.script.run でやり取りするのがポイントです。
🟢 簡単な例 ── テキストを表示するだけ
function showSimpleSidebar() {
const html = HtmlService.createHtmlOutput(
"<p style='font-family:sans-serif;padding:12px;'>これはサイドバーです 👋</p>"
).setTitle("はじめてのサイドバー");
SpreadsheetApp.getUi().showSidebar(html);
}
🔵 実務的な例 ── 新セグメントを追加して即判定するフォーム
実務サイドバーの定番は「入力フォーム」です。サイドバーに市場データを入力して「追加」を押すと、
GAS側が一覧表に1行追加し、PPM判定まで済ませて結果を返す——という流れを作ります。
HTMLは 別ファイルとして用意し、createHtmlOutputFromFile で読み込みます。
HTMLファイルの作り方:スクリプトエディタ左の「+」→「HTML」を選び、ファイル名を Sidebar にして、下の内容を貼り付けて保存します(拡張子 .html は自動)。Code.gs と Sidebar.html の2ファイル構成になります。
// サイドバー(Sidebar.html)を開く
function showSidebar() {
const html = HtmlService.createHtmlOutputFromFile("Sidebar")
.setTitle("➕ セグメント追加");
SpreadsheetApp.getUi().showSidebar(html);
}
// 画面(HTML)から呼ばれる:1行追加してPPM判定し、結果を返す
function addSegment(form) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const growth = Number(form.growth);
const share = Number(form.share);
const rank = judgePpm(growth, share);
const action = recommendAction(rank);
// 末尾に1行追加:A〜F列
sheet.appendRow([form.name, Number(form.size), growth, share, rank, action]);
return rank; // 画面側に判定結果を返す
}
<!DOCTYPE html>
<html>
<head><base target="_top"></head>
<body style="font-family:sans-serif; padding:12px;">
<h3>新しいセグメントを追加</h3>
<label>セグメント名</label>
<input id="name" type="text" style="width:100%">
<label>市場規模(億円)</label>
<input id="size" type="number" style="width:100%">
<label>成長率(%)</label>
<input id="growth" type="number" style="width:100%">
<label>自社シェア(%)</label>
<input id="share" type="number" style="width:100%">
<button onclick="submitForm()" style="margin-top:10px;">追加して判定</button>
<p id="result" style="font-weight:bold;"></p>
<script>
function submitForm() {
const form = {
name: document.getElementById("name").value,
size: document.getElementById("size").value,
growth: document.getElementById("growth").value,
share: document.getElementById("share").value
};
// GAS側の addSegment を呼び、戻り値を画面に表示
google.script.run
.withSuccessHandler(function(rank) {
document.getElementById("result").textContent = "判定: " + rank;
})
.addSegment(form);
}
</script>
</body>
</html>
google.script.run が画面とサーバーの「橋」です。HTML側の google.script.run.addSegment(form) が、GAS側の addSegment 関数を呼び出します。結果は withSuccessHandler に渡した関数で受け取ります(処理は非同期なので、戻り値を直接代入できない点に注意)。これで、コードを知らない人でもフォーム入力だけでデータ追加できる本格ツールの完成です。
6パターン⑤:ダイアログを表示する
最後はポップアップ=ダイアログ。「確認したい」「ちょっと入力させたい」「結果サマリーを見せたい」場面で使います。
手軽な順に ① alert(お知らせ)→ ② prompt(入力)→ ③ showModalDialog(HTMLで自由表示) の3段階を押さえましょう。
🟢 簡単な例 ── alert と prompt
// ① alert:メッセージを見せてOKを待つ
function showAlert() {
SpreadsheetApp.getUi().alert("判定が完了しました!");
}
// ② prompt:ユーザーに文字を入力してもらう
function askThreshold() {
const ui = SpreadsheetApp.getUi();
const res = ui.prompt("成長率のしきい値は?(例: 10)", ui.ButtonSet.OK_CANCEL);
if (res.getSelectedButton() === ui.Button.OK) {
ui.alert("入力された値: " + res.getResponseText());
}
}
prompt は「どのボタンが押されたか」と「入力文字」の2つを返します。getSelectedButton() でOK/キャンセルを判定し、getResponseText() で入力文字を取り出します。ButtonSet には OK / OK_CANCEL / YES_NO などがあります。
🔵 実務的な例 ── 判定サマリーをHTMLダイアログで見せる
一覧の判定結果を集計し、「花形◯件/問題児◯件…」のサマリーをきれいなポップアップで表示します。
集計には第5章の forEach を使い、表示は showModalDialog(HTMLを自由にレイアウトできるダイアログ)で行います。
function showSummaryDialog() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const numRows = sheet.getLastRow() - 1;
const ranks = sheet.getRange(2, 5, numRows, 1).getValues(); // E列
// --- forEach で件数を集計する ---
const count = { "花形": 0, "問題児": 0, "金のなる木": 0, "負け犬": 0 };
ranks.forEach(function(r) {
const k = r[0];
if (count[k] !== undefined) count[k]++;
});
// --- 集計結果をHTMLに組み立てる ---
const html =
"<div style='font-family:sans-serif; padding:8px 14px;'>" +
"<h3 style='margin-top:0;'>PPM判定サマリー</h3><ul style='line-height:2;'>" +
"<li>⭐ 花形:" + count["花形"] + " 件</li>" +
"<li>❓ 問題児:" + count["問題児"] + " 件</li>" +
"<li>💰 金のなる木:" + count["金のなる木"] + " 件</li>" +
"<li>🐕 負け犬:" + count["負け犬"] + " 件</li>" +
"</ul></div>";
const dialog = HtmlService.createHtmlOutput(html)
.setWidth(320).setHeight(220);
SpreadsheetApp.getUi().showModalDialog(dialog, "📈 判定サマリー");
}
3つのダイアログの使い分け:alert は「見せて終わり」、prompt は「短い入力」、showModalDialog は「自由なレイアウトで見せる/複雑な操作」。サマリーやグラフ、複数項目の入力など凝った表示は showModalDialog(HTML)が担当します。前半②の独自メニュー「📈 判定サマリーを表示」から、この関数を呼べるようにつないであります。
7つまずきポイントと対処
繰り返し・関数・UIが絡む今回は、つまずきも増えます。代表的な5つを先回りで押さえましょう。
① ループの「1個ずれ」(off-by-one)
続ける条件を <= にすると1回多く回り、存在しない要素を読んで undefined が出ます。配列ループは <(未満)が基本です。
// ✗ 間違い:i が data.length まで回り、1件はみ出す
for (let i = 0; i <= data.length; i++) { ... }
// ✓ 正しい:未満(<)で止める
for (let i = 0; i < data.length; i++) { ... }
② getValues の行数を取りすぎる
getLastRow() は「データの最終行番号」。見出し行を除いたデータ行数は getLastRow() - 1 です。
getRange(2, 1, getLastRow(), 4) のように -1 を忘れると、空行まで読んで判定が崩れます。
③ 配列の番号は0から、シートの行・列は1から
| 対象 | 数え始め | 例 |
|---|---|---|
| 配列のインデックス | 0 から | 1件目 = data[0] |
getRange の行・列 | 1 から | 1行1列目(A1) = getRange(1, 1) |
だから「配列の i 番目」と「シートの行」をつなぐとき、getRange(i + 2, ...) のように「+2」(0始まり+見出し1行)の調整が要ります。
④ メニュー・トリガーが反応しない
onOpen や onEdit は名前が決まった特別な関数です。綴りを間違える(onedit 等)と動きません。
また独自メニューはファイルを開いた時だけ作られるので、追加直後は再読み込みが必要。
シンプル onEdit で ui.alert 等の権限が要る操作は使えない点も再確認しましょう(第13章)。
⑤ 図形ボタンに割り当て忘れ/引数つき関数を割り当てる
図形を置いただけでは動きません。「︙」→「スクリプトを割り当て」で関数名(かっこなし)を登録します。 割り当てる関数は引数なしであること。引数が必要な処理は、引数なしの「入口関数」から呼び出してください(第12章)。
困ったら、まず Logger.log と「実行ログ」。「onEdit がそもそも呼ばれているか?」を確かめたいときは、関数の先頭に Logger.log(e.range.getA1Notation()) を入れて、編集後に「実行数」画面でログを見ます。どこまで動いているかを見える化するのが、デバッグの第一歩です(第9章のAIデバッグにも、このログを添えると精度が上がります)。
GAS実践① ── スプレッドシート操作の基本
ここからが今回(第4回)の本題です。前半では「画面(UI)」を作りましたが、後半はデータそのものを読み書きする力を体系的に固めます。
SpreadsheetApp の構造を理解し、セル1つの読み書きから、表を丸ごと扱う一括処理、検索・並べ替え・フィルタ、そして「集計して別シートに出力」までを一気通貫で身につけます。
この後半で使うサンプルシート「リサーチ一覧」。第2〜3回と同じ構成です。A列=セグメント名、B列=カテゴリ、C列=成長率(%)、D列=シェア(%)、E列=判定(花形/問題児/金のなる木/負け犬)、F列=推奨アクション。1行目は見出し、2行目以降がデータです。第3回で作った judgePpm() / recommendAction() も引き続き部品として使います。
8SpreadsheetApp の全体像 ── 3階層のオブジェクト構造
第3回では getValues() などを「おまじない」的に使いました。ここで一度、「GASはスプレッドシートをどう見ているか」を整理します。
ポイントは、ファイル → シート → 範囲(セル)という3つの階層(入れ子)になっていることです。目的の場所にたどり着くには、この階層を上から順にたどります。
| 入口メソッド | 何が返るか | 使いどころ |
|---|---|---|
SpreadsheetApp.getActiveSpreadsheet() | いま開いているファイル | まずはこれで「ファイル」を掴む |
.getActiveSheet() | いま表示中のシート | アクティブなタブを対象にする |
.getSheetByName("名前") | 名前で指定したシート | 対象シートを確実に指定(推奨) |
.getRange(...) | セルの範囲 | 読み書きしたい場所を指定(第9章) |
.getUi() | 画面のUI | 前半で学んだメニュー・ダイアログ |
🟢 簡単な例 ── ファイル名とシート名を確かめる
function showSheetInfo() {
const ss = SpreadsheetApp.getActiveSpreadsheet(); // ① ファイル
const sheet = ss.getActiveSheet(); // ② シート
const fileName = ss.getName(); // ファイル名
const tabName = sheet.getName(); // シート(タブ)名
ss.toast("ファイル:" + fileName + " / シート:" + tabName, "📄 情報", 4);
}
「アクティブ」=いま自分が見ているもの。getActiveSpreadsheet は「今開いているファイル」、getActiveSheet は「今見えているタブ」を指します。手軽ですが、複数シートを扱う自動化ではどのタブが選ばれているかで結果が変わってしまいます。実務では getSheetByName("リサーチ一覧") のように名前で明示指定するのが安全です。
9シートと範囲(Range)を取得する
読み書きの主役は Range(範囲)です。getRange() には4つの書き方があり、場面で使い分けます。
特に大事なのが、「行, 列, 行数, 列数」の4つの数字で範囲を指定する形。表をまとめて扱うときの基本形です。
| 書き方 | 意味 | 例(結果) |
|---|---|---|
getRange("B2") | A1記法で1セル | B2セル |
getRange(2, 2) | 行番号, 列番号で1セル | 2行2列目 = B2 |
getRange(2, 1, 5, 3) | 開始行, 開始列, 行数, 列数 | A2から下5行×右3列(A2:C6) |
getRange("A2:C6") | A1記法で範囲 | A2:C6 |
便利:データが入っている範囲を自動で掴む
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
// データが入っている長方形を丸ごと(見出し行も含む)
const all = sheet.getDataRange();
// 最終行・最終列の番号を調べる
const lastRow = sheet.getLastRow(); // 例:11(見出し+10件)
const lastCol = sheet.getLastColumn(); // 例:6(A〜F)
// 見出しを除いたデータだけ(2行目から、行数=最終行−1)
const body = sheet.getRange(2, 1, lastRow - 1, lastCol);
行・列は「1」から、配列は「0」から。スプレッドシートの1行目・1列目は 1。一方、読み込んだ二次元配列は data[0] が1行目です。この「1始まり」と「0始まり」のズレが、第4回でいちばんつまずく所。getRange(2, 1, lastRow - 1, ...) の - 1 は「見出し1行を除く」意味だと覚えておきましょう。
10セルを読み書きする(getValue / setValue)
範囲を掴めたら、いよいよ読み書きです。1つのセルは getValue() で読み、setValue() で書き込みます。
「get=取り出す/set=入れる」と対で覚えると迷いません。
🟢 簡単な例 ── 1セル読んで、別のセルに書く
function readWriteOneCell() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const name = sheet.getRange("A2").getValue(); // A2を読む
sheet.getRange("H1").setValue("先頭の市場は:" + name); // H1に書く
}
🔵 実務的な例 ── 集計セルと数式を書き込む
読み書きは「値」だけでなく「数式」も可能です。setFormula() を使えば、シート上に =SUM(...) のような数式をGASから設定できます。
「行数が毎回変わる」表でも、getLastRow() と組み合わせれば正しい範囲の合計を書けます。
function writeShareTotal() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const last = sheet.getLastRow();
sheet.getRange("H1").setValue("シェア合計(%)");
// D2からD(最終行)までを合計する数式を書き込む
sheet.getRange("H2").setFormula("=SUM(D2:D" + last + ")");
sheet.getRange("H1").setFontWeight("bold");
}
| よく使うセル操作 | 働き |
|---|---|
getValue() / setValue(値) | 1セルの読み/書き |
setFormula("=SUM(...)") | セルに数式を入れる |
setBackground("#fde68a") | セルの背景色 |
setFontWeight("bold") | 文字を太字に |
clearContent() | 中身(値)を消す |
単数の getValue と複数の getValues。末尾に s が付くかどうかで別物です。getValue()(単数)は1つの値を返し、getValues()(複数形)は二次元配列を返します。1セルなら getValue、表なら getValues ── 次章でこの「一括」の威力を見ます。
11一括で読み書きする(getValues / setValues)
ここが第4回の山場です。100件のデータを処理するとき、1セルずつ getValue / setValue すると、スプレッドシートへの「アクセス(往復)」が何百回も発生し、非常に遅くなります。
そこで getValues() で表を一気に配列へ読み込み、加工してから setValues() で一気に書き戻すのが鉄則です。
🟢 簡単な例 ── 範囲をまとめて読む
function readAll() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const last = sheet.getLastRow();
// A2:F(最終行) を二次元配列として読み込む
const data = sheet.getRange(2, 1, last - 1, 6).getValues();
Logger.log("1件目の市場名:" + data[0][0]); // [行][列] とも0始まり
Logger.log("読み込んだ件数:" + data.length);
}
🔵 実務的な例 ── 全件を一括で判定して書き戻す
第3回では1件ずつ判定しました。ここでは getValues → map → setValues の一括3ステップで、全セグメントのPPM判定を一気に処理します。
判定ロジックは第3回で作った judgePpm() / recommendAction() をそのまま再利用します。
function evaluateAllBulk() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const numRows = sheet.getLastRow() - 1; // 見出しを除く
if (numRows < 1) return; // データが無ければ何もしない
// ① C・D列(成長率・シェア)を一括で読む
const input = sheet.getRange(2, 3, numRows, 2).getValues();
// ② 各行を判定し、E・F列ぶんの二次元配列を作る
const output = input.map(function (row) {
const rank = judgePpm(Number(row[0]), Number(row[1]));
const action = recommendAction(rank);
return [rank, action]; // [E列の値, F列の値]
});
// ③ E2から一括で書き戻す(numRows行 × 2列)
sheet.getRange(2, 5, numRows, 2).setValues(output);
SpreadsheetApp.getActiveSpreadsheet().toast(numRows + "件を一括判定しました", "✅", 3);
}
setValues は「範囲の形」と「配列の形」を必ず一致させます。getRange(2, 5, numRows, 2) は numRows行 × 2列。書き込む配列も、同じ行数・同じ列数の二次元配列でなければ 「行数・列数が一致しません」 というエラーになります。「読んだ範囲と同じ形で書き戻す」を合言葉にしましょう。
12データを検索・並べ替え・フィルタする
読み込んだ表は、ただの二次元配列です。第3回で学んだ配列メソッド(filter / forEach)や、シートの並べ替え機能を使えば、 「花形だけ抜き出す」「成長率の高い順に並べる」「特定の値を探す」といったデータ整理を自動化できます。
| やりたいこと | 手段 | キーになる書き方 |
|---|---|---|
| 絞り込み(条件に合う行だけ) | 配列の filter | data.filter(row => row[4] === "花形") |
| 並べ替え(シート上で) | Range の sort | range.sort({column: 3, ascending: false}) |
| 検索(セル位置を探す) | TextFinder | sheet.createTextFinder("花形") |
🟢 簡単な例 ── 「花形」の行だけ抜き出す(フィルタ)
function listStars() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const last = sheet.getLastRow();
const data = sheet.getRange(2, 1, last - 1, 6).getValues();
// E列(判定=5番目 → 添字4)が「花形」の行だけ残す
const stars = data.filter(function (row) {
return row[4] === "花形";
});
stars.forEach(function (row) {
Logger.log(row[0] + " → " + row[5]); // 市場名 → アクション
});
SpreadsheetApp.getActiveSpreadsheet().toast("花形は " + stars.length + " 件");
}
🔵 実務的な例 ── 成長率の高い順に並べ替え/検索
シートそのものを並べ替えるなら Range.sort() が手軽です。{column: 3, ascending: false} で「3列目(成長率)の降順」を指定します。
また、値のセル位置を知りたいときは TextFinder が便利です。
// 成長率(C列=3列目)の高い順に、データ行を並べ替える
function sortByGrowth() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const last = sheet.getLastRow();
sheet.getRange(2, 1, last - 1, 6)
.sort({ column: 3, ascending: false }); // 見出しは含めない
}
// 「花形」が最初に現れるセルの場所を調べる
function findStar() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
const cell = sheet.createTextFinder("花形").findNext();
if (cell) {
SpreadsheetApp.getActiveSpreadsheet()
.toast("最初の花形は " + cell.getA1Notation() + " にあります");
}
}
「シートを並べ替える」か「配列を並べ替える」か。Range.sort() はシート上の見た目そのものを並べ替えます。一方、読み込んだ配列を data.sort(...) すれば、シートは触らずメモリ上だけで並べ替えられます。「表示も変えたい」なら前者、「計算に使うだけ」なら後者、と使い分けましょう。
13統合実践 ── 集計して「別シート」に出力する
いよいよ今回のハンズオン演習です。学んだ道具を組み合わせ、「リサーチ一覧を読み取り → 判定ランクごとに集計 → 『集計結果』という別シートに出力」する自動化スクリプトを完成させます。 これが第4回の成果物「スプレッドシート集計自動化スクリプト」になります。
読む
「リサーチ一覧」のE列(判定)を getValues でまとめて取得。
集計する
ランクごとに件数を数え、オブジェクトに {花形: 3, …} と集計。
別シートに書く
「集計結果」シートを用意し、setValues で表を一括出力+装飾。
function exportSummary() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const src = ss.getSheetByName("リサーチ一覧");
const last = src.getLastRow();
if (last < 2) return; // データが無ければ中止
// ① E列(判定ランク)だけを一括で読む
const ranks = src.getRange(2, 5, last - 1, 1).getValues();
// ② ランクごとに件数を集計
const order = ["花形", "問題児", "金のなる木", "負け犬"];
const count = {};
order.forEach(function (k) { count[k] = 0; });
ranks.forEach(function (r) {
const k = r[0];
if (count[k] !== undefined) count[k]++;
});
// ③ 出力シートを用意(無ければ作る/あれば中身を消す)
let out = ss.getSheetByName("集計結果");
if (!out) {
out = ss.insertSheet("集計結果");
} else {
out.clear();
}
// ④ 見出し+データを二次元配列にまとめ、一括で書き込む
const table = [["判定ランク", "件数"]];
order.forEach(function (k) {
table.push([k, count[k]]);
});
out.getRange(1, 1, table.length, 2).setValues(table);
// ⑤ 見出し行を装飾して見やすく
out.getRange(1, 1, 1, 2)
.setFontWeight("bold")
.setBackground("#1e293b")
.setFontColor("#ffffff");
out.autoResizeColumns(1, 2);
ss.toast("「集計結果」シートに出力しました", "✅ 完了", 4);
}
これが「集計自動化スクリプト」の完成形です。ボタン(前半②〜③)やメニューに exportSummary を割り当てれば、ワンクリックで集計レポートが別シートに出力されます。getValues で読み、配列で集計し、setValues で書く ── この「読む→加工→書く」の型は、今後どんな自動化にも応用できる基本パターンです。
14つまずきポイントと対処(スプレッドシート操作編)
スプレッドシート操作で特に多い5つのつまずきを、原因と対処のセットでまとめます。エラーが出たら、まずここを確認してください。
① setValues の「配列の形」が範囲と合わない
いちばん多いエラーです。getRange(2, 5, numRows, 2) は numRows行×2列。書き込む配列も、必ず同じ行数・列数の二次元配列にします。[[a, b], [c, d], …] の形かを確認。
② getValue(単数)と getValues(複数)の取り違え
末尾の s の有無で戻り値が別物です。1セルなら getValue()(値)、範囲なら getValues()(二次元配列)。配列に対して .toUpperCase() などを呼ぶとエラーになります。
③ 空のシートで getLastRow() が 0 になる
データが1件も無いシートでは getLastRow() が 0 や 1 を返し、getRange(2, 1, last - 1, 6) の行数が 0以下になってエラーに。冒頭で if (last < 2) return; とガードを入れましょう。
const last = sheet.getLastRow();
if (last < 2) { // 見出しのみ=データ無し
SpreadsheetApp.getUi().alert("データがありません");
return;
}
④ ループの中で getValue / setValue を繰り返して遅い
件数が増えると目に見えて遅くなります。第11章の通り、ループの外で1回だけ getValues/setValues する形に直しましょう。「シートへのアクセスはできるだけ少なく」が鉄則です。
⑤ getSheetByName の名前ミスで null になる
シート名が1文字でも違うと getSheetByName() は null を返し、続く .getRange で 「null のプロパティを読めません」 エラーに。タブ名の全角・半角・空白まで正確に合わせてください。
詰まったらAIに丸ごと聞く。第3回で学んだ「エラー文+やりたいこと+コード」の3点セットで、ChatGPTに貼れば原因をほぼ特定してくれます。Logger.log() で途中の値(data.length や numRows)を出して、「どこで形が崩れたか」を掴むのが解決の近道です。
第4回の宿題
集計結果を「別シート」に出力する
第13章の exportSummary を自分の「リサーチ一覧」で動かし、「集計結果」シートにランク別件数が出力されることを確認してください。前半で作ったメニューや図形ボタンに割り当て、ワンクリックで動く状態にできれば完璧です。
一括処理(getValues → setValues)に書き換える
1セルずつ getValue / setValue しているコードがあれば、第11章の要領で getValues → map → setValues の一括版に書き換え、処理が速くなることを体感しましょう。
AIに「機能追加」を依頼する
集計スクリプトに「花形だけを別シートに抽出する」機能を、ChatGPTに追加依頼してみましょう。うまく動かないときは、エラー文とコードをそのまま貼って直してもらうのがコツです。
提出方法:作成したスプレッドシートのURLと、最終的に動いたGASコード(Code.gs)の2点を、研修事務局へメールでご提出ください。提出期限:次回研修の前日17:00まで。