【问题标题】:Laravel migration with SQLite 'Cannot add a NOT NULL column with default value NULL'使用 SQLite 迁移 Laravel '无法添加默认值为 NULL 的 NOT NULL 列'
【发布时间】:2014-01-16 07:30:19
【问题描述】:

为什么我在使用 SQLite 驱动程序时会收到此警告?我的 MySQL 驱动程序没有问题,但 SQLite 抛出了这个错误。

这对我来说没有意义,因为我知道播种是在所有迁移完成后才发生的,所以为什么它抱怨这个问题只有在数据库中已经存在数据时才会出现。

我的两次迁移是

第一次迁移

  public function up() {
    Schema::create('users', function($table) {
      $table->increments('id');
      $table->string('username');
      $table->string('email');
      $table->string('password');
    });
  } 

第二次迁移

public function up() {
    Schema::table('users', function(Blueprint $table) {
        $table->date('birthday')->after('id');
        $table->string('last_name')->after('id');
        $table->string('first_name')->after('id');
    });
}

错误

Exception: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL column with default value NULL (SQL: alter table "users" add column "birthday" date not null)

【问题讨论】:

    标签: sqlite laravel eloquent


    【解决方案1】:

    我按照人们的解释做了,它奏效了,只是让你的外键在你的迁移中可以为空

    public function up()
    {
        Schema::table('products', function (Blueprint $table) {
            $table->foreignId('category_id')
                ->nullable()
                ->constrained();
        });
    }
    

    【讨论】:

    • 使外键可以为空并不是一个更好的解决方案,它满足您的用例但不是我的recommended solution
    【解决方案2】:

    所有人的解决方案都很好,但我想找到一种可重用且可读的方法来做到这一点,所以我做了一个 trait 并希望它可以帮助你从迁移中删除一些样板代码。

    方法是$this->databaseDriverIs("driver_name_here");

    这是我在典型的表创建迁移中使用它的方式:

    <?php
    
    use App\Traits\WithDatabaseDriver;
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreateCountryTable extends Migration
    {
      use WithDatabaseDriver;
    
      public function up()
      {
        Schema::create("country", function (Blueprint $table) {
          $table->increments("id");
          $table->string("name");
    
          $table->unique("name", "unique_country_name");
        });
    
        Schema::table("continent", function (Blueprint $table) {
          $column = $table->unsignedInteger("countryId");
    
          // This is where we apply the "fix" if 
          // the driver is SQLite
          if ($this->databaseDriverIs("sqlite")) {
            $column->nullable();
          }
    
          $table->foreign("countryId")->references("id")->on("country");
        });
      }
    
      public function down()
      {
        Schema::dropIfExists("country");
      }
    }
    

    这就是完成所有工作的特质:

    <?php
    
    namespace App\Traits;
    
    trait WithDatabaseDriver
    {
      /**
       * @var string
       */
      private $driver;
    
      public function __construct()
      {
        $this->driver = config("database.default");
      }
    
      public function databaseDriverIs(string $driver): bool
      {
        return $this->driver === $driver;
      }
    }
    

    【讨论】:

    • 如果你的项目使用很多数据库: $this->driver = DB::connection()->getPDO()->getAttribute(PDO::ATTR_DRIVER_NAME);或 $this->driver = Schema::connection($this->getConnection())->getConnection()->getDriverName();
    【解决方案3】:

    解决此问题的另一种解决方法是首先将字段创建为可为空,然后将字段更改为非空。因此,例如在这种情况下,我们将执行以下操作:

    public function up() {
        // Create fields first as nullable
        Schema::table('users', function(Blueprint $table) {
            $table->date('birthday')->after('id')->nullable();
            $table->string('last_name')->after('id')->nullable();
            $table->string('first_name')->after('id')->nullable();
        });
    
        // Either truncate existing records or assign a value to new fields
        if (true) {
            DB::table('users')->truncate();
        } else {
            DB::table('users')->update([
                'birthday' => '2019-05-01',
                'last_name' => 'last name',
                'first_name' => 'first name',
            ]);
        }
    
        // Change the fields to not be null
        Schema::table('users', function(Blueprint $table) {
            $table->date('birthday')->nullable(false)->change();
            $table->string('last_name')->nullable(false)->change();
            $table->string('first_name')->nullable(false)->change();
        });
    }
    

    【讨论】:

      【解决方案4】:

      我成功使用的一种解决方法是检查正在使用的数据库驱动程序并稍微修改 SQLite 的迁移。

      例如:

      class MyMigration extends Migration
      {
          public function up()
          {
              $driver = Schema::connection($this->getConnection())->getConnection()->getDriverName();
      
              Schema::table('invoices', function (Blueprint $table) use ($driver) {
                  $table->string('stripe_invoice')->nullable()->change();
      
                  if ('sqlite' === $driver) {
                      $table->string('stripe_invoice_number')->default('');
                  } else {
                      $table->string('stripe_invoice_number')->after('stripe_invoice');
                  }
              });
          }
      }
      

      【讨论】:

      • 谢谢,我喜欢这个内存测试解决方案。
      【解决方案5】:

      看起来这是一个 SQLite 奇怪的东西。根据Laracast forum thread about the same issue

      从头开始添加表时,您可以指定 NOT NULL。但是,添加列时不能这样做。 SQLite 的规范说你必须为此设置一个默认值,这是一个糟糕的选择。

      再看SQLite ALTER TABLE docs,我发现:

      如果指定了 NOT NULL 约束,则该列必须具有 NULL 以外的默认值。

      我想在 SQLite 的世界中,不提供默认值与说默认值应该为 NULL 是一回事(而不是意味着这个不可为空的列没有默认值,所以值 必须在每次插入时提供)。

      如果您需要向现有表中添加不可为空的列,SQLite 似乎只会让您处于糟糕的状态,该列也不应该有默认值。

      【讨论】:

      • PDO 应该使用隐式转换。
      【解决方案6】:

      如果你不希望该列是 nullable - 那么你需要让 Laravel 知道默认的应该是什么

      一个选项是像这样的空字符串""

      public function up() {
          Schema::create('users', function($table) {
            $table->date('birthday')->after('id')->default('');
            $table->string('last_name')->after('id')->default('');
            $table->string('first_name')->after('id')->default('');
      
          });
        } 
      

      【讨论】:

      • @Fabrizio - 这有帮助吗?
      • 它可能会破坏您的代码,您可以使用is_null 再次检查这些变量是否为空,否则,因为“”表示该列不为空,而是一个空字符串。
      • 这使代码运行,但完全超出了在数据库中检查 NOT NULL 的目的。
      • 实际上,这是错误的,因为在 MySQL 中,您可以在不指定默认值的情况下将列设置为 NOT NULL:如果您想强制用户设置值,这可能是一种期望的行为.
      • @fudo - OP 的原始问题是关于 SQLite - 而不是 mySQL。
      【解决方案7】:

      你必须添加

      ->nullable()
      

      对于可能具有空值的列

      【讨论】:

      • -&gt;default('');
      【解决方案8】:

      我不熟悉 Laravel,但显然使用 after 方法来指定列的顺序似乎特别提到 ONLY MySQL (Laravel) 和讨论 @987654322 @ 似乎指出了它与 SQLite 一起使用的困难。它可能不兼容,因为它的功能:“...使用 after 方法 to specify the order of columns”设计违反了 SQLite 文档 (SQLite) 中添加列的限制...其中显示:"The new column is always appended to the end of the list of existing columns." 我不能说使用-&gt;default($value) 分配默认值是否可以帮助您。

      【讨论】:

      • after 方法调用在 SQLite 上被忽略。我自己也有同样的问题,这似乎是 SQLite 适配器的一个限制 - 第一次创建表时,可以让某些列不可为空,但是在更改和添加列时,即使表是空的,由于某种原因它犹豫了。当我拥有 SQLite 的唯一原因是进行内存验收测试时,这有点烦人
      • 仍然有这个问题。您找到解决方法了吗?
      猜你喜欢
      • 2018-01-25
      • 2011-03-11
      • 2012-04-08
      • 2015-02-17
      • 2012-01-18
      • 1970-01-01
      • 2011-03-12
      • 1970-01-01
      • 2014-11-19
      相关资源
      最近更新 更多