2010-07-03 27 views
49

Tôi nhận được lỗi sau khi cố gắng thêm cột NOT NULL vào bảng hiện có. Tại sao nó lại xảy ra? Tôi đã thử rake db: thiết lập lại suy nghĩ rằng các hồ sơ hiện có là vấn đề, nhưng ngay cả sau khi đặt lại DB, vấn đề vẫn tồn tại. Bạn có thể vui lòng giúp tôi tìm ra điều này không.Cách giải quyết "Không thể thêm cột NOT NULL với giá trị mặc định NULL" trong SQLite3?

Migration file

class AddDivisionIdToProfile < ActiveRecord::Migration 
    def self.up 
    add_column :profiles, :division_id, :integer, :null => false 
    end 

    def self.down 
    remove_column :profiles, :division_id 
    end 
end 

Thông báo lỗi

SQLite3 :: SQLException: Không thể thêm một KHÔNG cột NULL với giá trị mặc định NULL: ALTER TABLE "profile" ADD " division_id "integer NOT NULL

Trả lời

34

Bạn đã có các hàng trong bảng và bạn đang thêm một cột mới division_id. Nó cần một cái gì đó trong cột mới đó trong mỗi hàng hiện có.

SQLite thường sẽ chọn NULL, nhưng bạn đã chỉ định nó không thể là NULL, vì vậy nó nên là gì? Nó không có cách nào để biết.

Xem Adding a Non-null Column with no Default Value in a Rails Migration

Đó giới thiệu trên blog là để thêm cột mà không kiềm chế không null, và nó sẽ được bổ sung với NULL trong mỗi hàng. Sau đó, bạn có thể điền vào các giá trị trong division_id và sau đó sử dụng change_column để thêm ràng buộc không null.

Xem blog tôi đã liên kết để xem ví dụ về tập lệnh di chuyển thực hiện quy trình ba bước này.

+6

giả định của bạn về đã có được hàng trong bảng âm thanh chính xác, và nó sẽ chỉ là về bất kỳ rdbms nào khác. Tuy nhiên, tôi lưu ý trong câu trả lời của tôi rằng SQLite là một ngoại lệ cho điều này. Lỗi này xuất hiện ngay cả khi bảng trống, vì vậy tôi đã đăng một giải pháp ngắn hơn. –

+0

Điều này không đúng - sqlite không có cách nào để thay đổi cột. Tôi không biết 'change_column' là gì, nhưng nó không phải là sqlite. – Benubird

+0

@Benubird, 'change_column' là một phương thức API trong [di chuyển Ruby on Rails] (http://guides.rubyonrails.org/active_record_migrations.html), đó là những gì OP hỏi. –

141

Đây là (những gì tôi sẽ xem xét) một trục trặc với SQLite. Lỗi này xảy ra cho dù có bất kỳ bản ghi nào trong bảng hay không.

Khi thêm bảng từ đầu, bạn có thể chỉ định NOT NULL, đó là những gì bạn đang làm với ký hiệu ": null => false". Tuy nhiên, bạn không thể làm điều này khi thêm cột. Đặc tả của SQLite nói rằng bạn phải có một mặc định cho điều này, đó là một sự lựa chọn tồi. Thêm một giá trị mặc định không phải là một tùy chọn vì nó đánh bại mục đích của việc có một khóa ngoài NOT NULL - cụ thể là, toàn vẹn dữ liệu.

Đây là cách để khắc phục sự cố này và bạn có thể làm tất cả trong cùng một quá trình di chuyển. LƯU Ý: đây là trường hợp bạn chưa có hồ sơ trong cơ sở dữ liệu.

class AddDivisionIdToProfile < ActiveRecord::Migration 
    def self.up 
    add_column :profiles, :division_id, :integer 
    change_column :profiles, :division_id, :integer, :null => false 
    end 

    def self.down 
    remove_column :profiles, :division_id 
    end 
end 

Chúng tôi đang thêm cột không có ràng buộc NOT NULL, sau đó ngay lập tức thay đổi cột để thêm ràng buộc. Chúng ta có thể làm điều này bởi vì trong khi SQLite dường như rất quan tâm trong một cột thêm vào, nó không quá cầu kỳ với các thay đổi cột. Đây là một mùi thiết kế rõ ràng trong cuốn sách của tôi.

Đó chắc chắn là một hack, nhưng nó ngắn hơn nhiều lần di chuyển và nó sẽ vẫn làm việc với các cơ sở dữ liệu SQL mạnh mẽ hơn trong môi trường sản xuất của bạn.

+0

Đúng, câu trả lời hay, +1 từ tôi –

+0

Cảm ơn! Đây chính xác là những gì tôi cần. – PBJ

+0

hoạt động! Cảm ơn đã giúp đỡ. – Hendrik

6

Nếu bạn có bảng có các hàng hiện có thì bạn sẽ cần phải cập nhật các hàng hiện có trước khi thêm ràng buộc null.Các Guide on migrations khuyến cáo sử dụng một mô hình địa phương, như vậy:

Rails 4 trở lên:

class AddDivisionIdToProfile < ActiveRecord::Migration 
    class Profile < ActiveRecord::Base 
    end 

    def change 
    add_column :profiles, :division_id, :integer 

    Profile.reset_column_information 
    reversible do |dir| 
     dir.up { Profile.update_all division_id: Division.first.id } 
    end 

    change_column :profiles, :division_id, :integer, :null => false 
    end 

end 

Rails 3

class AddDivisionIdToProfile < ActiveRecord::Migration 
    class Profile < ActiveRecord::Base 
    end 

    def change 
    add_column :profiles, :division_id, :integer 

    Profile.reset_column_information 
    Profile.all.each do |profile| 
     profile.update_attributes!(:division_id => Division.first.id) 
    end 

    change_column :profiles, :division_id, :integer, :null => false 
    end 

end 
+0

Nếu bạn muốn di chuyển của bạn có thể đảo ngược ('rake db: rollback') thêm phương thức' down' và thay thế 'change' bằng' up' – sampi

Các vấn đề liên quan