データベース●●○○○

SQL

SQL ・ えすきゅーえる

リレーショナルデータベースへの問い合わせ言語。欲しい結果を宣言し、取り方はDBに任せる。

✦ 30秒で試す — 航海取り出し方を1行も書かずに、欲しいデータが届くこの文を送ると、データベースはどうする?予想してみる

概要

SQL(Structured Query Language)は、RDBMSに対してデータの問い合わせや更新を行うための言語です。最大の特徴は「どうやって取るか」の手順ではなく「何が欲しいか」という条件を書く宣言的なスタイルにあります。SELECT name FROM users WHERE age >= 20 と書けば、データベースが最適な取り出し方を自分で考えて結果を返してくれます。

PostgreSQL、MySQL、SQLiteなど製品は違っても、基本文法はISO/IECで標準化されたほぼ共通のSQLです。一度身につければデータベースをまたいで通用する、ソフトウェア開発で最も息の長いスキルの一つと言えます。バックエンド開発だけでなく、データ分析やBI(可視化)ツールの世界でも共通言語として使われています。

なぜ生まれたか

リレーショナルモデルが提唱される以前のデータベースでは、データを取り出す手順——どのレコードからポインタをたどり、どの順で読むか——をプログラマが逐一書く必要がありました。データの物理的な配置を知らなければ問い合わせが書けず、配置が変わればプログラムも壊れる。データを使いたいだけの人にとって、あまりに敷居が高い世界でした。

1970年代、IBMのサンノゼ研究所でリレーショナルモデルを実装する実験システム System R が開発され、その問い合わせ言語として Donald Chamberlin と Raymond Boyce が SEQUEL(1974年。商標の問題で後に SQL と改名)を設計しました。目標は「手続きを知らなくても、欲しい結果を英語に近い形で宣言できること」です。取り出し方の最適化はデータベース側(オプティマイザ。実行計画を立てる部品)の仕事になり、人間は「何が欲しいか」に集中できるようになりました。この分業こそがSQLの発明の本質で、1986年に ANSI、翌1987年に ISO の標準となって以降、リレーショナルデータベースの事実上の共通語であり続けています。

詳細

問い合わせの基本形と評価の順

SQL の中心は SELECT 文です。FROM で対象のテーブルを指定し、WHERE で行を絞り込み、SELECT で欲しい列を選びます。GROUP BY と集計関数(COUNT・SUM など)でグループごとに集計し、HAVING で集計後のグループを絞り、ORDER BY で並べ替え、LIMIT(標準 SQL では FETCH FIRST)と OFFSET で返す行数を切ります。そしてRDBMSの真価を引き出すのが JOIN で、外部キーでつながった複数のテーブルを結合し、「顧客名と注文内容を並べる」といった問い合わせを1文で書けます。

FROM・JOIN(表を決めて結合)WHERE(1行ずつ絞る)GROUP BY(束ねる)HAVING(束ごと絞る)SELECT(列を選び、別名を付ける)ORDER BY(並べ替える)LIMIT・OFFSET(行数を切る)
SELECT 文の論理的な評価の順。書く順(SELECT が先頭)とは違う

つまずきどころは、書く順(SELECT が先頭)と論理的な評価の順(FROM が先)が一致しないことです。「WHERE では SELECT で付けた別名が使えない」のは、WHERE の段ではまだ SELECT が評価されていないからで、逆に ORDER BY では別名が使えます。「集計した合計で絞りたいのに WHERE に書くとエラーになる」のも同じ理由で、WHERE は束ねる前に1行ずつ見る段、HAVING は束ねた後にグループごとに見る段です。なお、これは結果の意味を決める論理的な順であって、実際の処理の順はオプティマイザが自由に組み替えます。

句の条件を変えて、小さな注文表がどの段で落ち、束ねられ、並び替わるかを確かめてください。

⚡ 体験: 評価の順に行を流す
書く順(丸数字 = 評価の順)5SELECT 店舗, SUM(金額) AS 合計1FROM 注文2WHERE 金額 >= 500 ▾3GROUP BY 店舗4HAVING (なし) ▾6ORDER BY 合計 DESC ▾7LIMIT 2 ▾評価の順(押すとその段の表を見る)FROMWHEREGROUP BYHAVINGSELECTORDER BYLIMITFROM: 注文テーブルの全8行を読む店舗商品金額渋谷コーヒー480新宿ケーキ620渋谷ケーキ650池袋コーヒー450新宿コーヒー500渋谷サンド720池袋ケーキ600新宿サンド700
○ WHERE で行が落ちた○ HAVING で束ごと落ちた○ WHERE で別名が使えない○ LIMIT で上位だけ残した

左の SQL の句(▾ の付いた条件)を押して変えてください。その句の段から、注文表が評価の順に流れ直します。

4つの役割

SQLは役割ごとにいくつかの部分言語に分かれています。データを読み書きするDML(SELECT / INSERT / UPDATE / DELETE)、テーブルなどの構造を定義するDDL(CREATE / ALTER / DROP)、権限を制御するDCL(GRANT / REVOKE)、そしてトランザクションを制御するTCL(BEGIN / COMMIT / ROLLBACK)です。日常の開発で最も触れるのはDMLですが、スキーママイグレーション(スキーマ変更)ではDDLを、整合性が重要な処理ではTCLを使います。なお、PostgreSQL のように DDL もトランザクションの中で取り消せる製品と、MySQL のように DDL を実行すると暗黙にコミットされる製品があり、マイグレーションの失敗時の後始末が変わります。

SQLDML ─ データ操作SELECT / INSERTUPDATE / DELETEDDL ─ 構造定義CREATE / ALTERDROPDCL ─ 権限制御GRANT / REVOKETCL ─ Tx制御BEGIN / COMMITROLLBACK
SQLを構成する4つの部分言語。日常の開発で最も触れるのはDML

実行計画とパフォーマンス

宣言的な言語であるがゆえに、同じ結果を返すSQLでも実行のされ方は大きく変わります。データベースはSQLを受け取ると、統計情報をもとに「どのインデックスを使うか」「テーブルをどの順で結合するか」を決めた実行計画を立てます。EXPLAIN 文でこの計画を確認でき、「インデックスが使われずテーブル全体を走査していた」といった遅さの原因を突き止められます。SQLが書けることと、速いSQLが書けることの間には、この実行計画を読む力の差があります。

実務での落とし穴

実務上、最も重大な落とし穴がセキュリティです。ユーザーの入力を文字列連結でSQLに埋め込むと、入力に紛れ込ませた命令が実行されるSQLインジェクションの脆弱性になります。必ずプレースホルダ(パラメータ化クエリ)を使い、値と命令を分離するのが鉄則です。テーブル名や ORDER BY の列名のようにプレースホルダにできない部分は、許可した名前の一覧から選ぶ形にします。

また、アプリケーションからはORMを通じてSQLを間接的に発行することが多くなりましたが、ORMが生成するSQLの良し悪しを判断するにも、結局は素のSQLと実行計画を読む力が必要です。方言(LIMIT句の書き方や日付関数など製品ごとの差異)はあるものの、標準SQLの基礎はどの現場でも通用します。「まず標準的な書き方を身につけ、必要になったら方言を調べる」という学び方が効率的です。

試問

カフェの注文表(3店舗・8件)に SQL で問い合わせます。句の条件(▾ の付いた部分)を押すと選択肢が順に切り替わり、注文表がデータベースの評価の順(FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT)に流れ直して、各段で行が落ちたり束ねられたりします。GROUP BY 店舗 で店舗ごとに束ね、SUM(金額) が店舗の合計です。

✦ 命題挑戦中

すべての注文の合計が1500円以上の店舗だけを、LIMIT で切らずに、合計の大きい順に返せ

  • LIMIT で切らない(条件に合う店舗は何件でも全部返す)
  • 返るのが、全注文の合計が1500円以上の店舗ちょうど(合計の値も全注文ぶん)
  • 合計の大きい順に並ぶ
⚡ 体験: 評価の順に行を流す
書く順(丸数字 = 評価の順)5SELECT 店舗, SUM(金額) AS 合計1FROM 注文2WHERE 金額 >= 500 ▾3GROUP BY 店舗4HAVING (なし) ▾6ORDER BY 合計 DESC ▾7LIMIT 2 ▾評価の順(押すとその段の表を見る)FROMWHEREGROUP BYHAVINGSELECTORDER BYLIMITFROM: 注文テーブルの全8行を読む店舗商品金額渋谷コーヒー480新宿ケーキ620渋谷ケーキ650池袋コーヒー450新宿コーヒー500渋谷サンド720池袋ケーキ600新宿サンド700

左の SQL の句(▾ の付いた条件)を押して変えてください。その句の段から、注文表が評価の順に流れ直します。