web-dev-qa-db-ja.com

Rails / ActiveRecordで許可されているNULLで一意のインデックスを指定することは可能ですか?

列に一意のインデックスを指定したいが、NULL値も許可する必要があります(複数のレコードにNULL値を含めることができます)。 PostgreSQLでテストすると、NULL値を持つレコードが1つあることがわかりますが、次の場合は問題が発生します。

irb(main):001:0> u=User.find(5)
  User Load (111.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 5]]
=> #<User id: 5, email: "[email protected]", created_at: "2013-08-28 09:55:28", updated_at: "2013-08-28 09:55:28">
irb(main):002:0> u.email=nil
=> nil
irb(main):003:0> u.save
   (1.1ms)  BEGIN
  User Exists (4.8ms)  SELECT 1 AS one FROM "users" WHERE ("users"."email" IS NULL AND "users"."id" != 5) LIMIT 1
   (1.5ms)  ROLLBACK
=> false

したがって、データベースで許可されている場合でも、Railsは最初にUserが存在するかどうかを確認し、email列をNULL。データベースが許可するだけでなく、Railsも上記のように最初にチェックしない方法はありますか?

ユーザーがメールを入力する必要はありませんが、入力する場合はメールでユーザーを検索できるようにする必要があります。ユーザーをメールに関連付ける別のモデルを作成できることはわかっていますが、私は上記の方法でそれを実行したいのです。

[〜#〜] update [〜#〜]email列:

class AddEmailToUsers < ActiveRecord::Migration
  def change
    add_column :users, :email, :string
    add_index :users, :email, :unique => true
  end
end

そして、これがUserモデルに追加したコードです。

validates :email, uniqueness: true

validatesモデルにUser呼び出しを追加したことを忘れていました。 Railsが最初にチェックすることは理にかなっています。データベースの他の質問が一意のインデックスとNULLフィールドを持つことが安全であるかどうかです)? Railsで指定します。メールがnilでない限り、一意であることを検証したいですか?

33
at.

移行は機能し、複数のnull値を許可します(ほとんどのデータベースエンジンの場合)。

ただし、ユーザークラスの検証は次のようになります。

validates :email, uniqueness: true, allow_nil: true
38
aross

これがデータベースレベルで機能する理由を明確にするには、SQLで使用される3つの値のロジックを理解する必要があります:truefalsenull

nullは、通常、不明を意味するものと解釈されます。したがって、操作におけるその意味論は、通常、その特定の値が何であるかを知らず、回答がまだ解決できるかどうかを確認することと同じです。たとえば、1.0 * nullnullですが、null OR truetrueです。前者の場合、unknownによる乗算は不明ですが、後者の場合、条件式の後半によりステートメント全体が常に真になるため、左側が何であっても問題ありません。

インデックスに関しては、標準では何も指定されていないため、ベンダーは未知の意味を解釈する必要があります。個人的には、PostgreSQLのドキュメントのように一意のインデックスを定義する必要があると思います。

インデックスが一意であると宣言されている場合、インデックス値が等しい複数のテーブル行は許可されません

質問はnull = nullの値が何であるかです。正解はnullです。したがって、これらのPostgreSQLドキュメントの行間を少し読んで、一意のインデックスによって、その値に対して等値演算子がtrueを返す複数の行が許可されないと言う場合、複数のnull値を許可する必要があります。これがまさにPostgreSQLの仕組みです。そのため、その設定では、nullを値として持つ複数の行を持つ一意の列を作成できます。

一方、一意のインデックスの定義を解釈して、不等式演算子がfalseを返さない複数の行を許可しないようにしたい場合、null値を持つ複数の行を持つことはできません。誰がこの反対のセットアップで動作することを選択するでしょうか?これは、Microsoft SQL Serverが一意のインデックスを定義するために選択する方法です。

一意のインデックスを定義するこれらの両方の方法は、2003 SQL標準のnullの定義に基づいて正しいです。したがって、それは実際に基盤となるデータベースに依存します。しかし、そうは言っても、大半はPostgreSQLと同様に動作すると思います。

16
rokob