Day2 前半のゴール
「“PostgreSQLはちょっと真面目なやつ”という感覚をつかむ」
今日は、SQLite と MySQLを触ってきたあなたに向けて、
「PostgreSQLって、どこが違うの?」を一番大事な3点に絞って話します。
厳密な型システム
NULLの扱い
標準SQLへの準拠度
前半のゴールはこうです。
PostgreSQLが「型にうるさい」「NULLに正直」「標準SQLにかなり忠実」な性格だと理解する。
MySQLで“なんとなく許されていたこと”が、PostgreSQLでは怒られるイメージを持つ。
その違いが「めんどくさい」ではなく、「バグを早めに見つけてくれる頼れる相棒」だと感じられる。
厳密な型システムとは何か
「“とりあえず文字列に突っ込んでおく”を許さない世界」
SQLite は「ほぼ何でも TEXT に入るし、型もゆるい」世界でした。
MySQLも、「文字列を数値カラムに入れようとすると、勝手に変換してくれる」ことが多かったはずです。
PostgreSQLは、ここがかなり違います。
一言で言うと、「型に対して厳格で、変なものは基本的に拒否する」タイプです。
例えば、こういうテーブルがあるとします。
CREATE TABLE sample (
id INTEGER,
price INTEGER
);
SQLここで、MySQLだとこんなINSERTが「通ってしまう」ことがあります。
INSERT INTO sample (id, price) VALUES (1, '100');
SQLMySQLは '100' を数値に変換してくれるので、特にエラーにならず入ります。
しかし、PostgreSQLは「文字列を整数に勝手に変換する」ことをあまりしません。
場合によっては、こういうINSERTでエラーになります。
「型が違うものは、ちゃんと直してから入れてね」というスタンスです。
この厳しさは、最初は「うわ、めんどくさい」と感じるかもしれません。
でも、実務ではむしろメリットです。
「本当は数値を入れるべきところに、うっかり文字列を渡していた」
「日付カラムに、フォーマットがおかしい文字列を入れようとしていた」
こういうバグを、PostgreSQLは早い段階で止めてくれます。
型の“真面目さ”が効いてくる具体例
「日付・時刻・JSONなど、“ちゃんとした型”が豊富」
PostgreSQLは、型の種類もかなり豊富です。
日付・時刻・タイムゾーン付き時刻、JSON、配列など、MySQLよりも「型としてちゃんと用意されているもの」が多いです。
例えば、日付と時刻を扱うときに、こういうテーブルを作れます。
CREATE TABLE events (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
start_at TIMESTAMPTZ NOT NULL
);
SQLTIMESTAMPTZ は「タイムゾーン付きのタイムスタンプ」です。
日本時間・UTC・他の国の時間を意識したシステムでは、これがかなり重要になります。
ここに、変な文字列を入れようとすると、PostgreSQLはちゃんと怒ります。
INSERT INTO events (title, start_at)
VALUES ('テスト', '2024-13-99 99:99:99');
SQLこれは「存在しない日付・時刻」なので、エラーになります。
MySQLだと、場合によっては「0000-00-00 00:00:00」のような“謎の値”として入ってしまうことがあります。
PostgreSQLは、「おかしいものはおかしい」と言ってくれるので、
「変な値が静かに紛れ込む」リスクが減ります。
NULLの扱いが“正直”という話
「NULLは“何も分からない”であって、“0や空文字”ではない」
次に、NULLの話です。
これは、SQLite・MySQL経験者でもよく混乱するポイントです。
NULLは、「値がない」「分からない」という状態を表します。
0でも空文字でもなく、「そもそも値が存在しない」という意味です。
PostgreSQLは、このNULLの意味をかなり真面目に扱います。
特に、比較演算や集計のときに、その“正直さ”が顔を出します。
例えば、こういうテーブルがあるとします。
CREATE TABLE people (
id INTEGER,
age INTEGER
);
SQLここに、こんなデータが入っているとします。
id=1, age=20
id=2, age=NULL
「20歳以上の人を取りたい」として、こう書いたとします。
SELECT * FROM people WHERE age >= 20;
SQLこのとき、PostgreSQLは「age=NULL の行は、条件に合致しない」と判断します。
なぜなら、「NULL >= 20 は、真でも偽でもなく“不明”」だからです。
NULLは「分からない」なので、「20以上かどうかも分からない」という扱いになります。
その結果、「age>=20 の条件には含めない」という動きになります。
MySQLでも基本は同じですが、設定やモードによっては挙動が変わることがあります。
PostgreSQLは、このあたりがかなり一貫していて、「NULLはとにかく分からないもの」として扱います。
NULLと比較するときの“お約束”
「= NULL ではなく IS NULL / IS NOT NULL を使う」
NULLの扱いで一番大事なルールは、「NULLとの比較には = を使わない」ということです。
例えば、「年齢が未設定の人を取りたい」ときに、こう書いてしまいがちです。
SELECT * FROM people WHERE age = NULL;
SQLこれは、PostgreSQLでは「常に偽」になります。
なぜなら、「NULL = NULL も“不明”」だからです。
正しい書き方はこうです。
SELECT * FROM people WHERE age IS NULL;
SQL逆に、「年齢が設定されている人」を取りたいときは、
SELECT * FROM people WHERE age IS NOT NULL;
SQLと書きます。
この「IS NULL / IS NOT NULL を使う」というルールは、標準SQLの世界では当たり前ですが、
MySQLだけを使っていると、意外と意識されていないことがあります。
PostgreSQLは標準SQLにかなり忠実なので、「NULLとの比較はISを使う」というルールを守らないと、普通にハマります。
ここは、Day2の超重要ポイントです。
標準SQLへの準拠度が高いとはどういうことか
「“他のDBでも通じる書き方”を身につけやすい」
最後に、「標準SQLへの準拠度」の話をします。
SQLには、「こう書くべき」という標準仕様(SQL標準)が存在します。
ただ、現実には各DBが独自拡張を入れていて、「方言」がたくさんあります。
MySQLは、かなり方言が多いDBです。LIMIT の書き方、AUTO_INCREMENT、ENUM 型、INSERT ... ON DUPLICATE KEY UPDATE など、
「MySQLでしか通じない書き方」がたくさんあります。
PostgreSQLも独自機能はありますが、基本的な部分では標準SQLにかなり忠実です。
例えば、
NULLの扱い(3値論理)
トランザクションの振る舞い
JOINやサブクエリの書き方
などは、「教科書的なSQL」とほぼ同じです。
この「標準に近い」ということは、
「PostgreSQLで身につけたSQLの書き方は、他のDBでも通じやすい」
という意味でもあります。
SQLite → MySQL → PostgreSQLと来たあなたは、
「方言の多いMySQL」と「標準寄りのPostgreSQL」の両方を知ることになります。
これは、エンジニアとしてかなり強い武器になります。
Day2 前半のまとめ
PostgreSQLは、MySQLに比べて「型に厳格」で、整数カラムに変な文字列を入れようとするとエラーになるなど、“おかしな値を静かに受け入れない”性格を持ち、日付・時刻・JSON・配列などの豊富な型を真面目に扱うことで、バグを早めに炙り出してくれる。
NULLについては、「値がない/分からない」という意味を3値論理で一貫して扱い、age >= 20 の条件に age=NULL の行は含めず、「NULLとの比較には = NULL ではなく IS NULL / IS NOT NULL を使う」という標準SQLのルールを守らないと普通にハマる世界であり、全体として標準SQLへの準拠度が高いため、PostgreSQLで身につけた“正統派のSQLの書き方”は他のDBにも持ち運びやすい――という「ちょっと真面目なやつ」という性格をつかむのが Day2 前半の着地点になる。
