Как решить "Невозможно добавить столбец NOT NULL со значением по умолчанию NULL" в SQLite3?

Я получаю следующую ошибку при попытке добавить столбец NOT NULL в существующую таблицу. Почему это происходит? Я попробовал rake db: reset, считая, что существующие записи являются проблемой, но даже после сброса БД проблема сохраняется. Не могли бы вы помочь мне понять это.

Файл миграции

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

Сообщение об ошибке

SQLite3:: SQLException: не может добавить столбец NOT NULL со значением по умолчанию NULL: ALTER TABLE "profiles" ADD "division_id" integer NOT NULL

Ответ 1

У вас уже есть строки в таблице, и вы добавляете новый столбец division_id. Это нужно что-то в этом новом столбце в каждой из существующих строк.

SQLite обычно выбирает NULL, но вы указали, что оно не может быть NULL, так что это должно быть? У него нет возможности узнать.

Увидеть:

Эта рекомендация для блога заключается в добавлении столбца без ограничения not null, и он будет добавляться с NULL в каждой строке. Тогда вы можете заполнить значения в division_id, а затем использовать change_column добавить не нулевое ограничение.

См. Блоги, на которые я ссылался, для описания скрипта миграции, который выполняет этот трехэтапный процесс.

Ответ 2

Это (что я считаю) сбой с SQLite. Эта ошибка возникает, если в таблице есть какие-либо записи.

При добавлении таблицы с нуля вы можете указать NOT NULL, что вы делаете с нотой ": null = > false". Однако при добавлении столбца это невозможно. Спецификация SQLite говорит, что для этого вам нужно иметь по умолчанию, что является плохим выбором. Добавление значения по умолчанию не является опцией, потому что оно нарушает цель использования внешнего ключа NOT NULL, а именно целостности данных.

Здесь можно обойти этот глюк, и вы можете сделать все это в той же миграции. ПРИМЕЧАНИЕ. Это относится к случаю, когда у вас еще нет записей в базе данных.

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

Мы добавляем столбец без ограничения NOT NULL, а затем сразу же изменяем столбец, чтобы добавить ограничение. Мы можем это сделать, потому что SQLite, по-видимому, очень обеспокоен при добавлении столбца, он не настолько разборчив с изменениями столбцов. Это ясный дизайн запаха в моей книге.

Это определенно взлом, но он короче нескольких миграций, и он все равно будет работать с более надежными базами данных SQL в вашей рабочей среде.

Ответ 3

Если у вас есть таблица с существующими строками, вам необходимо обновить существующие строки перед добавлением ограничения null. Руководство по миграции рекомендует использовать локальную модель, например:

Рельсы 4 и выше:

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

Рельсы 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