解決在Rails資料表遷移中,
以non-null為條件進行add_column時會引發的錯誤

什麼錯誤?

若在新建一張資料表時,希望其中欄位不要有空值,可以在 建立時 輸入null: false以資料庫層級進行資料驗證。但若是在 已經存在 的資料表下,希望新增一欄不為空值的欄位時,就會引發錯誤。

假設有張資料表為User,其中已有name、email等欄位,但還沒有任何資料存入(如下表)

User table
Name Email

這時想要在欄位中加入gender,並指定不能為空值:

1
2
3
def change
add_column :users, :gender, :string, null: false
end

若是以上面方式進行migration,將會出現錯誤:

1
Cannot add a NOT NULL column with default value NULL

為何產生錯誤?

SQLite在新增一個不能為空值的欄位時,會需要你順手設定他的default值;因為SQLite將欄位的default設為null,若這時限制新的欄位不能是null,卻又不給他其他default,將會產生矛盾,因此發生錯誤。

如何解決?

1. 圖法煉鋼,砍掉資料表重練

若資料表內沒有資料,且沒有其他關聯,那砍掉可能比較快;但如果已經有其他資料,就沒辦法這樣子用了。

2. 在User Model中增加資料驗證

可以在 model/user.rb 內增加資料驗證:validates_presence_of :gender 這是一個避開資料庫設定的方法,雖然也能擋掉gender是空值的情形,但資料庫層級與應用程式層級的驗證仍是不同的;若不是在資料庫中設限制,一旦應用程式轉換了就有可能讓資料出現空值,因此這個方法並沒有真正解決問題。

3. 加入default值的設定

1
2
3
def change
add_column :users, :gender, :string, null: false, default: "male"
end

若過去的資料能夠有個固定的default,這將會是個好辦法;不過以性別的例子,就不會是個好方式。

4. 先不設定null與default,之後在change_column

1
2
3
4
def change
add_column :users, :gender, :string
change_column :users, :gender, :string, null: false
end

採取先新增欄位,再瞬間增加條件限制。能夠這樣做是因為SQLite對新增欄位較為嚴謹,算是一個繞過障礙的做法吧!

但如果資料表已經有資料了…

那就沒辦法了,只能讓歷史資料在該欄位先有值,再做新的資料表遷移了。


參考連結

  1. Adding a Non-null Column with no Default Value in a Rails Migration
  2. StackOverFlow: How to solve “Cannot add a NOT NULL column with default value NULL” in SQLite3