SQL クエリのコーディング規約(スタイルガイド)

SQLを読みやすくするためのインデントや記法に関するガイドです。個人的なルールかつ、厳格に決めていないので読みやすさを意識して調整したりします。各サンプルのクエリはMySQL で実行確認しています。

コーディングスタイルの説明のため、クエリ自体にはあまり意味はないです。パフォーマンスの悪いクエリもありますがご了承ください。

SQL スタイルガイド

  • 予約語/関数は大文字
    • SELECT, INSERT, UPDATE, FROM, WHERE など
  • インデントはスペース2つ
  • SELECT 句のカラム指定のカンマ , は先頭
  • 条件の AND / OR は先頭
  • セミコロン ; は最終行
  • AS は読みやすくするために適宜使う
    • AS 自体は省略して書いたりします

SQL フォーマット

1行が1カラムに対する記述にまとまる様に意識して書いています。

SELECT クエリのフォーマット

SELECT
  id
, name
FROM
  users
WHERE
  admin = 1
  AND password_digest IS NULL
  AND created_at >= '2016-01-01 00:00:00'
  AND created_at <  '2016-12-01 00:00:00'
ORDER BY
  id ASC
LIMIT
  0, 10
;

LIMIT の指定はカンマで改行を入れても良いですが、やや冗長な気がするのでまとめて1行で書いています。

INSERT クエリのフォーマット

あまり書かないですが、たまにマスターデータやテスト用にデータを一括で入れる時には下記の様な形式で書いています。

INSERT INTO
  users (`name`, `email`, `admin`, `created_at`, `updated_at`)
VALUES
  ('John', 'john@example.com', 1, NOW(), NOW())
, ('Tom', 'tom@example.com', 0, NOW(), NOW())
, ('Robin', 'robin@example.com', 0, NOW(), NOW())
;

UPDATE クエリのフォーマット

垂直方向にそろえるインデントが好きなので、 = など見やすくなりそうな位置で揃えます。

UPDATE
  microposts
SET
  content    = 'tweet'
, updated_at = NOW()
WHERE
  id = 5
;

CASE 句など、1行1カラムにならない例外的なスタイル

改行のタイミングには厳格なルールはないですが、一行単位でまとまりになる様に適宜改行してます。あと、括弧 () だけでインデントはあんまりしないです。

SELECT
  (CASE u.admin
    WHEN 0 THEN 'member'
    WHEN 1 THEN 'admin'
    ELSE 'unknown'
  END)     AS user_type
, COUNT(*) AS user_count
FROM
  users u
GROUP BY
  u.admin
;

テーブル結合 (JOIN)

JOIN は結合条件が比較的シンプルな場合が多いので、 ON 句を含めて1行に書く様にしています。複数のテーブルを結合するときには読みやすくなると思います。

SELECT
  m.user_id
, COUNT(*) post_count
FROM
  microposts m
  INNER JOIN users u ON u.id = m.user_id
WHERE
  u.admin = 0
GROUP BY
  m.user_id
;

サブクエリ

ネストの深いSQL は読みづらいので、できるだけネストを浅くします。 WITH によってサブクエリ部分を外に出すことをお勧めします。ただ、MySQL には WITH が実装されていないので、仕方なくネストする場合は、スペース2つでインデントして書きます。

SELECT
  *
FROM
  users u
WHERE
  NOT EXISTS (
    SELECT
      1
    FROM
      relationships r
    WHERE
      r.followed_id = u.id
  )
;

AS の命名規約

厳格ではないですが、テーブル名の別名は元のテーブルがある程度分かる名前にします。サブクエリなどで一時的な集計テーブルが発生する場合は、何となく大文字で別名を付けています。名前が被って困る場合は、適当に上から順番に連番を振ります。

SELECT
  U.created_date
, SUM(U.user_count)  AS user_count
, SUM(U.post_count)  AS post_count
FROM
  -- 日次会員登録数
  (SELECT
    T1.created_date
  , COUNT(1)  AS user_count
  , 0         AS post_count
  FROM
    (SELECT
      u.id
    , CAST(u.created_at AS DATE) AS created_date
    FROM
      users u
    ) T1
  GROUP BY
    T1.created_date
  -- 日次投稿数
  UNION ALL SELECT
    T2.created_date
  , 0         AS user_count
  , COUNT(1)  AS post_count
  FROM
    (SELECT
      m.id
    , CAST(m.created_at AS DATE) AS created_date
    FROM
      microposts m
    ) T2
  GROUP BY
    T2.created_date
  ) U
GROUP BY
  U.created_date
;

さすがにルールに沿って書いても読みづらい。パフォーマンスのためにも、短くシンプルなSQL にした方が良いと思います。

参考:テーブル構成

サンプルコードのSQL はRails チュートリアルのテーブル定義を使っています。

mysql> desc users;
+-----------------+--------------+------+-----+---------+----------------+
| Field           | Type         | Null | Key | Default | Extra          |
+-----------------+--------------+------+-----+---------+----------------+
| id              | int(11)      | NO   | PRI | NULL    | auto_increment |
| name            | varchar(255) | YES  |     | NULL    |                |
| email           | varchar(255) | YES  | UNI | NULL    |                |
| created_at      | datetime     | YES  |     | NULL    |                |
| updated_at      | datetime     | YES  |     | NULL    |                |
| password_digest | varchar(255) | YES  |     | NULL    |                |
| remember_token  | varchar(255) | YES  | MUL | NULL    |                |
| admin           | tinyint(1)   | YES  |     | NULL    |                |
+-----------------+--------------+------+-----+---------+----------------+

mysql> desc microposts;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| content    | varchar(255) | YES  |     | NULL    |                |
| user_id    | int(11)      | YES  | MUL | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

mysql> desc relationships;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| id          | int(11)  | NO   | PRI | NULL    | auto_increment |
| follower_id | int(11)  | YES  | MUL | NULL    |                |
| followed_id | int(11)  | YES  | MUL | NULL    |                |
| created_at  | datetime | YES  |     | NULL    |                |
| updated_at  | datetime | YES  |     | NULL    |                |
+-------------+----------+------+-----+---------+----------------+