Как решить "Не удается добавить столбец 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 "профилирует" ДОБАВИТЬ "идентификатор_д деления" целое число НЕ ПУСТО

6 ответов

Решение

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

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

Увидеть:

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

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

Это (что я бы рассмотрел) глюк с 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 в вашей производственной среде.

Вы можете добавить столбец со значением по умолчанию:

      ALTER TABLE table1 ADD COLUMN userId INTEGER NOT NULL DEFAULT 1

Если у вас есть таблица с существующими строками, вам нужно будет обновить существующие строки перед добавлением 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

В Rails 6 у меня сработала следующая миграция:

class AddDivisionToProfile < ActiveRecord::Migration[6.0]
  def change
    add_reference :profiles, :division, foreign_key: true
    change_column_null :profiles, :division_id, false
  end
end

Заметка :division в первой строке и :division_id В секунду

API Doc для change_column_null

Не забывайте, что есть что-то положительное в том, чтобы требовать значение по умолчанию с ALTER TABLE ADD COLUMN NOT NULL, по крайней мере, при добавлении столбца в таблицу с существующими данными. Как описано в https://www.sqlite.org/lang_altertable.html#altertabaddcol:

Команда ALTER TABLE работает путем изменения текста SQL схемы, хранящейся в таблице sqlite_schema. Никаких изменений в содержимом таблицы при переименовании или добавлении столбцов не производится. Из-за этого время выполнения таких команд ALTER TABLE не зависит от количества данных в таблице. Они запускаются в таблице с 10 миллионами строк так же быстро, как и в таблице с одной строкой.

Сам формат файла поддерживает этот https://www.sqlite.org/fileformat.html.

Запись может иметь меньше значений, чем количество столбцов в соответствующей таблице. Это может произойти, например, после того, как SQL-оператор ALTERTABLE ... ADD COLUMN увеличил количество столбцов в схеме таблицы без изменения ранее существовавших строк в таблице. Пропущенные значения в конце записи заполняются с использованием значения по умолчанию для соответствующих столбцов, определенных в схеме таблицы.

С помощью этого трюка можно добавить новый столбец, обновив только схему, операция, которая заняла 387 миллисекунд с тестовой таблицей, содержащей 6,7 миллиона строк. Существующие записи в области данных вообще не затрагиваются, и экономия времени огромна. Недостающие значения для добавленного столбца поступают из схемы «на лету», а значение по умолчанию - ПУСТО (NULL), если не указано иное. Если новый столбец НЕ ПУСТОЙ, тогда значение по умолчанию должно быть установлено на другое значение.

Я не знаю, почему нет специального пути для ALTER TABLE ADD COLUMN NOT NULL, когда таблица пуста. Хорошим обходным решением, возможно, является создание таблицы с самого начала.

Другие вопросы по тегам