概要
SQLの歴史や基本構文、各DBの違いなどを勉強を兼ねてまとめてみました。
目次
概要
SQLとは
SQLは「Structured Query Language」の略で、リレーショナルデータベース管理システム(RDBMS)を操作するための言語です。
標準SQL
SQLはANSIやISOによって標準化されており、標準化されたSQLは標準SQLと呼ばれます。標準SQLは主要なRDBMS(Oracle、SQL Server、PostgreSQL、MySQLなど)で利用可能で、その標準SQLからベンダー独自で拡張されたものは「方言」と呼んだりします。
標準SQLに含まれない、代表的なベンダー拡張の例
- Oracle:CONNECT BY(階層問い合わせ)、ROWNUM
- SQL Server:TOP、WITH (NOLOCK)
- PostgreSQL:ILIKE(大文字小文字を無視したLIKE)、RETURNING(※OracleやSQL Serverも対応)
- MySQL:AUTO_INCREMENT(自動採番)、LIMIT(※PostgreSQLも対応)
SQLの歴史
リレーショナルモデルの登場
1970年、IBMの研究者であるエドガー・コッドがリレーショナルデータベースの論文を発表しました。
このモデルは、データを行と列からなる表 (テーブル) で表現し数学の集合のように集めたり組み合わせたりして、条件を論理式で指定して操作するという当時としては革新的な仕組みでした。
当時主流だった階層型DBやネットワーク型DBに比べ、データの独立性や柔軟性が高く、理論的にシンプルな設計が可能になりました。
SQLの誕生
1970年代初頭にIBMの研究者ドナルド・チェンバリンとレイモンド・ボイスによって開発されました。
当初は「SEQUEL」(Structured English Query Language)という名前で、IBMのSystem Rプロジェクト (エドガー・コッドが提唱したリレーショナルモデルを実用化するためのプロジェクト) の一環として設計されました。英語に近い構文で直感的にデータ操作ができることを目指し、SELECT, FROM, WHERE などの言葉が基本構文に採用されました。
その後、商標の問題1で「SQL」に改名され、現在の標準的なデータベース言語として広く普及しました。
リレーショナルデーベースという発想を具体的に実現するために、IBM内でsystem Rというプロジェクトが立ち上げられ、その中でデータを操作するための言語としてSQLも作られた、というイメージです。
設計思想
SQLには設計思想として、「宣言的であること」というものがあります。これは「何をしたいか」を書くだけで、どうやるかはDBMSに任せるという方針です。当時主流だった手続き型言語(COBOLやC言語)とは対照的で、ユーザーはデータ取得の手順を記述せず「何をしたいか」のみ記述します。
処理順などの最適化はDBMSに任せています。
SELECT name FROM users WHERE age > 20;
- 「20歳以上の名前を取得したい」と宣言するだけ。
また、なるべく英語のままの構文とすることで、非技術者でも理解しやすいことを重視しています。例えば、SELECT ... FROM ... WHERE ...は、自然言語の順序に近いものとなっています。これは、当時のIBMの目標「ビジネスユーザーでも使える言語」に沿ったものになります。
基本構文 (DML)
DML (Data Manipulation Language) はテーブル内やビュー内のデータに対して操作できる構文です。テーブル自体やビュー自体に対しての操作はできません。
SELECT文
SELECT 列名1, 列名2, ...
FROM テーブル名
WHERE 条件;
FROM
どのテーブルからデータを取るかを指定しているので、標準SQLでは必ず必要となっています。ただ一部のDB (MySQLやPostgreSQL) では、FROMを省略して定数を返すことができます。
SELECT 1; -- MySQLやPostgreSQLで可能
WHERE
文法上は必須ではないです。省略すると、テーブルの全行が対象になります。
INSERT文
INSERT INTO テーブル名 (列名1, 列名2, ...)
VALUES (値1, 値2, ...);
INTO
標準SQLでは記述が必須となっていますが、一部DB(例:SQL Server、MySQL)では方言として省略可能になっています。
VALUES
通常のINSERTでは必須です。VALUES句で追加するデータを指定するのが基本になっているためです。
ただし、INSERT ... SELECTのように別のテーブルやクエリ結果を挿入する場合はVALUESを使いません。
INSERT INTO users (name, age)
SELECT name, age FROM temp_users;
UPDATE文
UPDATE テーブル名
SET 列名1 = 新しい値1, 列名2 = 新しい値2, ...
WHERE 条件;
SET
何を変更するか指定するため、必ず必要です。
WHERE
文法上は必須ではないですが、実務では必須級です。省略してしまうと、テーブル上のすべての行が更新対象です。
UPDATE users
SET name = 'Taro';
上記の場合、usersテーブルの全員のnameがTaroになるので、ほぼ事故です。
DELETE文
DELETE FROM テーブル名
WHERE 条件;
FROM
どのテーブルを削除するかを指定するので、必ず必要です。
WHERE
文法上は必須ではないですが、実務では必須級です。省略してしまうと、テーブル上のすべてのデータが削除されます。
DELETE FROM users;
上記の場合、usersテーブルの全データが消えます (テーブル自体は残ります) 。
基本構文 (DDL)
DDL (Data Definition Language) はテーブル自体やビュー自体に対しての操作を行います。テーブルやビューのデータに対する操作はできません。
例外として、TRUNCATE TABLEは、テーブルの全データを削除しますが、構造は残ります。これはDMLのDELETEに似ていますが、DDL扱いです。
理由としては、TRUNCATEはテーブルのデータページを再初期化するため、構造変更に近い動作をするためです。
CREATE / ALTER / DROP などのDDL構文は、データベース製品ごとに構文や制約が異なる場合があります。
DDL構文に限った話ではないですが、実務では必ず使用するDBの公式ドキュメントを確認してください。
CREATE文
CREATE TABLE テーブル名 (
列名1 データ型 制約,
列名2 データ型 制約,
...
);
TABLE
この個所は、データベースを作成したい場合はDATABASE、ビューを作成したい場合はVIEWになります。
CREATE DATABASE データベース名;
CREATE VIEW ビュー名 AS
SELECT 列名1, 列名2 FROM テーブル名;
ALTER文
ALTER TABLE テーブル名 操作;
TABLE
この個所は、データベースを変更したい場合はDATABASE、ビューを変更したい場合はVIEWになります。
ALTER DATABASE 変更前のDB名 RENAME TO 変更後のDB名;
ALTER VIEW 変更前のビュー名 RENAME TO 変更後のビュー名;
DBごとにSQLの構文が大きく異なります。(PostgreSQL は上記でOK、MySQL は RENAME DATABASE 不可、Oracle は別手順になります)
よく使う操作
PostgreSQLでの例です。
ALTER TABLE users ADD COLUMN email VARCHAR(100);
ALTER TABLE users DROP COLUMN email;
- DDLの「DROP TABLE ~」のDROPとは違い、こちらはカラムを削除するDROPです
ALTER TABLE users ALTER COLUMN age TYPE SMALLINT;
ALTER TABLE users RENAME COLUMN name TO full_name;
DROP文
DROP TABLE テーブル名;
TABLE
この個所は、データベースを削除したい場合はDATABASE、ビューを削除したい場合はVIEWになります。
DROP DATABASE データベース名;
DROP VIEW ビュー名;
基本構文 (DCL)
DCL (Data Control Language) 実務で直接書くことは少ないです。
権限管理はDBA(データベース管理者)が担当することが多く、開発者や一般ユーザーは触らないケースがほとんどです。
GRANT文
ユーザーやロールに対して、特定の操作権限を与えます。
ロールとは、複数の権限をまとめて管理するための仕組みです。ユーザーに直接権限を付与するのではなく、ロールに権限を付与し、そのロールをユーザーに割り当てることで管理を簡単にします。
GRANT SELECT, INSERT ON users TO user1;
- SELECT, INSERT → 許可する操作
- users → 対象テーブル
- user1 → 権限を与えるユーザー
REVOKE文
付与した権限を取り消します。
REVOKE INSERT ON users FROM user1;
GRANTとREVOKEはセットで覚えると、権限を付与 or 削除ができるようになります。
基本構文 (TCL)
TCL (Transaction Control Language) トランザクションを制御するためのSQLコマンドです。複数のDML操作(INSERT、UPDATE、DELETEなど)をまとめて一括で確定または取り消すときに使います。
COMMIT
トランザクション内で行った変更を確定します。
COMMIT;
これを実行すると、INSERTやUPDATEなどの変更がデータベースに反映されます。
ROLLBACK
トランザクション内で行った変更を元に戻します(最後のCOMMITまで)。
ROLLBACK;
実務での使用例
BEGIN; -- TCL: トランザクション開始(DBによっては START TRANSACTION)
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- DML: データ更新
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- DML: データ更新
COMMIT; -- TCL: 確定(変更を保存)
-- エラーがあれば
ROLLBACK; -- TCL: 取り消し(最後のCOMMITまで戻す)
実務では、SQLスクリプト(複数のSQL文をまとめたファイル)や、ツール(SQL Developer、A5M2など)で複数文を実行します (上から順番に実行されます)。
SQL DeveloperやA5M2のようなツールを使用している場合、そのツール内で自動コミットの設定がONになっていると、DML文のみの記載でも、即時確定になります。逆に、自動コミットOFFになっていると、COMMITするまで確定されません(ROLLBACK可能)。
一般的な仕様として、DML、DDL、TCLなどのSQLで指定したテーブルが存在しない場合はエラーになります。
各DBのSQLの特徴
少しだけですが、Oracle DB、PostgreSQL、MySQL、Accessについて、各DBのSQLの特徴を記載しました。
Oracle DB
空文字('')はNULLとして扱う
SELECT CASE WHEN '' IS NULL THEN 'NULL扱い' ELSE 'NOT NULL' END FROM dual;
-- 結果: 'NULL扱い'
SELECT CASE WHEN '' IS NULL THEN 'NULL扱い' ELSE 'NOT NULL' END;
-- 結果: 'NOT NULL'
空文字をNULLとみなすのは、主要なRDBMSの中ではOracle特有の仕様です。
Oracleは文字列型に「空文字」を保持しない設計を採用しました。そのため、VARCHAR2やCHARに空文字('')をINSERTすると、内部的にNULLとして格納されます。
空文字をNULLとみなす設計を採用した当時はSQL標準がまだ曖昧で、空文字とNULLの区別が厳密ではありませんでした。ただその後に標準SQLでも「空文字とNULLは別物」と定義されました。しかし標準SQLに則った仕様に変更すると、既存アプリへの影響が大きいため、Oracleは標準SQLの準拠より既存アプリとの互換性を優先しました。
この仕様はOracleと他DBの移植時に落とし穴となる場合があり、特に以下で問題になります。
- WHERE句で col = '' を使っていた場合、OracleではNULL判定になるが、他DBでは空文字判定になる
- UNIQUE制約やインデックスで、Oracleは空文字をNULL扱いするので重複を許容するが、他DBは許容しない
PostgreSQL
データ型が豊富で柔軟
配列型(ARRAY)があったり、JSONやJSONBの機能が高機能で、検索・インデックス・演算子が豊富にあります。
標準SQL (ANSI SQL) の準拠度が高い
PostgreSQLは標準SQLの書き方でそのまま動くことが多いですが、Oracleなど独自構文や非標準の方言が多いDBもあります。
下記は2つとも「id=1を起点に、manager_id をたどって部下の階層(自分+すべての下位)を取得する」という論理的に同じ目的のクエリです。
WITH RECURSIVE subordinates AS ( -- subordinates という名前の再帰CTEを定義 (RECURSIVE キーワードがあるので、CTE内で自分自身を参照できる)
SELECT id, name, manager_id -- アンカー部 (再帰CTEの最初の部分で、再帰処理の起点となるデータを取得するSELECT文)
FROM employees
WHERE id = 1
UNION ALL -- アンカー部と再帰部の結果を結合
SELECT e.id, e.name, e.manager_id -- 再帰部
FROM employees e
INNER JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates; -- CTEで集めた全階層の従業員を取得
- 再帰CTE (Recursive Common Table Expression) とは、SQLで自己参照的な処理を繰り返すための仕組みです。特に、階層構造 (ツリーや親子関係) をたどるクエリでよく使われます。
- CTE:
WITH 名前 AS (SELECT ...)で定義する一時的な結果セット。 - 再帰CTE:そのCTEの中で自分自身を参照することで、繰り返し処理を実現。
- CTE:
SELECT id, name, manager_id
FROM employees
START WITH id = 1 - 階層の 起点(ルート) を指定
CONNECT BY PRIOR id = manager_id; - 階層の 親子関係 を定義
上の二つの構文はどちらとも処理をループさせている文ですが、for文のような構文はありません。
これもSQLが「宣言的」である特徴で、ユーザーは「何をしたいか」だけ書き、どうループするかはDBMSに任せています。
MySQL
ENUM 型の簡易サポート
PostgreSQLやOracleにもENUMはありますが、MySQLは非常に簡単にかけます。
CREATE TABLE t (status ENUM('NEW','DONE','CANCEL'));
PostgreSQLはENUM型をサポートしていますが、まず型を作成してからテーブルに使う必要があります。
-- ENUM型を定義
CREATE TYPE status_enum AS ENUM ('NEW', 'DONE', 'CANCEL');
-- テーブルで使用
CREATE TABLE t (
id SERIAL PRIMARY KEY,
status status_enum
);
OracleにはENUM型がないので代替方法としてCHECKを使用する形になります。
CREATE TABLE t (
id NUMBER PRIMARY KEY,
status VARCHAR2(10) CHECK (status IN ('NEW', 'DONE', 'CANCEL'))
);
Access
標準SQL (ANSI SQL) 準拠度は低め
JOINやサブクエリなどはサポートしますが、ウィンドウ関数やCTE(WITH句)は非対応です。
条件式はIIF関数を使用する(CASE式は使えない)
条件式はCASE式は使えず、IIFを使用します。
SELECT IIF(Amount > 100, 'High', 'Low') AS Category FROM Orders;
文字列連結は &(|| ではない)
文字を連結するのは||ではなく&を使用します。
SELECT FirstName & ' ' & LastName AS FullName FROM Customers;
日付型は # で囲む
日付型は#で囲みます。
SELECT * FROM Orders WHERE OrderDate = #2025-09-10#;
その他補足知識
WHERE句の役割
WHERE句は、行を絞り込むための条件を指定するものです。列を特定するものではないです。
条件に一致した行だけが、SELECTなら取得され、UPDATEなら更新され、DELETEなら削除されます。
なので、WHERE句は行フィルターのようなイメージです。
列を指定するのはSELECT文ではSELECT、UPDATE文ではSETの部分です。
SELECT文では、列を指定します。UPDATE文ではSETで更新対象の列を選択します。
- SELECT name, age FROM users → 列を選択
- UPDATE users SET name = 'Taro' → 列を更新
WHEREは「どの行を対象にするか」を決めるだけです。
1行が1件のデータ
そもそもリレーショナルデータベースの基本単位として、1行 (1レコード) が1件のデータです。
1列は1データではなく、1つの属性 (項目名) になります。
そのため、WHERE句でもどのデータを対象にしたいか決めるため、行単位で絞り込んでいます。
LEFT JOIN と RIGHT JOIN のテーブル対応
基本ルール (覚え方)
LEFT JOIN
- FROM 句の左側のテーブルを「全部残す」
- 右側は一致するものだけ
RIGHT JOIN
- FROM 句の右側のテーブルを「全部残す」
- 左側は一致するものだけ
例
SELECT *
FROM Customers AS C
LEFT JOIN Orders AS O
ON C.CustomerID = O.CustomerID;
LEFT JOINなので、Customers (左側) は全件残る
Ordersは一致するものだけ
SELECT *
FROM Customers AS C
RIGHT JOIN Orders AS O
ON C.CustomerID = O.CustomerID;
RIGHT JOINなので、Orders (右側) は全件残る
Customers は一致するものだけ
表を交えた例 (LEFT JOIN)
テーブルA (Customers)
| CustomerID | Name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
テーブルB (Orders)
| OrderID | CustomerID | Product |
|---|---|---|
| 101 | 1 | Laptop |
| 102 | 1 | Mouse |
| 103 | 2 | Keyboard |
SELECT A.CustomerID, A.Name, B.Product
FROM Customers A
LEFT JOIN Orders B ON A.CustomerID = B.CustomerID;
結果
| CustomerID | Name | Product |
|---|---|---|
| 1 | Alice | Laptop |
| 1 | Alice | Mouse |
| 2 | Bob | Keyboard |
| 3 | Carol | NULL |
- LEFT JOINは左表 (Customers) の全行を必ず残すため、左表より行数が少なくなることはない
- この例のように、右表に一致する行が複数ある場合はその分だけ1行増える
- 右表に一致する行がない場合は結合後の右表の列はNULLになる
- JOINは主キー(Primary Key)と外部キー(Foreign Key)で結合するのが基本
-
LEFT JOIN Orders B ON A.CustomerID = B.CustomerIDのCustomerIDのように、結合条件に使う列は、通常「同じ意味を持つキー」なので、双方のテーブルでも列名が同じか、少なくとも似ていることが多い
-
混乱を防ぐコツ
- LEFT / RIGHTはFROMの位置で決まる
- 「JOINのキーワードの左側」ではなく、FROMの左側が基準
LEFT JOIN を基本にする
- 多くの開発者はLEFT JOINを標準にして、必要ならテーブルの順序を変える
- RIGHT JOINは可読性が下がるので、避けることが多い
INNER JOIN は?
- 両方に一致するデータだけ (共通部分)
- LEFT / RIGHTの概念はない
JOINとだけ記載がある場合は?
- JOIN はINNER JOINの省略形。つまり、JOINとだけ書いた場合は両方のテーブルで一致する行だけが結果に含まれる
例
SELECT *
FROM Customers
JOIN Orders
ON Customers.CustomerID = Orders.CustomerID;
上記は以下と同じ意味です。
SELECT *
FROM Customers
INNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerID;
DMLにおける句と文の対応について
DMLには様々な句がありますが、どの句がどの文に対応しているか、簡単にまとめてみました。
SELECT文でしか使えない句
- DISTINCT
- GROUP BY
- HAVING
- ORDER BY
- LIMIT / OFFSET(DBによる)
- WINDOW句(OVER)
INSERT文でしか使えない句
- VALUES
- DEFAULT VALUES
- OUTPUT(SQL Server)
- RETURNING(PostgreSQL)
- ON DUPLICATE KEY UPDATE(MySQL)
- ON CONFLICT DO UPDATE(PostgreSQL)
UPDATE文でしか使えない句
- SET
- OUTPUT(SQL Server)
- RETURNING(PostgreSQL)
DELETE文でしか使えない句
- USING(PostgreSQLでJOINする場合)
- OUTPUT(SQL Server)
- RETURNING(PostgreSQL)
共通で使える句
- FROM(SELECT / DELETE)
- UPDATEでは、FROMは標準SQLでは規定されていないです。ただPostgreSQL や SQL Server は方言として対応しています。(INSERTでのFROMは
INSERT ... SELECT ... FROM ...のようにINSERT内のSELECT文内で使用します)
- UPDATEでは、FROMは標準SQLでは規定されていないです。ただPostgreSQL や SQL Server は方言として対応しています。(INSERTでのFROMは
- JOIN / ON (SELECTで標準、UPDATE/DELETEで一部DBサポート)
- WHERE (SELECT / UPDATE / DELETE)
SQLの実行順序
別記事にまとめました。
SQLの実行順序についてまとめてみた
終わりに
SQLは奥が深い言語ですが、基本を押さえれば応用も徐々にできるようになるかと思います。今回の内容を踏まえて実際に手を動かして試しながら、少しずつ理解を深めていこうと思いました。
-
「SEQUEL」はイギリスの航空機メーカー「Hawker Siddeley社」が商標登録していたため、IBMはこの名称を使い続けることができませんでした。そのため、母音を取り除いて「SQL」 という名前に変更されました。 ↩