Google シートをリレーショナルスキーマに変換する:段階的モデリング計画
Google シートをリレーショナルスキーマに変換する手順を平易に解説:繰り返しグループを見つけ、キーを選び、リレーションをマップして将来のデータの混乱を防ぐ方法。

スプレッドシートがデータベースになると混乱する理由
スプレッドシートは小さなリストには便利です。列をその場で変えたり、どこにでもメモを書いたり、目で見て問題を直したりできます。しかし、ファイルが共有される「唯一の正しい情報源」になると、その自由さは問題になります。
データが増えると同じ問題が何度も現れます。顧客や商品を一元管理していないため重複が出ます。電話番号のように同じ項目で行ごとに値が食い違うこともあります。列にリストが隠れている("Tags", "Products", "Attendees")か、フォーマットが混ざっている("$1,200", "1200", "1.2k")と、フィルタやレポートが面倒になります。
Google シートからリレーショナルスキーマに移すのは安全性のためです。データベースは構造を明確にするので、矛盾を作らずにクエリ、検証、更新ができます。
役に立つ考え方:1 行は現実の「1 つのもの」を表すべきです。もし行が取引(deal)、顧客、商品のリストを同時に表しているなら、後で更新が痛みを伴います。
簡単なテスト:1 行で同じフィールドに 2 つ以上の値が必要になることはありませんか?
- 1 件の注文に複数の商品がある
- 1 件のプロジェクトに複数のチームメンバーがいる
- 1 人の顧客に複数の住所がある
答えが「はい」なら、それは「ワイドな行」の問題ではなく「別テーブル」の問題です。きれいにモデル化すれば、壊れやすい手作業に頼る代わりにフォームや検証を上に構築できます。
まずシートが本当に何を意味しているか定義する
シートは整理されて見えても、人によって意味が違うことがあります。Google シートをリレーショナルスキーマに変換する前に、シートが何を追跡しているのか合意しましょう。
列ではなく成果(アウトカム)から始めます。データはどんな意思決定を支援するべきか:週次の収益レポート、未対応チケットの一覧、フォローアップを割り当てるワークフロー、顧客通話中の簡易検索など。決定を名指しできないフィールドは、データベースに入らないことが多いです。
次に、ヘッダーやメモに隠れている名詞を抜き出します。これらが将来のテーブルになります:customers、orders、products、invoices、tickets、agents、locations。もし列が二つの名詞を混ぜている(例:"Customer + Company")なら、1 箇所に複数のものを保存しています。
定義は早めに合意する
小さな意味の違いが後で大きな手直しになります。基本について明確にしておきましょう:
- 「注文」とは何を指すか(見積もり、支払い済み購入、または両方)?
- 「顧客」とは何か(個人、会社、またはどちらも)?
- 1 つの注文に複数の商品があり得るか?
- 1 つのメールが複数の顧客に属するか?
- 「ステータス」は何を示すべきか(現在の状態か履歴か)?
例:シートが「Order」ごとに 1 行を持ち、Products セルがカンマ区切りリストなら、その行が「チェックアウト」「出荷」「請求書」のどれを表すのかを決めます。選択によってスキーマが変わります。
元のシートのコピーを読み取り専用で固定しておきましょう。新しいテーブルが同じ質問に答えられるか検証するために使います。
構造が見えるようにシートを整える
Google シートをリレーショナルスキーマに変換する前に、シートが「データ」に見えるようにします。データベースは一貫した行と列を必要とします。装飾的なレイアウトはモデル化に必要なパターンを隠します。
セルの結合、複数ヘッダー行、データ範囲内の小計のようなレイアウトの工夫を取り除きます。ヘッダーは 1 行だけにして、その後はレコードの行だけにします。合計が必要なら別の要約タブに置き、実際のレコードと混ぜないようにします。
次に各列のフォーマットを揃えます。データベースは "1/2/24"、"2024-02-01"、"Feb 1" が同じ日付だと推測してくれません。電話番号、通貨、名前も同様です。1 つの形式を選び、どこでも使います。厳しく感じても徹底してください。
短いクリーンアップの実務的な作業:
- 各行が 1 つのものを表しているか確認する(1 注文、1 顧客、1 チケットなど)。
- 空白のスペーサ行や列を削除する。
- "N/A"、"-"、空文字列を統一ルールに置き換える。
- どの列が計算列で、どれが人が入力する列かをマークする。
最後に、セル内に複数の値が含まれる場所(例:"red, blue, green")をフラグします。まだスキーマを直さず、後で別行になることを覚えておくだけで構いません。
繰り返しグループとリストを隠すフィールドを特定する
スプレッドシートのデータモデリングで最大の警告サインは繰り返しです。シートはしばしば "複数のもの" を 1 行に押し込もうとして、列を繰り返したり、セルに複数値を詰め込みます。これは素早い追跡には効きますが、フィルタ、レポート、一貫した更新が必要になると壊れます。
通常「別テーブルにするべき」パターン
次の形を探します:
Item 1,Item 2,Item 3やPhone 1,Phone 2のような番号付きの列。- 「Home」や「Work」向けに重複した住所フィールドのような繰り返しブロック。
- カンマや改行、"and" で値を結合しているセル(例:"Mouse, Keyboard, Monitor")。
- "Approved 2025-01-10" や "Alex (Manager)" のように 2 つの概念が混ざった列。
- 注文行が同時にすべての Order Items を保存しようとしているような、二段階の情報を持つ行。
例:販売トラッカーが Order ID, Customer, Product 1, Qty 1, Product 2, Qty 2 を使っていると、問題に行き当たります。注文によって商品数は 1 個のときもあれば 8 個のときもあります。シートは横に広がり続けるか、データを失い始めます。リレーショナルでは Orders は 1 つのテーブル、Order Items は注文ごとの商品を 1 行で表す別テーブルになります。
セル内のリストは、それぞれを個別のレコードとして扱います。セルが "Email, SMS" とあれば、チャネルをきれいに追跡するために別テーブル(または結合テーブル)が必要です。
概念が混ざった列も静かに問題を引き起こします。各フィールドが 1 つの明確な事実だけを保持するように早めに分割しましょう。
見つけた実体からテーブルを作る
シート内の現実世界のものに名前を付けられたら、それぞれをテーブルにします。スプレッドシートは 1 つの大きなグリッドではなく、小さく目的のはっきりしたリストの集合に変わります。
もし 1 行が 2 つの異なるものの詳細を混ぜているなら、それはおそらく 2 つのテーブルが必要です。販売トラッカーの行が顧客情報(名前、電話)、注文情報(日付、ステータス)、商品情報(SKU、価格)を含んでいるなら、顧客は注文ごとに変わらず、商品は単一の注文に依存しません。分割することで重複編集や不一致を防げます。
最終決定の前に、各テーブルの目的を 1 文で書いてみてください。"and also" を言わずに説明できないテーブルは広すぎることが多いです。
実務ルール:
- 同じものを表し、同じライフサイクルを共有する属性は一緒に保つ(顧客名と顧客メール)。
- 複数回現れる可能性があるものは別テーブルに移す(複数の注文商品、複数の住所)。
- セルにリストがあるなら(カンマ区切り、繰り返し列)、それは別テーブル。
- 二つのフィールド群が異なる理由で変わるなら分ける(注文ステータスと顧客連絡先情報)。
その後、列名は明確かつ一貫性のあるものにします。単語は単純な名詞を選び、"Info" や "Details" のような曖昧なラベルは避けます。
時間が経っても安定するキーを選ぶ
各テーブルの主キーは早めに決めます。良いキーは地味です:変わらない、常に存在する、一行ごとに一意です。
自然キー(現実世界の値)はうまくいくこともありますが、本当に安定している場合のみです。SKU は永続的であることを意図しているので良い自然キーになり得ます。一方でメールは安定しているように見えて、変更されたり、共有されたり、重複が生じたりします。名前、電話番号、住所は変わるし一意とは限りません。
安全なデフォルトは自動生成 ID(customer_id, order_id など)です。実世界の識別子は通常のフィールドとして残し、ビジネスルールに合えばユニーク制約を追加します。メールが変わっても customer_id は変わらず、関連する注文は正しい顧客を指し続けます。
シンプルなキーのルール:
- 実世界の識別子が変わる、欠ける、再利用される可能性があるなら自動 ID を使う。
- 実際に管理していて永続性が保証されている場合のみ自然キーを使う(例:SKU)。
- 重複が悪い場合のみフィールドにユニークを設定する。
- "不明" が有効な状態でない限り NULL を許可しない。
- "ユニーク" が何を意味するか(テーブル内、会社単位、期間単位など)を書き残す。
例:Contacts テーブルでは主キーに contact_id を使い、email はビジネスルール次第でユニークにする。phone は全員が持っているわけではないので空欄を許可してもよい。
推測せずにリレーションをマップする
多くの重大なミスは、物事の関係を推測することから生じます。シンプルなルールを使いましょう:もし 1 行が多くの何かを "所有する" なら、それは 1 対多です。外部キーは "多" の側に置きます。
例:1 人の Customer が多くの Orders を持てるなら、Orders テーブルに customer_id を格納します。Customers に注文番号のカンマ区切りを保持すると、重複や欠損がすぐに出ます。
多対多はスプレッドシートの落とし穴です。1 注文が多くの商品を含められ、1 商品が多くの注文に現れるなら、結合テーブル(line items)が必要です。通常は order_id, product_id、数量や購入時の価格などを持ちます。
1 対 1 の関係は稀です。追加データがオプションであるか、プライバシーやパフォーマンスのために分ける場合(例:User と UserProfile)に意味があります。タブが 2 つあるからといって安易に分けるのは警告サインです。
履歴は別の構造が必要です。値が時間とともに変わるなら(ステータス、価格、住所)、1 つの列を上書きしないでください。履歴テーブルに行として保存しておけば「その日付時点で何が真だったか?」に答えられます。
矛盾を防ぐために十分に正規化する
簡潔に言えば:1 つの事実は 1 か所に保存する。顧客の電話番号が 5 行に出てくるなら、誰かが 4 行を更新して 1 行を忘れるでしょう。
正規化を平易に説明すると:
1NF、2NF、3NF を実務的に
第一正規形(1NF)は各セルが 1 値を持つことを意味します。列に "red, blue, green" や "SKU1|SKU2|SKU3" があるなら隠れたリストです。関連テーブルの行に分けます。
第二正規形(2NF)は主に明細行で現れます。もし OrderItems のキーが (OrderID, ProductID) なら、CustomerName のようなフィールドはそこに置いてはいけません。それは注文に依存するので別の場所にあります。
第三正規形(3NF)は非キーのフィールドが他の非キーに依存してはいけないことを意味します。例:ZipCode と City を保存していて、City が ZipCode で決まるなら不一致のリスクがあります。
簡単な自己チェック:
- 同じ値を複数箇所で編集しないといけないか?
- ある変更で多数の行を更新しなければならないか?
- ID から導けるラベルを保存していないか?
- 合計がそれを出す元の行のすぐそばに保存されていないか?
非正規化が許されるとき
読み取り重視のレポーティングのために非正規化することはありますが、安全に行うならレポート用テーブルは再構築できるコピーとして扱います。正規化されたテーブルを真の情報源に保ちます。
合計や残高、ステータスのような派生値は、明確な再計算ルールがある場合のみ複製します。実務的には:生のトランザクションを保存し、クエリで合計を計算し、パフォーマンス上必要なら合計をキャッシュする、という方針が良いです。
将来の手直しを生むよくある罠
多くの「シートでは動いた」問題はツールのせいではなく意味の設計のせいです。目標は各行が同じやり方で、同じ簡潔なことを言うようにすることです。
よくある罠:
- 名前を ID として使う。"John Smith" は一意ではなく変更されます。生成 ID を使い、表示名はラベルとして扱いましょう。
- リストを 1 セルに詰める。簡単に見えますが検索や検証、集計が壊れます。リストは関連テーブルに置きます。
- 現在の状態と履歴を混ぜる。単一の Status 列は最新の状態と変更履歴の両方を示せません。タイミングが重要ならタイムスタンプ付きのイベントとして保存します。
- 1 つのテーブルが複数の意味を持つように過負荷にする。顧客、ベンダー、従業員を含む Contacts シートは、適用されないフィールドが出てきて混乱します。役割で分けるか、共通の Person テーブルを作って役割別のテーブルを追加します。
- 必須フィールドと任意フィールドを無視する。キー項目が空欄になると結合できない行が生まれます。必須項目を決めて早めに強制しましょう。
Orders テーブルに Item 1, Item 2, Item 3 のような列があるなら、それは繰り返しグループです。Orders テーブルと OrderItems テーブルを計画しましょう。
スキーマを確定する前の短いチェックリスト
スキーマをロックする前に、明確さのための最終確認をします。後の多くのデータ痛は初期の小さなショートカットから来ます。
各テーブルが 1 つの単純な質問に答えるか確認します。"Customers" は顧客を意味するべきで、最新の注文や通話メモまで含めるべきではありません。一文で説明できないテーブルは混ざり過ぎです。
最終チェック:
- 行を一意に特定する列(または列の組み合わせ)を指せるか?名前が変わっても特定できるか?
- セルに複数値があるか(カンマ区切りタグ、複数メール、Item1/Item2 列など)?あれば子テーブルに分ける。
- 各リレーションは意図した外部キーで保存されているか?多対多には結合テーブルがあるか?
- 重要なフィールドにルールがあるか(プロセスを壊す欠損に対して必須、重複が有害ならユニーク)?
- 事実(顧客住所、商品価格、従業員の役割)を 1 箇所で更新できるか?
現実テスト:同じ顧客をわずかに違う綴りで二重登録させるのが簡単なら、より良いキーやユニークルールを追加しましょう。
例:販売トラッカーシートをきれいなテーブルに変える
各行が 1 件の商談(deal)になっている販売トラッカーを想像してください。列は Customer Name、Customer Email、Deal Amount、Stage、Close Date、Products(カンマ区切り)、Notes(時々複数メモが 1 セルに入る)などです。
この 1 行は二つの繰り返しグループを隠しています:products(1 件の商談が多くの商品を含む)と notes(商談に対して多くのメモがある)。セル内のリストはクエリが難しく、矛盾しやすいのでここでの変換は失敗しがちです。
実際の動きに合ったきれいな "アフター" モデルの例:
- Customers (CustomerId, Name, Email)
- Deals (DealId, CustomerId, Amount, Stage, CloseDate)
- Products (ProductId, Name, SKU)
- DealProducts (DealId, ProductId, Quantity, UnitPrice)
- DealNotes (NoteId, DealId, NoteText, CreatedAt)
CustomerId、DealId、ProductId は安定した識別子です。DealProducts は多対多の関係を解決します:1 件の商談に多くの商品があり、1 つの商品が多くの商談に出現します。DealNotes はメモを分離するので "Note 1, Note 2, Note 3" の列が不要になります。
モデリング前は「商品ごとの収益」のようなレポートは文字列を分割し、一貫性のない名前に賭ける作業でした。モデリング後は DealProducts を Deals や Products とジョインするだけで簡単に集計できます。
次のステップ:スキーマから実際のアプリへ
紙の上でスキーマが良さそうに見えたら、実データでテストするために実際のデータベースに移します。すべてを一度にインポートしないでください。まず小さなバッチを読み込み、壊れた部分を直してから繰り返します。
リスクを抑える実務手順:
- テーブルとリレーションを作成する。
- 50~200 行をインポートして合計とレコードのスポットチェックを行う。
- マッピング問題(列ミス、欠損 ID、重複)を修正して再インポートする。
- 安定したら残りを読み込む。
早めに検証ルールを追加して、乱雑なスプレッドシート習慣が戻らないようにします。必須フィールドは必須にし、許される値(ステータスなど)を制限し、書式(日時やメール)を検証し、外部キーで存在しない顧客の注文を作れないようにします。
その後、更新用のシートを使うのはやめましょう。フォームと明確なワークフローがあればデータ保護は格段に楽になります。
もしコードを書かずにスキーマを実用的な内部ツールに変えたいなら、AppMaster (appmaster.io) が役立ちます:テーブルとリレーションを視覚的にモデル化し、同じモデルから本番対応のバックエンド、Web アプリ、ネイティブモバイルアプリを生成できます。
よくある質問
共有される「信頼できる唯一の情報源」としてシートを使っていて、重複や矛盾、集計が面倒になってきたら移行を検討しましょう。カンマ区切りのリストや Item 1/Item 2 のような列、コピー&ペーストで直す手間が常態化しているなら、リレーショナルスキーマで時間を大幅に節約できます。
1 行で同じフィールドに複数の値が必要になるなら、それは繰り返しグループです。例:1 つの注文に複数の商品、1 人の顧客に複数の住所、1 回のイベントに複数の参加者など。これらは子テーブル(または結合テーブル)にするべきで、追加の列やセル内のリストではありません。
元のシートを読み取り専用で固定し、データ範囲からセル結合、複数ヘッダー行、集計行を取り除きます。各列の書式を統一(1 つの日付形式、1 つの通貨表記、空欄の扱いを統一)してからモデリングを始めると、構造が見えやすくなります。
各テーブルのデフォルトは自動生成 ID にするのが安全です。人の名前やメールは変更されたり重複したりするので、customer_id のような不変の ID を主キーにして、メールや SKU は通常のフィールドとして保持します。重複が本当に許されない場合のみユニーク制約を追加します。
所有関係で判断します。1 人の顧客が多くの注文を持てるなら、Orders テーブルに customer_id を置きます。多対多なら結合テーブル(例:OrderItems)を作り、order_id、product_id、数量や購入時の価格を持たせます。
矛盾を防ぐために「事実は一箇所にだけ」保存することを目標にします。完全な正規化は必須ではありませんが、同じ顧客電話番号が複数行に散らばるような重複は避けるべきです。更新は一回で済むように設計しましょう。
セル内のカンマ区切りリストは別行に分けます。たとえば「Email, SMS」はフィルタやバリデーションで扱いにくいので、親行に紐づく個別のレコード(または結合テーブル)として保存します。
現在の状態と履歴を分けます。ステータスや価格が時間とともに変わるなら、変更を時刻付きの履歴テーブルに保存しておけば「先月の状態は何だったか?」といった問いに正確に答えられます。現在値は最新のキャッシュとして別に持っても構いません。
まずは少量(50~200 行程度)をインポートして合計や個別レコードを照合します。マッピングの問題や欠損 ID、重複を直してから残りを読み込むと安全です。プロセスが再現可能になってから一括移行しましょう。
コードを書かずにスキーマを実用的な内部ツールにしたいなら、ノーコード/ローコードツールが役立ちます。AppMaster (appmaster.io) ではテーブルとリレーションを視覚的にモデリングし、同じモデルから本番対応のバックエンド、Web アプリ、ネイティブモバイルアプリを生成できます。


