Day2 後半のゴール
「“MySQLなら通ったのにPostgreSQLで怒られる”を武器に変える」
後半は、前半で話した3つのキーワードを、
「実際にどうコードが変わるのか」「どう考え方を変えると楽になるのか」まで落とし込みます。
ここでのゴールはこうです。
MySQL的な“ゆるい書き方”がPostgreSQLでどう怒られるか、具体例でイメージできる。
NULLを含む条件式・集計・JOINで、ハマりやすいポイントを事前に避けられる。
「標準SQL寄りの書き方」を意識して、今後のSQLスタイルを少しアップデートできる。
厳密な型システムを“実務のメリット”として捉え直す
「暗黙変換に頼らない書き方に慣れる」
まずは「型が厳しい」話を、もう少し実務寄りに見ていきます。
MySQLでは、こんなコードを書いても、だいたい動いてしまうことがあります。
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
amount INT
);
INSERT INTO orders (id, amount) VALUES ('1', '1000');
SQL文字列 '1' や '1000' を、勝手に数値に変換してくれます。
PostgreSQLでは、こういう書き方は基本的にNGです。
PostgreSQLで同じことをやるなら、こう書くのが“素直な書き方”です。
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
amount INTEGER
);
INSERT INTO orders (amount) VALUES (1000);
SQLここでのポイントは二つです。
id は SERIAL(実体は整数+シーケンス)にして、自動採番に任せる。
数値カラムには、最初から数値リテラル(1000)を渡す。
「文字列で渡してもDBがいい感じに変換してくれるだろう」という発想を捨てて、
「DBの型に合わせて、アプリ側でちゃんと整形してから渡す」というスタイルに切り替えると、PostgreSQLとはすぐ仲良くなれます。
日付・時刻でも“曖昧さ”を残さない
日付・時刻も同じです。
MySQLでは、こんな値が入ってしまうことがあります。
INSERT INTO events (start_at) VALUES ('0000-00-00 00:00:00');
SQLPostgreSQLは、こういう「ありえない日付」を基本的に許しません。
代わりに、「NULLにする」「正しい日付を入れる」のどちらかを選ばせます。
これは、「データの意味が曖昧なまま溜まっていく」のを防ぐためです。
“厳しい”というより、“曖昧さを残させない”という設計思想だと捉えると、かなり納得感が出てきます。
NULLの扱いを“条件式・集計・JOIN”で具体的に見る
「WHEREで落ちるNULL、集計で無視されるNULL」
NULLは、「分からない」「存在しない」という状態でした。
これが実際のクエリでどう効いてくるかを、3つの場面で見てみます。
1. WHERE句でのNULL
前半でも触れましたが、もう一度整理します。
SELECT * FROM people WHERE age >= 20;
SQLage が NULL の行は、この条件にマッチしません。
「20以上かどうか分からないから、含めない」という動きです。
「20歳以上“または年齢不明”を取りたい」なら、こう書く必要があります。
SELECT * FROM people
WHERE age >= 20 OR age IS NULL;
SQLここで「OR age IS NULL」を書き忘れると、「年齢不明の人が全部落ちる」というバグになります。
PostgreSQLはこの挙動が一貫しているので、「NULLをどう扱うか」を毎回意識する癖がつきます。
2. 集計関数とNULL
次に、AVGやSUMなどの集計関数です。
SELECT AVG(age) FROM people;
SQLこのとき、age が NULL の行は「平均の計算から除外」されます。
0として扱われるわけではありません。
例えば、データがこうだとします。
age: 20, 30, NULL
AVG(age) は (20 + 30) / 2 = 25 になります。
NULLを0として計算すると (20 + 30 + 0) / 3 = 16.66… ですが、そうはなりません。
「NULLは“そもそも値がない”ので、平均の母数にも入らない」というのが標準SQLのルールであり、PostgreSQLはそれに忠実です。
3. JOINとNULL
JOINでも、NULLは独特の顔を出します。
SELECT *
FROM orders o
LEFT JOIN users u ON o.user_id = u.id;
SQLここで、orders.user_id が NULL の行は、「どのユーザーにもJOINできない」ので、LEFT JOINの結果では u.* が全部NULLになります。
これは直感通りですが、「JOIN条件に = NULL を書いてしまう」と一気におかしくなります。
SELECT *
FROM orders o
LEFT JOIN users u ON o.user_id = NULL;
SQLこれは、「常に不明(=偽扱い)」なので、JOINが全く成立しません。
JOIN条件にNULLを直接書くのは基本的にNGで、「IS NULL」を使うならWHERE側で使う、というのがセオリーです。
標準SQL寄りの書き方に“寄せていく”練習
「MySQL方言から、少しずつ脱出する」
PostgreSQLを使うときに、意識しておくと得をするのが、「標準SQL寄りの書き方に寄せていく」という姿勢です。
例えば、MySQLでよく見る書き方と、標準寄りの書き方を比べてみます。
LIMITの書き方
MySQL:
SELECT * FROM orders LIMIT 10 OFFSET 20;
SQLPostgreSQLも同じ書き方ができますが、標準SQLではこう書くこともできます。
SELECT * FROM orders
OFFSET 20 FETCH FIRST 10 ROWS ONLY;
SQLPostgreSQLはこの標準的な書き方もサポートしています。
「どちらでも書ける」なら、標準寄りの方を選んでおくと、他のDBに移るときに楽です。
自動採番の書き方
MySQL:
id INT PRIMARY KEY AUTO_INCREMENT
SQLPostgreSQL:
id SERIAL PRIMARY KEY
SQLさらに新しめの標準寄りの書き方だと、こうも書けます。
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
SQLPostgreSQLは、このIDENTITY句もサポートしています。
「SERIALはPostgreSQL方言」「IDENTITYは標準寄り」という位置づけです。
最初はSERIALで十分ですが、「あ、標準的な書き方もあるんだな」と知っておくと、後で設計の幅が広がります。
「PostgreSQLに合わせる=SQL力が上がる」という視点
「厳しさを“トレーニング器具”として使う」
ここまで見てきたように、PostgreSQLは
型に厳しい
NULLに正直
標準SQLにかなり忠実
という性格を持っています。
これは裏を返すと、「PostgreSQLに合わせてSQLを書くと、自然とSQL力が上がる」ということでもあります。
アプリ側で型を意識して値を整形するようになる。
NULLをどう扱うかを毎回ちゃんと考えるようになる。
方言に頼らない書き方を選ぶ癖がつく。
その結果、「どのDBに行っても通用するSQL脳」が育ちます。
MySQLの“ゆるさ”は、プロトタイプや小さなツールを作るときにはすごく便利です。
一方で、PostgreSQLの“真面目さ”は、長く運用するシステムや、複雑なデータを扱う場面で効いてきます。
Day2の時点では、「PostgreSQLに怒られたら、“うるさいな”じゃなくて“あ、今ちゃんとしたSQLを書けてないんだな”と気づくサインなんだ」と思ってもらえたら十分です。
Day2 後半のまとめ
MySQLでは暗黙の型変換により、整数カラムに文字列リテラルを入れても通ってしまうことが多いのに対し、PostgreSQLでは INTEGER に '100' のような値を渡すとエラーになりやすく、SERIAL や INTEGER に対しては最初から数値リテラルを渡す・日付や時刻も正しいフォーマットで渡す、といった「型を意識した書き方」に自然と矯正される。
NULLについては、age >= 20 で age=NULL の行が落ちる、AVGやSUMがNULLを母数から除外する、= NULL が常に不明扱いになるため IS NULL / IS NOT NULL を使う必要がある、JOIN条件にNULLを直接書くとJOINが成立しない、といった“3値論理”が一貫して効いてくるので、「NULLをどう扱うか」を毎回設計の一部として考える癖がつく。
さらに、OFFSET ... FETCH FIRST ... ROWS ONLY や GENERATED ... AS IDENTITY のような標準SQL寄りの書き方もサポートしているPostgreSQLに合わせてSQLを書くことで、「方言に頼らない・型とNULLを意識した・他DBにも持ち運べる」SQLスタイルが身につき、PostgreSQLの“厳しさ”をそのまま自分のSQL力を鍛えるトレーニング器具として使えるようになる。
