26

我想指定一個列上的唯一索引,但我也需要允許NULL值(多個記錄可以有NULL值)。當與PostgreSQL的測試中,我看到,我可以有1條記錄與NULL值,但未來會造成一個問題:可以使用Rails/ActiveRecord中允許的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: "[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

+1

爲什麼rails會檢查用戶是否存在?它是你的用戶模型中的一些代碼還是默認行爲?你能發佈遷移代碼嗎? –

+0

你有任何驗證?我猜測查詢即將到來驗證。 –

+0

你們是對的,我更新了這個問題。 –

回答

28

您的遷移將會起作用,並且將允許值爲null(適用於大多數數據庫引擎)。

但是您對用戶類的驗證應如下所示。

validates :email, uniqueness: true, allow_nil: true 
11

解釋,爲什麼這個工程在數據庫級別,你必須瞭解在SQL中使用的三值邏輯:truefalsenull

null通常被認爲是未知的,因此它在操作中的語義通常相當於不知道該特定值是什麼,看看你是否仍然可以找出答案。因此,例如1.0 * nullnull,但是null OR truetrue。在第一種情況下,未知的乘法是未知的,但在第二種情況下,條件的後半部分使得整個陳述始終爲真,因此左側的內容並不重要。

現在談到索引時,標準沒有指定任何東西,所以供應商只能解釋未知的方法。就個人而言,我認爲唯一索引應該被定義爲PostgreSQL文檔:

當一個索引聲明爲唯一的,具有相同索引值的多個表中的行不會被允許

問題應該然後null = null的值是多少?正確的答案應該是null。因此,如果您在這些PostgreSQL文檔的各行之間閱讀一段時間,並且說一個唯一索引將禁止多個等於運算符爲所述值返回true的行,則應該允許多個值null。這正是PostgreSQL的工作原理,因此在該設置中,您可以擁有一個包含null作爲值的多行的唯一列。另一方面,如果您想要解釋唯一索引的定義,以便禁止不等行運算符不返回false的多行,那麼您將無法使用具有null值的多行。誰會選擇在這種對立設置中操作?這是Microsoft SQL Server選擇定義唯一索引的方式。

基於2003 SQL標準的null定義,這兩種定義唯一索引的方法都是正確的。所以它真的取決於你的底層數據庫。但據說,我認爲大多數人的操作類似於PostgreSQL。

+0

感謝您的詳細解釋 – Matthieu

相關問題