使用SQLite进行Laravel迁移'无法添加默认值为NULL的NOT NULL列'

时间:2013-12-29 04:28:21

标签: sqlite laravel eloquent

为什么在使用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)

8 个答案:

答案 0 :(得分:16)

看起来这是一个SQLite的怪异。根据{{​​3}}:

  
    

从头开始添加表时,可以指定NOT NULL。但是,添加列时无法执行此操作。 SQLite的规范说你必须有一个默认值,这是一个糟糕的选择。

  

进一步了解Laracast forum thread about the same issue,我发现:

  
    

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

  

我认为在SQLite的世界中,不提供默认值与声明默认值应该为NULL是相同的(相反意味着此非可空列没有默认值,因此值必须在每个插页上提供它。)

如果您需要将一个不可为空的列添加到现有表中,SQLite似乎只会让您处于错误状态,该列也应该没有默认值。

答案 1 :(得分: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('');

    });
  } 

答案 2 :(得分:4)

我对Laravel并不熟悉,但显然使用after方法来指定列的顺序似乎特别提到 ONLY MySQL {{3}讨论(Laravel)似乎指出了它与SQLite一起使用的困难。它可能不兼容,因为它的功能:“...使用after方法to specify the order of columns”设计符合SQLite文档(GitHub)中的限制,用于添加列...其中包含:{{1}我不能说使用"The new column is always appended to the end of the list of existing columns."分配默认值是否可以让你接受。

答案 3 :(得分:3)

我成功使用的解决方法是检查正在使用的数据库驱动程序,并稍微修改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');
            }
        });
    }
}

答案 4 :(得分:3)

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

方法是$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;
  }
}

答案 5 :(得分:1)

你必须添加

->nullable()

表示可能具有空值的列

答案 6 :(得分:0)

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

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();
    });
}

答案 7 :(得分:0)

正如人们所解释的那样,我做到了,而且确实有效,只是在迁移过程中使您的外键为空即可

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