41

我想在列上指定唯一索引,但我还需要允许NULL值(多条记录可以有NULL值)。使用 PostgreSQL 进行测试时,我看到我可以有 1 条记录NULL,但下一条记录会导致问题:

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: "a@b.com", 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存在具有不同 id 且email列设置为NULL. 有没有一种方法不仅数据库可以允许它,而且 Rails 也不会像上面那样首先检查?

这个想法是用户不必输入电子邮件,但如果他们这样做,我需要能够通过他们的电子邮件找到用户。我知道我可以创建另一个模型来将用户与电子邮件相关联,但我更愿意采用上述方式。

更新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

4

2 回答 2

53

您的迁移将起作用并允许多个null值(对于大多数数据库引擎)。

但是您对用户类的验证应如下所示。

validates :email, uniqueness: true, allow_nil: true
于 2013-08-28T20:19:00.700 回答
29

要阐明为什么这在数据库级别有效,您必须了解 SQL 中使用的三值逻辑:truefalsenull.

null通常被认为是未知的,因此它在操作中的语义通常等同于不知道那个特定的值是什么,看看你是否还能找到答案。例如1.0 * nullis nullbut null OR trueis true。在第一种情况下,乘以未知数是未知的,但在第二种情况下,条件的后半部分使整个语句始终为真,因此左侧的内容无关紧要。

现在,当涉及到索引时,该标准没有指定任何内容,因此供应商只能解释未知的含义。就个人而言,我认为应该在 PostgreSQL 文档中定义唯一索引:

当索引被声明为唯一时,将不允许具有相同索引值的多个表行

那么问题应该是什么是价值null = null?正确答案应该是null。因此,如果您在那些 PostgreSQL 文档的行之间读了一点,并说唯一索引将不允许相等运算符为所述值返回 true 的多行,那么null应该允许多个值。这正是 PostgreSQL 的工作方式,因此在该设置中,您可以拥有一个具有多行null作为值的唯一列。

另一方面,如果您想将唯一索引的定义解释为不允许不等式运算符不返回 false 的多行,那么您将无法拥有多行具有null值。谁会选择在这种相反的设置中进行操作?这就是 Microsoft SQL Server 选择定义唯一索引的方式。

根据 2003 年 SQL 标准对null. 所以它真的取决于你的底层数据库。但话虽如此,我认为大多数操作类似于 PostgreSQL。

于 2016-02-01T18:28:43.047 回答