Как решить "Не удается добавить столбец 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, так что это должно быть? У него нет возможности узнать.
Увидеть:
- Добавление ненулевого столбца без значения по умолчанию в Rails Migration (2009 г., более недоступен, так что это снимок на archive.org)
- Добавление столбца NOT NULL в существующую таблицу (2014)
Рекомендация этого блога заключается в добавлении столбца без ограничения 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
В секунду
Не забывайте, что есть что-то положительное в том, чтобы требовать значение по умолчанию с 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, когда таблица пуста. Хорошим обходным решением, возможно, является создание таблицы с самого начала.