Notion×スプレッドシート連携で二重管理をやめた設計(月のズレ0件に)

「二重入力、やめました。」Notionを原本、スプレッドシートを集計専用にしてGASで自動同期する仕組みのアイキャッチ画像

結論:Notionを「原本(マスタ)」、スプレッドシートを「集計・計算専用のビュー」と役割を分け、GASでNotion APIから定期的にデータを自動同期する仕組みを作りました。これにより、同じ数字を2箇所に手入力する作業がなくなりました。

この記事でわかること

  • Notion スプレッドシート 連携で二重管理が起きる典型的な原因
  • 「どちらをマスタにするか」の設計方針
  • GAS+Notion APIで自動同期する実装コード
  • 同期後の運用ルール

前回の記事でGASによる新着案件通知を作りましたが、その後Notionの案件管理DBを育てていく中で、金額や案件数をスプレッドシート側にも手入力していることに気づきました。Notionとスプレッドシートを連携させずに併用していると、この二重管理はほぼ必ず起きます。

二重管理が発生していた具体的な業務

項目 二重管理していた頃
案件情報の入力先 Notion + スプレッドシート(両方)
更新のたびに必要な作業 2箇所を手動で編集
月あたりの金額ズレ発生回数 2〜3回
ズレに気づくタイミング 月末の集計時(手遅れ)

片方を更新してもう片方を更新し忘れると、集計結果が実態と合わなくなります。これは「ツールが2つあるから」ではなく、「どちらが正の情報か決めていなかったから」起きる問題でした。

統合の設計方針(どちらをマスタにするか)

今回決めたルールはシンプルです。

  • Notion = マスタ(原本)。案件情報の入力・編集はすべてNotionで行う
  • スプレッドシート = ビュー(集計・計算専用)。直接編集はせず、Notionのデータを自動反映したものを見る・計算するためだけに使う

つまり「どちらにも自由に入力できる」状態をやめて、入力する場所を1つに絞り、もう片方は自動生成される「読み取り専用の写し」にする、という方針です。Notionをマスタにする場合、案件管理DBを育てるためにNotionの有料プランでデータベース機能を強化しておくと、この後の運用が安定します。

Notion×スプレッドシート同期フロー図
Notionを原本に、GASが1日1回スプレッドシートへ一方向で反映する

GAS/Notion APIでの連携実装

NotionのIntegration(APIキー)を発行し、案件管理DBをそのIntegrationと共有した上で、GASから定期的にデータベースをクエリしてスプレッドシートに書き出します。

const NOTION_TOKEN = 'ここにNotion Integrationのシークレットを入れる';
const DATABASE_ID = 'ここに案件管理DBのIDを入れる';
const SHEET_NAME = '案件一覧(同期)';

function syncNotionToSheet() {
  const results = queryNotionDatabase_();
  const sheet = getOrCreateSheet_();

  sheet.clearContents();
  sheet.appendRow(['案件名', 'ステータス', '金額', '締切', 'Notionリンク']);

  results.forEach(page => {
    const props = page.properties;
    const name = props['案件名']?.title?.[0]?.plain_text || '';
    const status = props['ステータス']?.status?.name || '';
    const amount = props['金額']?.number ?? '';
    const deadline = props['締切']?.date?.start || '';
    const url = page.url;

    sheet.appendRow([name, status, amount, deadline, url]);
  });
}

function queryNotionDatabase_() {
  const url = `https://api.notion.com/v1/databases/${DATABASE_ID}/query`;
  const response = UrlFetchApp.fetch(url, {
    method: 'post',
    headers: {
      'Authorization': `Bearer ${NOTION_TOKEN}`,
      'Notion-Version': '2022-06-28',
      'Content-Type': 'application/json'
    },
    payload: JSON.stringify({})
  });
  const json = JSON.parse(response.getContentText());
  return json.results;
}

function getOrCreateSheet_() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(SHEET_NAME);
  if (!sheet) sheet = ss.insertSheet(SHEET_NAME);
  return sheet;
}

トリガーは「時間主導型・1日1回」で十分です。頻繁に更新するなら30分〜1時間おきでも構いません。

つまずいたポイント:Notionのステータスプロパティは、DBの作り方によってstatus型とselect型のどちらかになっており、取得の書き方が変わります。最初select型のつもりでprops['ステータス']?.select?.nameと書いていて値が取れず、Claude Codeで実際のAPIレスポンスをそのまま出力させて初めてstatus型だったと気づきました。自分のDBがどちらの型か、実際のレスポンスを確認してから書くのが確実です。

統合後の運用ルール

  • スプレッドシートの「案件一覧(同期)」シートは直接編集しない(次の同期で上書きされます)
  • 集計・グラフ・請求書作成は、この同期シートを参照する別シートで行う
  • 案件情報の追加・変更は必ずNotion側で行う

導入前後の比較

項目 導入前 導入後
入力箇所 2箇所(Notion+シート) 1箇所(Notionのみ)
月あたりの金額ズレ 2〜3回 0回(運用2週間)
同期作業 手動 自動(1日1回)

よくある質問

Notion APIの無料枠でも使えますか?

Notion APIの利用自体は無料です。今回の同期処理程度のリクエスト数であれば、料金は発生しません。

スプレッドシート側からNotionに書き戻すことはできますか?

技術的には可能ですが、今回はあえて一方向(Notion→シート)に限定しています。双方向にすると「どちらが正か」が再び曖昧になり、二重管理の問題に逆戻りするためです。

Notionのデータベースが複数ある場合はどうすればいいですか?

DATABASE_IDと書き込み先シートをペアにして、同期処理をデータベースの数だけ繰り返す形で対応できます。

次回は、この仕組みをベースに、作業時間の自動集計と時給換算をスプレッドシート側で作っていきます。