1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DBでnullを許容するのか、空文字を許容するのかの備忘録

Posted at

背景・目的

  • 背景・目的
    • Ruby on Railsでアプリケーション開発を行っている
    • なぜDB層でdefault: ''を指定しない方針をとったのか?という質問をされて回答ができなかったので言語化しておく

前提

  • モデルケース

class CreateUsers < ActiveRecord::Migration[8.0]

  def change
	create_table :users do |t|
	  t.string :name
	  t.string :email
	  t.string :bio
	  t.string :password_digest
	  t.timestamps
	end
  end
end

  • ユーザーの情報を管理するUserモデルが、名前、メールアドレス、自己紹介(bio)の3つのカラムを持っている想定

  • 新規登録からログインのフローについて

    • 新規登録
      • ユーザーは新規登録画面から、名前、メールアドレス、パスワードを入力する
    • ログイン
      • ユーザーはログイン画面から、メールアドレス、パスワードを入力する
    • プロフィールについて
      • ユーザーはログイン後に任意のタイミングで、名前、メールアドレス、自己紹介(bio)の入力、編集をすることができる
  • その他

nameカラムについて考える

nullを許容する、空文字を許容する

# migration
t.string :name

# model
# バリデーションなし

namenullまたは空文字を許容する

どんな状態か

  • DB層ではnull: true(デフォルト)なので、nameカラムにnullが入る
  • アプリケーション層ではバリデーションがないので、空文字も登録できる
  • namenilのユーザーと、name""のユーザーが混在する

具体的に困るケース

  • ユーザー一覧画面でnameを表示しようとすると、nilの場合と空文字の場合の両方をチェックする必要がある
  • @user.namenilの可能性があるため、@user.name.upcaseのようなメソッドを呼ぶとエラーになる
  • ユーザーを名前で検索する機能が実質使えない

メリット

  • 思い浮かばなかったです

デメリット

  • 表示時に常にnilチェックと空文字チェックが必要
  • メールアドレスでしか、ユーザーを識別できない

採用するかどうか

しない。メリットがない

nullを許容する、空文字を禁止する

# migration
t.string :name

# model
validates :name, presence: true

namenullは許容するが、空文字は禁止する

どんな状態か

  • DB層ではnull: true(デフォルト)なので、nameカラムにnullが入る可能性がある
  • アプリケーション層ではpresence: trueで空文字を弾くが、DB層の制約はない
  • Railsのバリデーションを通さずにDBに直接INSERT(例:生のSQLや他のツール)すると、nullが入る

具体的に困るケース

  • Railsコンソールでバリデーションをスキップして作成するとnamenullのまま保存される
  • DBマイグレーションやバッチ処理でバリデーションをスキップすると、nullが入る可能性がある
  • @user.name.upcaseのようなメソッド呼び出しでNoMethodErrorが発生する可能性がある
  • バリデーション抜けたら、空文字が入る

メリット

  • 思い浮かばなかったです

デメリット

  • DBにはnullが入る可能性があるため、コード上でnilチェックが必要
  • バリデーションで空文字を弾いても、DB層ではnullが入る可能性がある
  • DB層とアプリケーション層で一貫性がない

採用するかどうか

しない。データの統合性がなくなる可能性がある

nullを禁止する、空文字を許容する

# migration
t.string :name, null: false, default: ''

# model
# バリデーションなし

namenullは禁止だが、空文字は許容する

どんな状態か

  • DB層ではnull: false, default: ''なので、nameは常に文字列型(nullにはならない)
  • アプリケーション層ではバリデーションがないので、空文字が登録できる
  • nameが空文字""のユーザーが存在する

具体的に困るケース

  • ユーザー一覧画面で名前が空欄のユーザーが表示される
  • ユーザーを名前で識別することができない
  • 後からユーザーが意図的に名前を空にしたのか、それとも入力していないだけなのかを判定できない

メリット

  • nilチェックは不要(常に文字列)

デメリット

  • 空文字のnameが登録される可能性がある
  • ユーザーを識別できない

採用するかどうか

完全になしではないが、採用しない

usersテーブルは現実のユーザー(人間)を管理するテーブルである。そのテーブルの中でnameが空文字(名前がない)というのは現実的に考えられないケースであるため。

nullを禁止する、空文字を禁止する

# migration
t.string :name, null: false

# model
validates :name, presence: true

namenullも空文字も禁止

どんな状態か

  • DB層ではnull: falseなので、nameカラムにnullが入らない
  • アプリケーション層ではpresence: trueで空文字を弾く
  • nameは必ず何か意味のある文字列が入っている状態が保証される

具体的に困るケース

  • nameには必ず何かの値を入れる必要があるため、名前が存在しないという状態を表現するのに工夫がいる。(要件次第)

メリット

  • DB層でデータ整合性を保証できる
  • コード上でnilチェックが不要
  • 必ず意味のあるユーザー名が登録される

デメリット

  • nameに値は必ず入れなければならない

採用するかどうか

採用する

  • 必ず意味のあるユーザー名が登録されることで、業務要件を満たすため。

emailカラムについて考える

nullを許容する、空文字を許容する

# migration
t.string :email

# model
# バリデーションなし

emailnullまたは空文字を許容する

どんな状態か

  • DB層ではnull: true(デフォルト)なので、emailカラムにnullが入る
  • アプリケーション層ではバリデーションがないので、空文字も登録できる
  • emailnilのユーザーと、email""のユーザーが混在する

具体的に困るケース

  • ログイン機能が動作しない(emailnilや空文字では認証できない)
  • ユーザーに通知メールを送る機能が使えない
  • @user.email.downcaseのようなメソッド呼び出しでNoMethodErrorが発生する

メリット

  • 思い浮かばなかったです

デメリット

  • 認証機能が動作しない
  • データ整合性が保証されない
  • 業務要件上、不適切

採用するかどうか

しない。メリットがない

nullを許容する、空文字を禁止する

# migration
t.string :email

# model
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }

emailnullは許容するが、空文字は禁止する

どんな状態か

  • DB層ではnull: true(デフォルト)なので、emailカラムにnullが入る可能性がある
  • アプリケーション層ではpresence: trueで空文字を弾くが、DB層の制約はない
  • Railsのバリデーションを通さずにDBに直接INSERTすると、nullが入る

具体的に困るケース

  • Railsコンソールでバリデーションをスキップして作成するとemailnullのまま保存される
  • DBマイグレーションでバリデーションをスキップすると、nullが入る可能性がある
  • @user.email.downcaseのようなメソッド呼び出しでNoMethodErrorが発生する可能性がある
  • ログイン機能が動作しない
  • バリデーション抜けたら、空文字が入る

メリット

  • 思い浮かばなかったです

デメリット

  • DBにはnullが入る可能性があるため、コード上でnilチェックが必要
  • バリデーションで空文字を弾いても、DB層ではnullが入る可能性がある
  • DB層とアプリケーション層で一貫性がない

採用するかどうか

しない。メリットがない

nullを禁止する、空文字を許容する

# migration
t.string :email, null: false, default: ''

# model
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }

emailnullは禁止だが、空文字は許容する

どんな状態か

  • DB層ではnull: false, default: ''なので、emailは常に文字列型(nullにはならない)
  • アプリケーション層ではformatバリデーションのみで、presenceがない
  • emailが空文字""のユーザーが存在する可能性がある

具体的に困るケース

  • ログイン画面でemailが空文字のユーザーではログインできない
  • メール送信機能が動作しない(空文字のメールアドレスには送信できない)

メリット

  • nilチェックは不要(常に文字列)

デメリット

  • 空文字のemailが登録される可能性がある
  • 認証機能が動作しない
  • 業務要件上、不適切

採用するかどうか

採用しない

  • メールが空文字だとログインができない
  • ユーザーを一意で判定する値がなくなる

nullを禁止する、空文字を禁止する

# migration
t.string :email, null: false

# model
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }

emailnullも空文字も禁止

どんな状態か

  • DB層ではnull: falseなので、emailカラムにnullが入らない
  • アプリケーション層ではpresence: trueformatで空文字と無効な形式を弾く
  • emailは必ず有効なメールアドレスが入っている状態が保証される

具体的に困るケース

  • メールアドレスを空にできない→そんなこと必要?って感じですが

メリット

  • DB層でデータ整合性を保証できる
  • コード上でnilチェックが不要
  • 必ず有効なメールアドレスが登録される
  • 認証機能が安定する

デメリット

  • 思い浮かばなかったです

採用するかどうか

採用する

  • emailは認証に必須のカラムなので、nullも空文字も許容できない
  • 要件を満たす

bioカラムについて考える

nullを許容する、空文字を許容する

# migration
t.text :bio

# model
# バリデーションなし

bionullまたは空文字を許容する

どんな状態か

  • DB層ではnull: true(デフォルト)なので、bioカラムにnullが入る
  • 空文字も許容する
  • bionilのユーザーと、bio""のユーザーが混在する

具体的に困るケース

  • 表示などでは常にnilをチェックする必要がある
  • データ分析で「自己紹介を登録しているユーザー数」を集計する際、WHERE bio IS NOT NULL AND bio != ''のように条件が複雑になる

メリット

  • 「まだ未入力」と「意図的に空にした」を区別できるかも(nullと空文字で区別)

デメリット

  • コード上でnilチェックと空文字チェックの両方が必要

採用するかどうか

採用しない

  • nilと空文字の両方をチェックする必要があり、コードが煩雑になる

nullを許容する、空文字を禁止する

# migration
t.text :bio

# model
validates :bio, presence: true

bionullは許容するが、空文字は禁止する

どんな状態か

  • DB層ではnull: true(デフォルト)なので、bioカラムにnullが入る可能性がある
  • アプリケーション層ではpresence: trueで空文字を弾くが、DB層の制約はない
  • DB層ではnull: trueなので、レコード作成時にbionullでも保存できる
  • 一度値を入れたら、その後は空にできない

具体的に困るケース

  • ユーザーが画面から操作するときに、後から自己紹介を空で登録することができない
  • バリデーションが抜けたら空文字が入る

メリット

  • 思い浮かばなかったです

デメリット

  • DBにはnullが入る可能性があるため、コード上でnilチェックが必要
  • バリデーションで空文字を弾いても、DB層ではnullが入る可能性がある
  • DB層とアプリケーション層で一貫性がない

採用するかどうか

採用しない

  • 一度値を入れたら空にできないという柔軟性のなさがある
  • オプショナルな項目としては不適切

nullを禁止する、空文字を許容する

# migration
t.text :bio, null: false, default: ''

# model
validates :bio

bionullは禁止だが、空文字は許容する

どんな状態か

  • DB層ではnull: false, default: ''なので、bioは常に文字列型(nullにはならない)
  • アプリケーション層では長さのバリデーションのみで、空文字は許容する

具体的に困るケース

  • 困るケースはなし(業務要件を満たす設計)
  • プロフィール画面では@user.bio.present?で簡単に判定できる
  • 自己紹介が「まだ未入力」なのか「後から空にした」のか、判定ができない

メリット

  • シンプル(NULLを考えなくていい)
  • 常に文字列として扱える
  • ユーザーは自由に入力/削除できる
  • コードがシンプルになる
  • 実務で十分

デメリット

  • 「未入力」と「意図的に空にした」を区別できない
  • ただし、ユーザーから見たら同じ状態なので実務上問題ない

採用するかどうか

採用する

  • オプショナルな項目として適切
  • nilチェックが不要でコードがシンプルになる
  • ユーザーが自由に入力/削除できる柔軟性がある
  • 実務上、「未入力」と「意図的に空にした」を区別する必要性は低い

nullを禁止する、空文字を禁止する

# migration
t.text :bio, null: false

# model
validates :bio, presence: true

bionullも空文字も禁止

どんな状態か

  • DB層ではnull: falseなので、bioカラムにnullが入らない
  • アプリケーション層ではpresence: trueで空文字を弾く
  • bioは必ず何か入力されている状態が強制される

具体的に困るケース

  • 新規登録時に自己紹介を入力しないと登録できない
  • ユーザーが自己紹介を削除(空にする)ことができない
  • 「あとで自己紹介を書く」という運用ができない

メリット

  • 必ず自己紹介が登録される

デメリット

  • ユーザーが自由に削除できなくなる

採用するかどうか

採用しない

  • オプショナルな項目を必須にしてしまうのは、ユーザー体験を損なう
  • 新規登録時に自己紹介を強制するのは柔軟性がない
  • 後から自己紹介を削除できないのは不便

感想

  • データベースにおいてnullを許容するのか、空文字を許容するのかを整理する良い機会になりました。
  • どちらが正解というよりかは、要件にあった適切な選択肢が取れるのか、それを人に説明できるのか?みたいなところを言語化して整理したかったのでよかった。
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?