この記事でわかること
- スプレッドシートを業務システム化するメリット
- 効果的なシート設計のパターン
- カスタムメニューの作り方
- 入力規則・データ検証の設定方法
- GASによる自動化の実装方法
この記事の対象読者
- Excelやスプレッドシートで業務管理している方
- 専用システムを導入するほどではないが効率化したい方
- GAS(Google Apps Script)で業務自動化を検討している方
- 低コストで業務システムを構築したい方
なぜスプレッドシートで業務システムなのか
専用システム vs スプレッドシート
| 項目 | 専用システム | スプレッドシート |
|---|---|---|
| 初期コスト | 高い(数十万〜数百万円) | 無料 |
| 月額費用 | あり(数千〜数万円/月) | なし |
| 導入期間 | 長い(数週間〜数ヶ月) | 即日 |
| カスタマイズ | 制限あり or 追加費用 | 自由自在 |
| 学習コスト | 新しいUIを覚える必要 | 使い慣れた操作 |
| データ出力 | 制限あり | 自由に加工可能 |
スプレッドシートが向いているケース
- ✅ 数人〜数十人で使う小規模な業務
- ✅ データ量が数万件以下
- ✅ 頻繁に仕様変更が発生する
- ✅ とりあえず試してみたい
- ✅ 予算が限られている
スプレッドシートが向かないケース
- ❌ 同時編集者が100人以上
- ❌ データ量が数十万件以上
- ❌ 厳密な権限管理が必要
- ❌ 複雑なワークフローが必要
システム設計の基本
3層構造で考える
スプレッドシートで業務システムを作る際は、以下の3層構造を意識します。
┌─────────────────────────────────────┐
│ UI層:メニュー・ボタン・入力規則 │
├─────────────────────────────────────┤
│ ロジック層:GAS(処理・自動化) │
├─────────────────────────────────────┤
│ データ層:シート(データ保存) │
└─────────────────────────────────────┘
| 層 | 役割 | 使用技術 |
|---|---|---|
| UI層 | ユーザーが操作する画面 | メニュー、ボタン、入力規則、条件付き書式 |
| ロジック層 | データ処理・自動化 | Google Apps Script(GAS) |
| データ層 | データの保存・管理 | スプレッドシートのシート |
シート設計パターン
基本の4シート構成
多くの業務システムは、以下の4シート構成で実現できます。
| シート名 | 役割 | 例 |
|---|---|---|
| マスタ | メインデータの一覧 | 顧客一覧、商品一覧、案件一覧 |
| 履歴 | トランザクション・ログ | 注文履歴、対応履歴、変更履歴 |
| 設定 | システム設定値 | APIキー、オプション、フラグ |
| ログ | 実行ログ・エラーログ | デバッグ・監査用 |
シート設計の例:顧客管理システム
マスタシート(顧客一覧)
| A | B | C | D | E | F | G |
|---|---|---|---|---|---|---|
| 選択 | 顧客ID | 会社名 | 担当者名 | メール | ステータス | 登録日 |
| ☑ | C001 | 株式会社ABC | 田中太郎 | tanaka@abc.co.jp | アクティブ | 2024/01/15 |
| ☐ | C002 | 株式会社XYZ | 鈴木花子 | suzuki@xyz.co.jp | アクティブ | 2024/02/20 |
履歴シート(対応履歴)
| A | B | C | D | E |
|---|---|---|---|---|
| 日時 | 顧客ID | 対応種別 | 内容 | 担当者 |
| 2024/06/01 10:30 | C001 | 電話 | 新製品の問い合わせ | 山田 |
| 2024/06/02 14:00 | C002 | メール | 見積もり送付 | 佐藤 |
設定シート
| A | B |
|---|---|
| 項目 | 値 |
| 通知メールアドレス | admin@example.com |
| 自動バックアップ | TRUE |
| 処理状況 | 待機中 |
シート設計のベストプラクティス
1. 1行目はヘッダー固定
データは必ず2行目から開始する
→ GASでの処理が簡単になる
2. A列は選択用チェックボックス
一括処理の対象を選択できるようにする
→ 「選択した行だけ処理」が実現できる
3. IDは自動採番
javascript
// 最大ID + 1 で自動採番
function getNextId(sheet, idColumn) {
const data = sheet.getDataRange().getValues();
let maxId = 0;
for (let i = 1; i < data.length; i++) {
const id = parseInt(data[i][idColumn - 1].toString().replace(/\D/g, '')) || 0;
if (id > maxId) maxId = id;
}
return maxId + 1;
}
4. 日時は自動記録
javascript
// 登録日時を自動記録
sheet.getRange(newRow, 7).setValue(new Date());
5. ステータスはプルダウン
入力規則でプルダウンを設定
→ 入力ミス防止、集計しやすい
入力規則の設定
プルダウンリストの作成
javascript
/**
* プルダウンリストを設定
*/
function setDropdown() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('顧客一覧');
// ステータス列(F列)にプルダウンを設定
const statusRange = sheet.getRange('F2:F1000');
const statusRule = SpreadsheetApp.newDataValidation()
.requireValueInList(['アクティブ', '休眠', '解約'], true)
.setAllowInvalid(false)
.build();
statusRange.setDataValidation(statusRule);
}
チェックボックスの設定
javascript
/**
* チェックボックスを設定
*/
function setCheckboxes() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('顧客一覧');
// A列にチェックボックスを設定
const checkRange = sheet.getRange('A2:A1000');
checkRange.insertCheckboxes();
}
メールアドレスの検証
javascript
/**
* メールアドレス形式を検証
*/
function setEmailValidation() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('顧客一覧');
// E列にメール形式の検証を設定
const emailRange = sheet.getRange('E2:E1000');
const emailRule = SpreadsheetApp.newDataValidation()
.requireTextContains('@')
.setAllowInvalid(true) // 警告のみ
.setHelpText('有効なメールアドレスを入力してください')
.build();
emailRange.setDataValidation(emailRule);
}
カスタムメニューの作成
基本的なメニュー
javascript
/**
* スプレッドシートを開いた時にメニューを作成
*/
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('📊 顧客管理')
.addItem('➕ 新規顧客を追加', 'addNewCustomer')
.addItem('📧 選択した顧客にメール送信', 'sendEmailToSelected')
.addSeparator()
.addItem('📤 CSVエクスポート', 'exportToCsv')
.addItem('📥 CSVインポート', 'importFromCsv')
.addSeparator()
.addSubMenu(ui.createMenu('⚙️ 設定')
.addItem('🔧 初期セットアップ', 'initialSetup')
.addItem('🔄 入力規則を再設定', 'resetValidation'))
.addToUi();
}
メニューアイコンの例
| アイコン | 用途 |
|---|---|
| ➕ | 追加 |
| ✏️ | 編集 |
| 🗑️ | 削除 |
| 📧 | メール |
| 📤 | エクスポート |
| 📥 | インポート |
| 🔄 | 更新・同期 |
| ⚙️ | 設定 |
| 🔧 | セットアップ |
| ▶️ | 開始 |
| ⏹️ | 停止 |
| ✅ | 完了・選択 |
よく使う機能の実装
選択した行を取得
javascript
/**
* チェックボックスで選択された行を取得
*/
function getSelectedRows() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('顧客一覧');
const data = sheet.getDataRange().getValues();
const selectedRows = [];
for (let i = 1; i < data.length; i++) {
if (data[i][0] === true) { // A列がチェックされている
selectedRows.push({
rowIndex: i + 1,
data: data[i]
});
}
}
return selectedRows;
}
/**
* 選択した顧客にメール送信
*/
function sendEmailToSelected() {
const selected = getSelectedRows();
if (selected.length === 0) {
SpreadsheetApp.getUi().alert('顧客を選択してください');
return;
}
selected.forEach(row => {
const email = row.data[4]; // E列:メールアドレス
const name = row.data[3]; // D列:担当者名
MailApp.sendEmail({
to: email,
subject: 'お知らせ',
body: `${name}様\n\nいつもお世話になっております。`
});
});
SpreadsheetApp.getUi().alert(`${selected.length}件のメールを送信しました`);
}
新規データの追加(ダイアログ)
javascript
/**
* 新規顧客追加ダイアログを表示
*/
function addNewCustomer() {
const html = HtmlService.createHtmlOutput(`
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
label { display: block; margin-top: 10px; font-weight: bold; }
input, select { width: 100%; padding: 8px; margin-top: 5px; box-sizing: border-box; }
button { margin-top: 20px; padding: 10px 20px; background: #4285f4; color: white; border: none; cursor: pointer; }
button:hover { background: #3367d6; }
</style>
<label>会社名</label>
<input type="text" id="company" required>
<label>担当者名</label>
<input type="text" id="name" required>
<label>メールアドレス</label>
<input type="email" id="email" required>
<label>ステータス</label>
<select id="status">
<option value="アクティブ">アクティブ</option>
<option value="休眠">休眠</option>
</select>
<button onclick="submitForm()">登録</button>
<script>
function submitForm() {
const data = {
company: document.getElementById('company').value,
name: document.getElementById('name').value,
email: document.getElementById('email').value,
status: document.getElementById('status').value
};
if (!data.company || !data.name || !data.email) {
alert('必須項目を入力してください');
return;
}
google.script.run
.withSuccessHandler(() => {
alert('登録しました');
google.script.host.close();
})
.withFailureHandler(err => alert('エラー: ' + err))
.saveNewCustomer(data);
}
</script>
`)
.setWidth(400)
.setHeight(350);
SpreadsheetApp.getUi().showModalDialog(html, '新規顧客登録');
}
/**
* 新規顧客を保存
*/
function saveNewCustomer(data) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('顧客一覧');
// 新しいIDを生成
const newId = 'C' + String(getNextId(sheet, 2)).padStart(3, '0');
// 新しい行を追加
sheet.appendRow([
false, // 選択チェックボックス
newId, // 顧客ID
data.company, // 会社名
data.name, // 担当者名
data.email, // メールアドレス
data.status, // ステータス
new Date() // 登録日
]);
}
CSVエクスポート
javascript
/**
* CSVファイルとしてエクスポート
*/
function exportToCsv() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('顧客一覧');
const data = sheet.getDataRange().getValues();
// CSVデータを作成
let csv = '';
data.forEach((row, index) => {
if (index === 0) {
// ヘッダー行(A列の「選択」は除く)
csv += row.slice(1).join(',') + '\n';
} else {
// データ行(A列のチェックボックスは除く)
csv += row.slice(1).map(cell => {
// カンマや改行を含む場合はダブルクォートで囲む
if (String(cell).includes(',') || String(cell).includes('\n')) {
return `"${cell}"`;
}
return cell;
}).join(',') + '\n';
}
});
// ダウンロードダイアログを表示
const html = HtmlService.createHtmlOutput(`
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
textarea { width: 100%; height: 200px; font-family: monospace; }
button { margin-top: 10px; padding: 10px 20px; background: #4285f4; color: white; border: none; cursor: pointer; }
</style>
<p>以下のデータをコピーしてCSVファイルとして保存してください:</p>
<textarea id="csv" readonly>${csv}</textarea>
<button onclick="copyToClipboard()">📋 コピー</button>
<script>
function copyToClipboard() {
document.getElementById('csv').select();
document.execCommand('copy');
alert('コピーしました');
}
</script>
`)
.setWidth(500)
.setHeight(350);
SpreadsheetApp.getUi().showModalDialog(html, 'CSVエクスポート');
}
条件付き書式の活用
ステータスによる色分け
javascript
/**
* ステータスに応じて行の背景色を変更
*/
function setConditionalFormatting() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('顧客一覧');
const range = sheet.getRange('A2:G1000');
// 既存のルールをクリア
sheet.clearConditionalFormatRules();
const rules = [];
// アクティブ:緑
rules.push(SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied('=$F2="アクティブ"')
.setBackground('#E8F5E9')
.setRanges([range])
.build());
// 休眠:黄色
rules.push(SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied('=$F2="休眠"')
.setBackground('#FFF8E1')
.setRanges([range])
.build());
// 解約:赤
rules.push(SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied('=$F2="解約"')
.setBackground('#FFEBEE')
.setRanges([range])
.build());
sheet.setConditionalFormatRules(rules);
}
自動化の実装
定期実行(トリガー)
javascript
/**
* 毎日の定期処理を設定
*/
function setupDailyTrigger() {
// 既存のトリガーを削除
ScriptApp.getProjectTriggers().forEach(trigger => {
if (trigger.getHandlerFunction() === 'dailyReport') {
ScriptApp.deleteTrigger(trigger);
}
});
// 毎日9時に実行
ScriptApp.newTrigger('dailyReport')
.timeBased()
.everyDays(1)
.atHour(9)
.create();
}
/**
* 日次レポートを送信
*/
function dailyReport() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('顧客一覧');
const data = sheet.getDataRange().getValues();
// 集計
let activeCount = 0;
let dormantCount = 0;
for (let i = 1; i < data.length; i++) {
if (data[i][5] === 'アクティブ') activeCount++;
if (data[i][5] === '休眠') dormantCount++;
}
// メール送信
const adminEmail = getSettingValue(ss, '通知メールアドレス');
MailApp.sendEmail({
to: adminEmail,
subject: '【日次レポート】顧客管理システム',
body: `
顧客管理システム 日次レポート
■ 顧客数サマリー
・アクティブ: ${activeCount}件
・休眠: ${dormantCount}件
・合計: ${data.length - 1}件
詳細はスプレッドシートをご確認ください。
${ss.getUrl()}
`
});
}
編集時の自動処理
javascript
/**
* 編集時に自動実行
*/
function onEdit(e) {
const sheet = e.source.getActiveSheet();
const range = e.range;
// 顧客一覧シート以外は無視
if (sheet.getName() !== '顧客一覧') return;
// ステータス列(F列)が変更された場合
if (range.getColumn() === 6) {
// 履歴に記録
const historySheet = e.source.getSheetByName('履歴');
const customerId = sheet.getRange(range.getRow(), 2).getValue();
historySheet.appendRow([
new Date(),
customerId,
'ステータス変更',
`${e.oldValue} → ${e.value}`,
Session.getActiveUser().getEmail()
]);
}
}
完成イメージ
メニュー構成
📊 顧客管理
├── ➕ 新規顧客を追加
├── 📧 選択した顧客にメール送信
├── ──────────────
├── 📤 CSVエクスポート
├── 📥 CSVインポート
├── ──────────────
└── ⚙️ 設定
├── 🔧 初期セットアップ
└── 🔄 入力規則を再設定
シート構成
顧客管理システム
├── 📋 顧客一覧(マスタ)
├── 📜 履歴(トランザクション)
├── ⚙️ 設定
└── 📝 ログ
実装のコツ
1. まず小さく作る
❌ 最初から全機能を作り込む
⭕ 必要最小限で動くものを作り、徐々に拡張
2. 設定値はシートで管理
❌ コード内に直接値を書く
⭕ 設定シートで管理し、変更しやすく
3. ログを残す
❌ 何が起きたかわからない
⭕ ログシートに記録し、トラブル時に追跡可能に
4. エラーハンドリング
javascript
// ユーザーに優しいエラー表示
try {
// 処理
} catch (e) {
SpreadsheetApp.getUi().alert('エラーが発生しました: ' + e.message);
addLog(ss, e.message, 'ERROR');
}
まとめ
スプレッドシート業務システムのメリット
| メリット | 説明 |
|---|---|
| 低コスト | 無料で始められる |
| 導入が早い | 即日で運用開始可能 |
| カスタマイズ自由 | 自分で機能追加できる |
| 学習コスト低 | 使い慣れた操作感 |
| データ活用しやすい | そのまま分析・加工可能 |
設計のポイント
| ポイント | 内容 |
|---|---|
| 3層構造 | UI層・ロジック層・データ層を意識 |
| 4シート構成 | マスタ・履歴・設定・ログ |
| 入力規則 | プルダウン・チェックボックスで入力ミス防止 |
| カスタムメニュー | 誰でも使いやすいUI |
| 自動化 | GASで定期実行・自動処理 |
関連記事
もっと詳しく知りたい方へ
「スプレッドシートで業務システムを作りたい」 「既存のExcel業務をGoogleに移行したい」 「GASで自動化を実現したい」
そのようなご要望があれば、お気軽にご相談ください。
AIDXの仕組みを社内で内製化したい方へ
スパルタAIDX研修で、現場で使えるAI実践スキルを短期集中で習得できます。
ベテランの技術継承や業務自動化を自社で実現したい方におすすめです。

AIDXの仕組みを構築を依頼したい方へ
貴社の課題に合わせた最適なソリューションをご提案いたします。
導入事例の詳細や、具体的な実装方法についてもお気軽にご相談ください。



