web-dev-qa-db-fra.com

Comment résoudre «Impossible d'ajouter une colonne NOT NULL avec la valeur par défaut NULL» dans SQLite3?

J'obtiens l'erreur suivante en essayant d'ajouter une colonne NOT NULL à une table existante. Pourquoi cela se produit-il?. J'ai essayé de rake db: reset en pensant que les enregistrements existants sont le problème, mais même après la réinitialisation de la base de données, le problème persiste. Pouvez-vous m'aider à comprendre cela.

Fichier de migration

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

Message d'erreur

SQLite3 :: SQLException: Impossible d'ajouter une colonne NOT NULL avec la valeur par défaut NULL: ALTER TABLE "profiles" ADD "division_id" integer NOT NULL

55
felix

Vous avez déjà des lignes dans le tableau et vous ajoutez une nouvelle colonne division_id. Il a besoin de quelque chose dans cette nouvelle colonne dans chacune des lignes existantes.

SQLite choisit généralement NULL, mais vous avez spécifié qu'il ne peut pas être NULL, alors que devrait-il être? Il n'a aucun moyen de le savoir.

Voir:

La recommandation de ce blog est d'ajouter la colonne sans la contrainte non nulle, et elle sera ajoutée avec NULL dans chaque ligne. Ensuite, vous pouvez remplir des valeurs dans le division_id puis utilisez change_column pour ajouter la contrainte non nulle.

Consultez les blogs auxquels j'ai lié pour une description d'un script de migration qui effectue ce processus en trois étapes.

39
Bill Karwin

C'est (ce que je considérerais) un problème avec SQLite. Cette erreur se produit qu'il y ait des enregistrements dans la table ou non.

Lorsque vous ajoutez une table à partir de zéro, vous pouvez spécifier NOT NULL, ce que vous faites avec la notation ": null => false". Cependant, vous ne pouvez pas le faire lors de l'ajout d'une colonne. La spécification de SQLite indique que vous devez avoir une valeur par défaut pour cela, ce qui est un mauvais choix. L'ajout d'une valeur par défaut n'est pas une option car elle va à l'encontre du but d'avoir une clé étrangère NOT NULL - à savoir, l'intégrité des données.

Voici un moyen de contourner ce problème, et vous pouvez tout faire dans la même migration. REMARQUE: c'est pour le cas où vous n'avez pas déjà d'enregistrements dans la base de données.

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

Nous ajoutons la colonne sans la contrainte NOT NULL, puis modifions immédiatement la colonne pour ajouter la contrainte. Nous pouvons le faire car, bien que SQLite soit apparemment très préoccupé lors d'un ajout de colonne, il n'est pas si difficile avec les changements de colonne. Ceci est une odeur de conception claire dans mon livre.

C'est certainement un hack, mais c'est plus court que plusieurs migrations et cela fonctionnera toujours avec des bases de données SQL plus robustes dans votre environnement de production.

154
Jaime Bellmyer

Si vous avez une table avec des lignes existantes, vous devrez mettre à jour les lignes existantes avant d'ajouter votre contrainte null. Le Guide sur les migrations recommande d'utiliser un modèle local, comme ceci:

Rails 4 et plus:

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
6
JosephL