2010-07-03 6 views
49

Je reçois l'erreur suivante en essayant d'ajouter une colonne NOT NULL à une table existante. Pourquoi cela se passe-t-il? J'ai essayé rake db: réinitialiser en pensant que les enregistrements existants sont le problème, mais même après la réinitialisation de la DB, le problème persiste. Pouvez-vous s'il vous plaît m'aider à comprendre cela.Comment résoudre "Impossible d'ajouter une colonne NOT NULL avec la valeur par défaut NULL" dans SQLite3?

File 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 NULL valeur par défaut: ALTER TABLE "profils" ADD " division_id "nombre entier NOT NULL

Répondre

34

Vous avez déjà des lignes dans la table et vous ajoutez une nouvelle colonne division_id. Il a besoin de quelque chose dans cette nouvelle colonne dans chacune des lignes existantes. SQLite choisirait normalement NULL, mais vous avez spécifié qu'il ne peut pas être NULL, alors que devrait-il être? Il n'a aucun moyen de savoir.

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

que la recommandation du blog est d'ajouter la colonne sans la contrainte non nulle, et il sera ajouté avec NULL dans chaque ligne. Ensuite, vous pouvez remplir les valeurs dans le division_id et ensuite utiliser change_column pour ajouter la contrainte non nulle.

Voir le blog auquel je suis associé pour un exemple de script de migration qui effectue ce processus en trois étapes.

+6

Votre hypothèse sur il être déjà des lignes dans la table semble correcte, et serait avec à peu près tous les autres rdbms. Cependant, j'ai noté dans ma réponse que SQLite est une exception à cela. Cette erreur apparaît même lorsque la table est vide, j'ai donc posté une solution plus courte. –

+0

Ce n'est pas correct - sqlite n'a aucun moyen de changer les colonnes. Je ne sais pas ce qu'est 'change_column', mais ce n'est pas sqlite. – Benubird

+0

@Bububird, 'change_column' est une méthode API dans [Ruby on Rails migrations] (http://guides.rubyonrails.org/active_record_migrations.html), qui est ce que le PO demandait. –

141

C'est (ce que je considère) un petit problème avec SQLite. Cette erreur se produit s'il existe 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 un défaut pour cela, ce qui est un mauvais choix. L'ajout d'une valeur par défaut n'est pas une option car cela annule le 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. NOTE: ceci est dans 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 avons ajouté la colonne sans la contrainte NOT NULL, puis modifier immédiatement la colonne pour ajouter la contrainte. Nous pouvons le faire parce que, bien que SQLite soit apparemment très concerné lors de l'ajout d'une colonne, ce n'est pas si difficile avec les changements de colonne. C'est une odeur de design claire dans mon livre.

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

+0

Ouais, bonne réponse, +1 de moi –

+0

Merci! C'est exactement ce dont j'avais besoin. – PBJ

+0

fonctionne! Merci pour l'aide. – Hendrik

6

Si vous avez une table avec des lignes existantes, vous devrez mettre à jour les lignes existantes avant d'ajouter votre contrainte null.Le Guide on 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 
+0

Si vous voulez que votre migration soit réversible ('rake db: rollback'), ajoutez la méthode' down' et remplacez 'change' par' up' – sampi

Questions connexes