第14章 SQLデータ管理の基礎

LPIC-1 105.3 相当

Webアプリケーションの多くはデータベースと連携して動いており、Linux管理者もトラブル調査やデータ確認のために、簡単なSQLを読み書きできると役立つ場面が多くあります。この章では、RDBMS(リレーショナルデータベース管理システム)の基本操作を扱います。

なぜLinux管理者がSQLを知る必要があるか

「アプリケーションからデータが正しく登録されているか確認したい」「特定の条件のレコードだけ削除したい」といった場面で、アプリケーションのコードを読まなくても、データベースに直接SQLを発行して確認・対処できると、調査のスピードが大きく変わります。LPIC-1でも、SQLの基本文法とMySQL/MariaDB・PostgreSQLの基本的なCLI操作が出題範囲に含まれています。

SELECTの基本

データを取得する基本のSQLはSELECT文です。

-- usersテーブルから、名前とメールアドレスを取得する
SELECT name, email FROM users WHERE age >= 20 ORDER BY name LIMIT 10;
句役割
FROM対象のテーブルを指定する
WHERE取得する行の条件を絞り込む
ORDER BY結果を並べ替える(既定は昇順、DESCで降順)
LIMIT取得する行数の上限を指定する
DISTINCT重複した行を除いて取得する

WHERE句では、=・<>(等しくない)・>・<などの比較演算子や、AND・OR・NOTといった論理演算子を組み合わせられます。文字列の部分一致にはLIKEとワイルドカード(%は任意の文字列、_は任意の1文字)を使い、値がNULL(未設定)かどうかはIS NULL / IS NOT NULLで判定します(= NULLとは書けない点に注意してください)。

集計とグループ化

GROUP BYを使うと、指定した列の値ごとにグループ化した上で、COUNT(件数)、SUM(合計)、AVG(平均)、MAX・MIN(最大・最小)といった集計関数を適用できます。グループ化した結果をさらに条件で絞り込みたい場合は、WHEREではなくHAVINGを使います。

-- 部署ごとの人数を集計し、5人以上の部署だけを表示する
SELECT department, COUNT(*) AS cnt FROM users GROUP BY department HAVING COUNT(*) >= 5;
よくある間違い: WHEREは集計前の「行」に対する条件、HAVINGは集計後の「グループ」に対する条件です。集計結果(COUNT(*)など)に対する条件をWHEREに書くとエラーになります。

JOIN(複数テーブルの結合)

実際のデータベースでは、ユーザー情報と注文情報のように、データが複数のテーブルに分かれて格納されているのが普通です。これらを1つの結果として取得するのがJOINです。

種類動作
INNER JOIN両方のテーブルに一致する行だけを取得する
LEFT OUTER JOIN左側のテーブルの行はすべて取得し、右側に一致がなければNULLで埋める
RIGHT OUTER JOIN右側のテーブルの行はすべて取得し、左側に一致がなければNULLで埋める
-- 注文があるユーザーだけでなく、注文がまだ無いユーザーも含めて一覧にする
SELECT users.name, orders.item FROM users
LEFT OUTER JOIN orders ON users.id = orders.user_id;

「注文があるユーザーだけでよいか(INNER JOIN)」「注文の有無にかかわらず全ユーザーを見たいか(LEFT OUTER JOIN)」で使い分けます。どちらを使うかによって結果の行数が変わるため、意図しないJOINの種類を選んでしまうと、集計結果が実態とずれてしまう点に注意が必要です。

データの追加・変更・削除

INSERT INTO users (name, email) VALUES ('Taro', 'taro@example.com');
UPDATE users SET email = 'new@example.com' WHERE id = 1;
DELETE FROM users WHERE id = 1;
注意: UPDATEやDELETEでWHERE句を書き忘れると、テーブル全体の行が一括で更新・削除されてしまいます。特に本番データベースに対して手作業でSQLを実行する際は、まずSELECTで対象を確認してから、同じWHERE条件でUPDATE/DELETEを実行する習慣をつけると事故を防げます。

テーブル自体を新しく作るにはCREATE TABLEを使います。主なデータ型には、整数のINT、可変長文字列のVARCHAR(n)、長い文章向けのTEXT、日時のDATETIMEなどがあります。

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255),
    created_at DATETIME
);

PRIMARY KEYは、その列(または列の組み合わせ)がテーブル内で行を一意に識別するための指定です。NOT NULLを付けた列には、値を省略して登録することができなくなります。こうした制約をあらかじめテーブル定義に組み込んでおくことで、アプリケーション側のバグによる不正なデータの混入を、データベース側で機械的に防げます。

トランザクションの基本

複数のSQL文をひとまとまりの処理として実行し、途中で失敗した場合はすべて取り消したい、という場面ではトランザクションを使います。BEGIN(またはSTART TRANSACTION)で開始し、問題がなければCOMMITで確定、途中で異常があればROLLBACKで開始前の状態に巻き戻します。

BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;

例えば口座間の送金処理のように、「片方だけ更新されて、もう片方は更新されない」という中途半端な状態を許してはいけない処理では、トランザクションでひとまとまりに扱うことが重要になります。

MySQL/MariaDBとPostgreSQLのCLI操作

コマンドラインからデータベースに接続するツールは、製品ごとに異なります。

製品接続コマンドテーブル一覧の確認
MySQL / MariaDBmysql -u user -pSHOW TABLES;
PostgreSQLpsql -U user -d db\dt

クエリの実行結果をシェルスクリプトに組み込みたい場合は、mysqlコマンドに-eオプションでSQLを直接渡したり、標準入力経由でSQLファイルを流し込んだりする方法がよく使われます。

補足: Debian系・RHEL系ともに、近年は互換製品のMariaDBがパッケージ管理の既定として採用されていることが多く(apt install mariadb-server / dnf install mariadb-server)、その場合でも接続クライアントは同じmysqlコマンドが提供され、上記の操作方法はそのまま使えます。「MySQLをインストールしたつもりが実体はMariaDBだった」ということも珍しくないため、mysql --versionで実際にどちらが動いているかを確認する習慣をつけておくとよいでしょう。
$ mysql -u user -p -e "SELECT COUNT(*) FROM users;" mydb
$ mysql -u user -p mydb < backup.sql   # SQLファイルの内容をまとめて実行する

確認クイズ

Q1. 集計後のグループに対して条件を指定する句はどれですか?

解説: WHEREは集計前の行に対する条件、HAVINGはGROUP BYで集計した後のグループに対する条件を指定します。

Q2. 左側のテーブルの行をすべて取得し、右側に一致するデータが無い場合はNULLで埋めるJOINはどれですか?

解説: LEFT OUTER JOIN は左側のテーブルの行をすべて残し、右側に一致がない場合はNULLで埋めます。両方に一致する行だけが欲しい場合はINNER JOINを使います。

Q3. UPDATE文を実行する際、テーブル全体が意図せず書き換わってしまう事故を防ぐために欠かせない句はどれですか?

解説: WHERE句を付け忘れると、対象を絞り込まずにテーブル全体が更新・削除されてしまいます。事前にSELECTで対象を確認してから実行するのが安全です。

Q4. PostgreSQLのpsqlで、接続中のデータベースのテーブル一覧を確認するコマンドはどれですか?

解説: psqlでは \dt でテーブル一覧を確認します。SHOW TABLES; はMySQL/MariaDB側の構文です。