2025年10月28日 著者: Matthew Revell

SQLを書くこと自体は難しくありません。難しいのは、うまく書くことです。

Beekeeper Studioは無料のオープンソースのデータベースGUIです

これまで使った中で最高のSQLクエリ&エディタツールです。データベースの管理に必要なものがすべて揃っています。 - ⭐⭐⭐⭐⭐ Mit

Beekeeper Studioは高速で直感的、そして使いやすいツールです。多数のデータベースに対応し、Windows、Mac、Linuxで快適に動作します。

BeekeeperのLinux版は100%フル機能。手抜きも機能の妥協も一切ありません。

開発環境では問題なく動くクエリも、本番環境では遅くなり始めます。ORDER BY がなければ結果は一貫せず、手軽な SELECT * はスキーマ変更で壊れ、小さなデータセットでは無害に見えるフィルタも、規模が大きくなると処理が止まってしまうことがあります。

経験を重ねるうちに、長く通用するパターンとそうでないパターンが見えてきます。

そこで、モダンでオープンソースのクロスプラットフォーム対応データベースGUI、Beekeeper Studio の開発チームが、7つのSQLベストプラクティスをご紹介します。

🔖 クイックリファレンス: これらのベストプラクティスを学ぶ際は、SQL言語チートシートを手元に置いておくと、SELECT、WHERE、JOIN、集計関数などの重要なSQLコマンドをすぐに確認できます。

Beekeeper Studioをダウンロード

1. 具体的に書く — SELECT * を避ける

SELECT * ですべての列を取得するのは手軽で柔軟に感じられます。返す列と除外する列を決める必要がなく、データセットを探索している段階なら問題なく機能します。

しかし、その柔軟性には代償があります。スキーマが変化したりJOINが増えたりすると、SELECT * は重複した列や想定外の列を取り込み、クエリを遅くし、デバッグを難しくします。

列を明示的に指定すれば、意図が明確になり、気づかないうちにコードが壊れることを防げます。

-- Risky
SELECT * FROM user_profiles WHERE user_id = 42;

-- Better
SELECT user_id, display_name, avatar_url
FROM user_profiles
WHERE user_id = 42;

2. CTEで複雑なロジックをシンプルにする

ネストしたサブクエリでも動作はしますが、すぐに読みにくくなります。どこで何がフィルタされているのか把握しづらく、デバッグでは何重もの括弧の間を行ったり来たりすることになりがちです。

共通テーブル式(WITH 句)を使うと、クエリを名前付きのステップに分割できます。各ステップを個別に実行・テストできるため、意図が明確になり、エラーも追跡しやすくなります。ただし、一部のデータベースはCTEをマテリアライズするため、ループ処理の多い場面ではインラインのサブクエリのほうが速い場合があります。

-- Hard to follow
SELECT customer_id, COUNT(*)
FROM (
  SELECT customer_id, created_at
  FROM orders
  WHERE created_at > now() - interval '7 days'
) o
GROUP BY customer_id;

-- Clearer
WITH recent_orders AS (
  SELECT customer_id, created_at
  FROM orders
  WHERE created_at > now() - interval '7 days'
)
SELECT customer_id, COUNT(*)
FROM recent_orders
GROUP BY customer_id;

3. 構文ではなく意図をコメントする

SQLを見ればわかることを繰り返すだけのインラインコメントには、あまり価値がありません。後で忘れてしまう本当のコンテキストは、クエリが何をするかではなく、なぜ存在するかです。

目的や用途について短く書き添えておけば、後でコードを見直したり引き継いだりするのが楽になります。未来の自分や、このクエリがなぜ重要なのかを理解しようとする次の開発者に向けて書きましょう。

-- Find users who signed up but never activated
-- Used in the weekly retention cohort report
SELECT u.id, u.email
FROM users u
LEFT JOIN events e
  ON u.id = e.user_id
  AND e.name = 'account_activated'
WHERE e.id IS NULL
  AND u.created_at > now() - interval '7 days';

4. インデックスを活かせるフィルタを書く

インデックスが役立つのは、フィルタがsargable(Search ARGument ABLEの略)である場合だけです。つまり、データベースが全行をスキャンせずにインデックスを直接利用できる状態のことです。

LOWER(email) や DATE(created_at) のように列を関数で囲むと、オプティマイザはインデックスを使えなくなります。型キャストでも同じことが起こり得ます。クエリは動作しますが、データが増えるにつれて遅くなっていきます。

-- Not sargable: disables index
WHERE LOWER(email) = 'test@example.com';

-- Sargable: uses existing index
WHERE email = 'test@example.com';

フィルタを効率的に保つには、データの格納方法に合わせて書きましょう。大文字小文字を区別しない比較が必要なら、値を一貫した形式で格納するか、関数インデックスを追加します。日付の場合は、列を変換するのではなく範囲で絞り込みましょう。そのほうが明確で、インデックスも有効に使えます。

-- Not sargable
WHERE DATE(created_at) = '2025-08-04';

-- Sargable
WHERE created_at >= '2025-08-04'
  AND created_at <  '2025-08-05';

5. JOINの前にフィルタする

特に探索やプロトタイピングの段階では、まずすべてをJOINしてから後でフィルタしたくなるものです。しかし、JOINのたびに行数は増え、不要な行が増えるほどクエリは遅くなり、オプティマイザも実行計画を立てにくくなります。

代わりに、結合する前に各テーブルを絞り込みましょう。そうすれば、データベースは本当に必要なデータだけをJOINするため、メモリ使用量が減り、キャッシュ効率が向上し、実行計画も予測しやすくなります。

-- Joins every user, even inactive ones
SELECT e.*
FROM users u
JOIN events e ON e.user_id = u.id;

-- Filter first: only active, recently seen users
WITH active_users AS (
  SELECT id
  FROM users
  WHERE active = true
    AND last_seen > now() - interval '90 days'
)
SELECT e.*
FROM active_users u
JOIN events e ON e.user_id = u.id;

6. 関連の有無の確認には IN より EXISTS を使う

関連する行が存在するかを確認する場合、IN でも用は足ります — 問題が起きるまでは。値の完全なリストを作成して一つずつ比較し、さらに NULL の扱い方によっては、有効な一致が隠れてしまうことがあります。

EXISTS の動作は異なります。一致が見つかった瞬間にtrueを返し、サブクエリ内の NULL の影響も受けません。その結果、特に大規模なデータセットでは、より速く、より信頼性の高いクエリになります。

-- Works, but can be slower and NULL-prone
SELECT *
FROM users
WHERE id IN (SELECT user_id FROM orders);

-- Clear and efficient: stops at first match
SELECT *
FROM users u
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.id
);

7. 必ず ORDER BY を指定する

SQLでは、明示的に指定しない限り行の順序は保証されません。

ORDER BY がなければ、データベースは最も都合の良い順序で自由に行を返します。その順序は、実行ごと、環境ごと、さらにはちょっとしたアップデートでも変わる可能性があります。

開発環境では安定して見えても、インデックスや実行計画、データ量が変わると、ページネーションが予測できなくなったり、APIの結果が一貫しなくなったりします。

-- Looks fine until it isn't
SELECT id, name
FROM users
WHERE active = true
LIMIT 10;

-- Predictable and reproducible
SELECT id, name
FROM users
WHERE active = true
ORDER BY created_at DESC
LIMIT 10;

まとめ

最良のクエリとは、1年後に読んでも理解できるクエリです。意図が明確で、動作が予測でき、隠れた落とし穴がありません。

これらの習慣はその考え方に基づいています。私たちが Beekeeper Studio で毎日実践しているもので、扱うすべてのデータベースでクエリを読みやすく、一貫性があり、高速に保つためのものです。

このガイドの無料PDF版を入手して、手元に置いたりチームで共有したりできます:

「忙しい開発者のための7つのSQL習慣」をダウンロード

Beekeeper Studio なら、集中して作業し続けられるよう設計された、モダンでクロスプラットフォーム対応のオープンソースSQLエディタで、これらの習慣を日々実践できます。

  • 20以上のデータベースに対応: Postgres、MySQL、SQLite、SQL Server、MongoDB、Redisなど。
  • 高速でレスポンシブなエディタ: スキーマを認識するオートコンプリート、タブ付きクエリ、シンタックスハイライト。
  • ビジュアルなデータツール: 作業の流れを中断せずに、テーブルの閲覧、行の編集、JSONの確認ができます。
  • AIアシスタント(プライバシー重視): データベースのために待ち望まれていたAIコーディングツール。スキーマを認識し、お好みのLLMで動作し、クエリの探索と改善をすばやく行えます。

Beekeeper Studioをダウンロード