Laravel 使用 SQLite 迁移“无法添加默认值为 NULL 的 NOT NULL 列”

问题描述 投票:0回答:9

为什么我在使用 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
9个回答
34
投票

看起来这是 SQLite 的一个奇怪现象。根据有关同一问题的 Laracast 论坛帖子

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

进一步查看SQLite

ALTER TABLE
文档,我发现:

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

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

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


21
投票

我成功使用的解决方法是检查正在使用哪个数据库驱动程序并稍微修改 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 ($driver === 'sqlite') {
                $table->string('stripe_invoice_number')->default('');
            } else {
                $table->string('stripe_invoice_number')->after('stripe_invoice');
            }
        });
    }
}

16
投票

如果您不希望该列为

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

    });
  } 

9
投票

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

方法是

$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;
  }
}

4
投票

我对 Laravel 不熟悉,但显然使用

after
方法来指定列的顺序似乎特别提到了 ONLY
MySQL
(Laravel) 和讨论 (GitHub) 似乎指出它与 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."
我可以'不知道用
->default($value)
指定默认值是否可以帮助你。


1
投票

你必须添加

->nullable()

对于可能具有空值的列


1
投票

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

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

0
投票

有趣的是,就我而言,我必须运行

php artisan config:cache
,它实际上开始为我的 SQLite 数据库工作(source)。

我会提前跑步

php artisan config:clear

很奇怪,但它完成了工作。


-1
投票

我按照人们的解释做了,它有效,只需在迁移中使外键可为空即可

public function up()
{
    Schema::table('products', function (Blueprint $table) {
        $table->foreignId('category_id')
            ->nullable()
            ->constrained();
    });
}
© www.soinside.com 2019 - 2024. All rights reserved.