可以在Rails / ActiveRecord中指定允许带NULL的唯一索引吗?

我想在列上指定唯一索引,但我还需要允许NULL值(多个记录可以具有NULL值)。 在使用PostgreSQL进行测试时,我发现我可以拥有一个带有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]] => # 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  true end end 

这是我添加到User模型的代码:

 validates :email, uniqueness: true 

我忘了我已经将validates调用添加到User模型。 所以Rails首先检查是有意义的。 我想唯一的另一个问题是数据库是否可以安全地拥有唯一索引和NULL字段? 有没有办法在Rails中指定我想validation电子邮件是唯一的,除非它是nil

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

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

 validates :email, uniqueness: true, allow_nil: true 

为了阐明为什么它在数据库级别工作,您必须了解SQL中使用的三值逻辑: truefalsenull

null通常被认为是未知的,因此它在操作中的语义通常等同于不知道该特定值是什么,并查看您是否仍然能够得出答案。 因此,例如1.0 * nullnullnull OR truetrue 。 在第一种情况下,乘以未知是未知的,但在第二种情况下,条件的后半部分使整个语句始终为真,因此左侧的内容无关紧要。

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

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

那么问题应该是null = null的值是多少? 正确答案应为null 。 因此,如果您在那些PostgreSQL文档的行之间稍微阅读并说一个唯一索引将禁止多个行,对于该值,相等运算符返回true,则应允许多个null值。 这正是PostgreSQL的工作方式,因此在该设置中,您可以拥有一个包含多个行的唯一列,并将null作为值。

另一方面,如果您希望将唯一索引的定义解释为禁止不等于运算符且不返回false的多行,则您将无法使多个行具有null值。 谁会选择在这种相反的设置中运作? 这就是Microsoft SQL Server选择定义唯一索引的方式。

根据2003 SQL标准的null定义,这两种定义唯一索引的方法都是正确的。 所以它真的取决于你的底层数据库。 但话虽如此,我认为大多数操作类似于PostgreSQL。