レイモンド・ボイスが初期SQL(System R)にもたらした実務的な設計判断──ジョイン、集計、NULLの扱い、最適化、トランザクションなどが、組織で使えるデータベースをどう実現したかを解説します。

レイモンド・ボイスは1970年代のIBMのSystem Rプロジェクトで主要な研究者の一人でした。同プロジェクトは関係データベース理論を実務で使えるものに変える努力をしたものです。もしあなたがSELECTを一度でも書いたことがあり、GROUP BYの恩恵を受けたり、データベースに更新の一貫性を期待したことがあるなら、その多くは当時の設計によって形作られています。
見落としがちなのは、SQLが成功したのは単に関係モデルが美しかったからではない、という点です。初期の設計者たち(ボイスを含む)は常に実務的な問いを投げかけました:現実のデータや締め切り、制約のある組織で関係問い合わせを実用的にするにはどうすればいいか? 本稿はそうした実務的選択に焦点を当てます。アナリスト、開発者、ビジネスチームがPhDを必要とせず一つのシステムを共有できるようにした機能群です。
関係理論は多くを約束しました:データを表に保存し、宣言的に質問し、レコードを手作業でたどらない。が、組織は紙の上だけで動くわけではありません。給与ファイル、在庫リスト、混乱したコード、欠損レコード、そして「レポートを出す」という圧力の下で、毎回プログラムを書き換える余裕はありません。
そのギャップ――優雅なアイデアと実際に使えるシステムの間――で初期のSQLは存在価値を示しました。研究者たちは単に関係データベースが存在可能であることを証明するだけでなく、実際のワークロードや利用者と接触しても生き残れることを示さねばなりませんでした。
IBMのSystem Rプロジェクトはその試金石でした。関係モデルを実装し、ベンチマークし、共有機で運用可能にするために、ストレージ構造、クエリプロセッサ、並行制御、そして教えられる言語を含む完全なチェーンを構築しました。
初期のSQLは当初SEQUEL(Structured English Query Language)として知られていました。名前は目的を示していました:ビジネスユーザーが慣れている書き方に近いクエリ構文を目指しつつ、システムが正確に実行できる操作へマッピングすることです。
System Rは現実的な制約下で構築され、それが設計の規律を強めました:
これらの制約が、読みやすさとルール化された厳格さのバランスを取る方向へSQLを誘導し、ジョイン、集計、トランザクション安全性といった機能が実務でも使えるようになりました。
初期のSQLが成功したのは、関係理論と一致しただけではなく、「使いやすさ」をコア要件としたからです。ボイスとSystem Rチームは、クエリが人にとって読み書き・レビュー・保守可能であることを重視しました。
SQLは複数の異なる利用者層が同じデータを共同利用することを想定して設計されました:
この混合が、手続き的な低レベルのコードではなく「select these columns from these tables where…」のような構造化された要求スタイルへSQLを押しやりました。
実務的なクエリ言語は引き継ぎに耐えねばなりません:レポート用クエリが監査用に使われ、運用クエリがダッシュボードのベースになり、別の人が数ヶ月後にそれを引き継ぐことになります。SQLの宣言的スタイルはこの現実を支えます。どのように取得するか(手順)を示すのではなく、何を欲しいかを示し、データベースが実行計画を決めます。
使いやすさを優先するにはトレードオフを受け入れる必要がありました:
SQLがこなすべき仕事は、定期レポート、追跡可能な監査、運用クエリの信頼性などです。目的は理論的な優雅さではなく、データを扱う担当者にとって実用的であることでした。
SQLの早期成功は巧妙なクエリ構文だけでなく、組織が自分たちのデータをどう捉えるかを簡単に説明できる点にも依ります。テーブルモデルは説明しやすく、ホワイトボードで書きやすく、チーム間で共有しやすいからです。
テーブルは顧客や請求書、出荷のように1種類の事物に関するレコードの集合を名前付きで表します。\n\n行は1件のレコード(1顧客、1請求書)、列はその属性(customer_id, invoice_date, total_amount)です。このグリッドの比喩はリストやフォーム、レポートという多くの業務ユーザーの思考と合致します。
スキーマはテーブル名、列名、データ型、関係性などの合意された構造です。「売上データがある」だけでなく、「売上とは何を意味し、どう格納するか」を明らかにします。
一貫した命名と型は単なる書類仕事ではなく、微妙な不一致を避けるための手段です。あるシステムが日付を文字列で保持し、別のシステムが本当の日付型を使うと、レポートは食い違います。複数部署が“status”の意味を違えて使うと、ダッシュボードは意見の衝突でしかなくなります。
スキーマが明示されていれば、翻訳なしで調整できます。アナリストはプロダクトマネージャーがレビューできるクエリを書けます。財務は運用と数値を突き合わせられます。新しいチームがシステムを使い始めるとき、スキーマが地図の役割を果たします。
初期SQLの選択は現実に由来します:データ品質は様々で、フィールドは時間とともに増え、要件は途中で変わります。スキーマは安定した契約を提供しつつ、列を追加したり型を厳密にしたり制約を導入して不正なデータの拡散を防げるようにします。
主キーやチェック制約のような制約は「そうあってほしい」という希望をデータベースが enforcement できるルールに変えます。
SQLの永続的なアイデアの一つは、ほとんどの質問を一貫した文の形で表せることです。初期のSQL設計者たちは(ボイスも含めて)人が学び認識しやすいクエリの“形”を好みました:SELECT … FROM … WHERE …。
この予測可能な構造は思ったより重要です。すべてのクエリが同じ方法で始まれば、読み手はいつも同じ順でスキャンできます:
この一貫性は教育、コードレビュー、引き継ぎに役立ちます。財務のアナリストは、運用部門のレポートが何をしているかを、書いた人でなくても概ね理解できることが多いのです。
日常業務の多くは次の二つの操作でまかなえます:
例:営業マネージャーが「今四半期に開設された有効アカウントを一覧にして」と言った場合、SQLでは少数のフィールドを選び、テーブルを指定し、日付とステータスでフィルタをかければよく、レコードを逐次検索して出力するカスタムプログラムは不要です。
核となる形が読みやすく合成可能だったため、ジョイン、集計、ビュー、トランザクションといった高度な機能があってもユーザーに複雑な手続き型コードを強制しませんでした。単純なレポートクエリから始めて、徐々に機能を積み上げられます。
現実の組織はすべてを一つの巨大なテーブルに入れておくわけではありません。顧客情報は注文情報とは異なる頻度で変わります。情報を分割すると重複が減りエラーも減りますが、答えを得るためにはその分割を再び結合する必要があります。
例として二つのテーブルを考えます:
「顧客名付きのすべての注文」を得たいなら、注文と顧客を識別子で一致させるジョインが必要です。
SELECT c.name, o.id, o.order_date, o.total
FROM orders o
JOIN customers c ON c.id = o.customer_id;
この一文で、アプリケーションコードで手作業でデータを縫い合わせる必要がなくなります。
ジョインは現実の雑さを露呈させます。
顧客に多数の注文がある場合、結果には顧客名が何度も現れます。これは保存上の「重複」ではなく、1対多の関係を結合した結果の見え方です。
欠損マッチはどうなるか?注文に存在しないcustomer_idがある(不正データ)と、INNER JOINではその行は静かに落ちます。LEFT JOINでは注文が残り、顧客側の列がNULLになります:
SELECT o.id, c.name
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id;
ここでデータ整合性が重要になります。キーや制約は単なる理論上の要請ではなく、孤立した("orphan")行がレポートの信頼性を損なうのを防ぐ実務的手段です。
初期のSQLが選んだ重要な方針はセットベース操作を奨励することでした:どの関係を欲しいかを宣言すれば、データベースが効率的にそれを生成する方法を決めます。注文を一件ずつループして対応する顧客を探す代わりに、一度だけマッチ条件を述べれば済むのです。このシフトこそが組織規模で関係クエリが実用的になる理由です。
組織は単にレコードを保存するだけでなく、答えを必要とします。今週いくつ注文を出荷したか?配送時間の平均はキャリアごとにどうか?どの製品が最も売上を生んでいるか?初期SQLが成功した理由の一つは、こうした日常的な「レポートの質問」を第一級の仕事として扱ったことです。
集約関数は多くの行を一つの数値に変えます:COUNTは件数、SUMは合計、AVGは平均、MIN/MAXは範囲を示します。これだけだと全体の要約にしかなりませんが、GROUP BYはそれを有用にします。カテゴリごと(店舗別、月別、顧客セグメント別)の1行を出せるからです。
SELECT
department,
COUNT(*) AS employees,
AVG(salary) AS avg_salary
FROM employees
WHERE active = 1
GROUP BY department;
WHEREはグルーピングの前に行を絞る(どの行を含めるか)。\n- HAVINGは集計後にグループを絞る(どの集約を残すか)。SELECT department, COUNT(*) AS employees
FROM employees
WHERE active = 1
GROUP BY department
HAVING COUNT(*) >= 10;
ほとんどのレポートバグは実は「粒度」のバグです:誤ったレベルでグルーピングしていることが多い。ordersをorder_itemsとジョインしてからSUM(order_total)をすると、注文ごとの合計がアイテム数分だけ掛けられてしまうことがあります。良い習慣は「ジョイン後に一行が何を表すか?」を問い、そこに合わせて集計することです。
また、GROUP BYに含まれていない列をそのままSELECTするのは、多くの場合レポート定義が不明瞭であることを示します。まずグルーピングキーを決め、それに合った指標を選びます。
実務データには欠落が多く含まれます。顧客レコードにメールがない、出荷に配送日がまだない、古いシステムがあるフィールドをそもそも収集していないなど。欠損をすべて「空」や「ゼロ」と扱うと集計が静かに壊れることがあるため、初期SQLは「分からない」という明示的領域としてNULLを導入しました。
NULLは「欠損(または該当なし)」を意味し、単純な比較の多くは真でも偽でもなく不明になります。例:salary > 50000はsala ryがNULLだと不明です。またNULL = NULLも不明です。
IS NULL / IS NOT NULLを使ってチェックします:
WHERE email IS NULLはメールがない行を見つける。WHERE email = NULLは期待通り動かない。レポートで安全な代替を出すにはCOALESCEを使います。
SELECT COALESCE(region, 'Unassigned') AS region, COUNT(*)
FROM customers
GROUP BY COALESCE(region, 'Unassigned');
フィルタで不明を誤って落とさないよう注意してください。WHERE status <> 'Cancelled'はstatusがNULLの行を除外します(比較が不明になるため)。ビジネスルールが「キャンセルでないか、あるいは不明なら含める」なら、明示的に
WHERE status <> 'Cancelled' OR status IS NULL
のように書きます。
NULLの振る舞いは合計、コンバージョン率、コンプライアンスチェック、データ品質ダッシュボードに影響します。NULLを意図的に扱うチームは、不意のクエリ挙動ではなく現実のビジネス意味に合ったレポートを作れます。
ビューは仮想テーブルの定義を保存するもので、データをコピーせずに結果セットの作り方を保管します。誰でも通常のSELECT–FROM–WHEREでそれを参照できます。
ビューは複雑なジョインやフィルタを繰り返し書かずに済ませ、標準化した定義を提供します。例えば「アクティブ顧客」が購入30日以内か、契約があるか、最近ログインしたかで定義が変わる場合、ビューでその規則を一箇所にエンコードできます。
CREATE VIEW active_customers AS
SELECT c.customer_id, c.name
FROM customers c
WHERE c.status = 'ACTIVE' AND c.last_purchase_date >= CURRENT_DATE - 30;
これによりダッシュボードやエクスポート、アドホッククエリが一貫した定義を参照できます。
ビューは、ユーザーに見せる列を限定したキュレーテッドなインターフェースを提供することで高レベルなアクセス制御を助けます。生テーブル全体への広い権限を与えるより、役割に必要なフィールドだけを露出するビューにアクセスを与える方が安全です。
運用上の本当の利点は保守性です。元のテーブルが進化(列の追加、名前変更、ビジネスルールの更新)しても、ビュー定義を一箇所更新すれば済みます。これにより「多くのレポートが一度に壊れる」問題を減らし、SQLベースのレポーティングを信頼できるものにします。
SQLは読み取りの優雅さだけでなく、多数の人やプログラムが同時に変更を加えるときに書き込みを安全にする必要がありました。実際の組織では常に更新が入ります:注文、在庫の変動、請求書の記録、座席予約。もし更新が部分的に成功したり上書きされたりすると、データベースは信頼できる情報源でなくなります。
トランザクションはデータベースが一つの作業単位として扱う変更の束で、「すべての変更が適用されるか、全く適用されないか」のどちらかになります。途中で障害があればロールバックして開始前の状態に戻せます。
複数ステップの業務(請求支払で残高を減らし、支払エントリを記録し、総勘定元帳を更新する等)は、これがないと不整合になります。
個々のユーザーの変更が正しくても、同時に操作すると不正な結果になることがあります。予約システムの例:
分離性がなければ両方の更新が成功し、二重予約が起こります。トランザクションと整合性制御は、各トランザクションが一貫したデータの見え方を得て、衝突が予測可能に処理されるようにします。
これらの保証は会計精度、監査可能性、日常の信頼性を可能にします。高負荷下でも更新が一貫していると証明できるデータベースは、単なるアドホックな問い合わせの場ではなく、給与・請求・在庫・コンプライアンス報告に十分使える基盤になります。
SQLの早期の約束は「問いかけができる」だけでなく、データが増えても問いかけ続けられることでした。ボイスとSystem Rチームは性能を重視しました。語義だけではなく実務的に使えてこそ意味があるからです。
5,000行のテーブルから50行返すクエリは、全走査でも即座に感じられるかもしれません。しかし同じテーブルが5千万行になると、全走査はI/Oの問題で数分に伸びることがあります。
SQLの文自体は同じでも、データベースがorder_id = 12345を見つけるための「方法」のコストが変わるのです。
SELECT *
FROM orders
WHERE order_id = 12345;
インデックスは本の索引のように該当箇所へ直接飛ぶ仕組みです。テーブル全体をめくる代わりに該当行を素早く見つけられます。
しかしインデックスは無料ではありません。ストレージを消費し、書き込みを遅くし、すべてのクエリに有効とは限りません。大量の行を要求するクエリでは、インデックスを何度もたどるよりテーブルスキャンの方が速い場合もあります。
初期SQLシステムの重要な選択の一つは、実行戦略をデータベースに任せることでした。オプティマイザはコストを見積もり、インデックス利用かスキャンか、結合順序はどうするかを選びます。これにより全ユーザーがデータベース技術者のように毎回手で調整する必要がなくなります。
夜間や週次のレポートを回すチームにとっては、理論的な優雅さよりも予測可能な性能が重要です。インデックスとオプティマイザの組合せにより、レポート実行時間を見積もり、ダッシュボードを応答性のある状態に保ち、「先月は動いたのに今月は遅い」という問題を減らせます。
レイモンド・ボイスの初期SQLへの取り組み(System R期に形作られたもの)は、チームが受け入れやすい選択を優先したため成功しました:読みやすく宣言的な言語、組織が既に使っている表とスキーマのモデル、欠損値のような現実の雑事を理想論より先に扱う姿勢。これらの選択は技術的にだけでなく社会的にもスケールするために長持ちしました。
初期の妥協点はいまだに日常の仕事に現れます:
命名規約や結合スタイル、日付処理、「active」「revenue」「customer」の定義など、あいまいさを減らす慣習を決めてください。重要なクエリはプロダクトコードのように扱い、ピアレビュー、バージョン管理、軽量テスト(行数、ユニーク性、既知の答え)を適用しましょう。ビューやキュレートされたテーブルで定義を共有し、指標が分裂しないようにします。
クエリを内部ツール(管理パネル、ダッシュボード、運用ワークフロー)に組み込む場合も同じ原則が当てはまります:共有定義、制御されたアクセス、ロールバックの方針を持つこと。
Koder.aiのようなプラットフォームはこの「実務的SQL」の系譜を反映しており、チャット駆動のワークフローでWeb/バックエンド/モバイルアプリを作れるようにしつつ、従来の基盤(フロントはReact、バックエンドはGo + PostgreSQL、モバイルはFlutter)とデータベース時代の規律(プランモード、スナップショット、ロールバック)を組み合わせています。
レイモンド・ボイスはIBMのSystem Rプロジェクトで重要な研究者の一人で、関係データベースの概念を実務で使える共有システムへと変える手助けをしました。彼の影響は、読みやすいクエリ、実務的な欠損データ処理、多人数同時利用時の信頼性や性能を支える機能設計など、「理論だけでなく実際に使える」SQLを作ることにあります。
System Rは1970年代のIBMの研究プロジェクトで、関係モデルをエンドツーエンドで実装し、運用できることを実証しました:ストレージ、クエリ処理、並行制御、そして教えやすい言語を含む全体系です。限られた計算資源、共有ワークロード、不完全な業務データといった現実的な制約と直面したことで、SQLの設計は実務で通用するものに磨かれました。
もともとSEQUELは「Structured English Query Language」の略で、ビジネスユーザーや開発者が素早く学べるように英語風の文法を志向していました。「英語らしさ」は、関係問い合わせを扱いやすく、同時に正確に実行できる操作へとマッピングするという目標を示していました。
SELECT–FROM–WHEREという一貫した形は、クエリを素早く読み、レビューし、保守できるようにします。
SELECT: 返したい項目FROM: どこから来るかWHERE: どの行を対象にするかこの予測可能性は、教育・引き継ぎ・再利用を支え、アドホックなレポートが長期的な運用ロジックへ発展する際に重要です。
ジョインは正規化された複数のテーブル(例:customersとorders)を結合して日常的な質問に答える手段であり、アプリケーションコードで手作業でデータを縫い合わせる必要をなくします。
実務的な点としては:
INNER JOINで行が消え、LEFT JOINではNULLで残るGROUP BYは生の行を選択した粒度で要約(件数、合計、平均など)に変えることで、レポーティングを簡単にします。
簡単なルール:
WHEREHAVINGよくある落とし穴は誤った粒度でのグルーピングやジョイン後の二重集計です。例えば、をと結合してからすると、アイテム数分だけ合計が膨らむことがあります。
NULLは「欠損(不明)」を表し、「空文字」や「ゼロ」とは異なります。これにより三値論理(真・偽・不明)が導入され、多くの比較で結果が「不明」になります。
実務的な対策:
IS NULL / IS NOT NULL を使う(= NULLは期待通り動かない)COALESCEを使う例:
ビューは保存されたクエリで、仮想テーブルのように振る舞います。チームが複雑な結合やフィルタを何度も書かずに再利用でき、定義を一箇所で標準化できます。
利点:
例:
トランザクションは複数の変更を一つの作業単位として扱い、「すべて実行されるか、全く実行されないか」を保証します。これにより、途中で失敗があっても整合性のある状態に戻せます。
並行利用の問題(例:ダブルブッキング)を防ぐために、トランザクションは分離レベルを使って各操作が一貫したデータの見え方を得るように調整します。会計・請求・在庫など、正確さと監査可能性が求められる用途では不可欠な機能です。
宣言的であっても、同じクエリが速かったり遅かったりすることがあります。インデックスは本の索引のように素早く該当箇所へ飛べる仕組みですが、ストレージを使い書き込みを遅くするというコストもあります。
Optimizer(最適化器)はスキャンかインデックス利用か、結合順序はどうするかなどを自動で決めることで、ユーザーが毎回実行戦略を考えなくて済むようにします。これがあるから、大規模データでも定期レポートやダッシュボードの応答性を保てます。
ordersorder_itemsSUM(order_total)SELECT COALESCE(region, 'Unassigned') AS region, COUNT(*)
FROM customers
GROUP BY COALESCE(region, 'Unassigned');
フィルタが不明を意図せず排除してしまうことがあるので、必要なら明示的に... OR status IS NULLのように書きます。
CREATE VIEW active_customers AS
SELECT c.customer_id, c.name
FROM customers c
WHERE c.status = 'ACTIVE' AND c.last_purchase_date >= CURRENT_DATE - 30;