Laravel 5.1 Migration and Seeding Cannot truncate a table referenced in a foreign key constraint - mysql

I'm trying to run the migration (see below) and seed the database, but when I run
php artisan migrate --seed
I get this error:
Migration table created successfully.
Migrated: 2015_06_17_100000_create_users_table
Migrated: 2015_06_17_200000_create_password_resets_table
Migrated: 2015_06_17_300000_create_vehicles_table
[Illuminate\Database\QueryException]
SQLSTATE[42000]: Syntax error or access violation: 1701 Cannot truncate a table
referenced in a foreign key constraint (`app`.`vehicles`, CONSTRAINT `vehic
les_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `app`.`users` (`id`
)) (SQL: truncate `users`)
[PDOException]
SQLSTATE[42000]: Syntax error or access violation: 1701 Cannot truncate a table
referenced in a foreign key constraint (`app`.`vehicles`, CONSTRAINT `vehic
les_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `app`.`users` (`id`
))
I looked up what this error is supposed to mean, and also found examples of other people running into the same problem, even just related to using MySQL, and their solutions, but applying:
DB::statement('SET FOREIGN_KEY_CHECKS=0;'); and
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
Within down() doesn't seem to work and when I run describe in MySQL the tables look right.
The migrations are named properly to make sure the users table is migrated first, and then vehicles so the foreign key can be applied, and the tables being setup up correctly suggests the migrations were run, but then the error occurs. I dropped and recreated the DB and tried it again and it is the same result. I also don't understand why it is trying to truncate on the first migration and seed of the database, I wouldn't have thought that would occur when you tried to run php artisan migrate:refresh --seed.
// 2015_06_17_100000_create_users_table.php
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('username', 60)->unique();
$table->string('email', 200)->unique();
$table->string('password', 255);
$table->string('role')->default('user');
$table->rememberToken();
$table->timestamps();
});
}
}
public function down()
{
Schema::drop('users');
}
// 2015_06_17_300000_create_vehicles_table.php
class CreateVehiclesTable extends Migration
{
public function up()
{
Schema::create('vehicles', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('make');
$table->string('model');
$table->string('year');
$table->string('color');
$table->string('plate');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users');
});
}
}
public function down()
{
Schema::drop('vehicles');
}

DB::statement('SET FOREIGN_KEY_CHECKS=0;');
App\User::truncate();
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
And it works!

As the error says, you can not truncate tables referenced by foreign keys. Delete should work though...
DB::table('some_table')->delete();

before drop
Schema::disableForeignKeyConstraints();
and before close run method
Schema::enableForeignKeyConstraints();

To clear a table using Eloquent:
Model::query()->delete();
Example using default user model
User::query()->delete();

I faced the same issue with my Role and Permission setup and this is what I did that worked as I wanted. Truncate() will reset the Increment column to 1 but throw a foreign key error while delete on the other hand works fine but doesn't reset the increment column, so I did the following in my Seeder's file (i.e RoleSeeder.php in my case)
1. [ delete() method ]
$roles = [];
... // Some foreach statement to prepare an array of data for DB insert()
// Delete and Reset Table
DB::table('roles')->delete();
DB::statement("ALTER TABLE `roles` AUTO_INCREMENT = 1");
// Insert into table
DB::table('roles')->insert($roles);
This will cascade all other child tables attached to the roles table. in my case users_roles table. This way I avoided disabling and enabling foreign key checks.
2. Something to put in mind / Second Approach [ truncate() method ]
if you don't have the intention of deleting all the data stored in the child's table (in my case users_roles table) ... You can go with truncate() and then in the DatabaseSeeders.php file you disable and enable foreign key check. As I tested this and the users_roles data was intact, the seed on affected roles table.
//RoleSeeders.php File
$roles = [];
... // Some foreach statement to prepare an array of data for DB insert()
// Truncate Table
DB::table('roles')->truncate();
// Insert into table
DB::table('roles')->insert($roles);
Then in the DatabaseSeeder.php file, you do;
public function run()
{
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
$this->call([
RoleSeeder::class,
]);
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
}
But I prefer the delete() method, since I don't have to disable/enable the foreign key check

you can use
DB::table('your_table_name')->delete();
to empty a table, this won't delete the table structure. But the auto increment id will not start from initial number.

Here is what works for me every time.
When you're adding the foreign key, make sure to add cascade.
the syntax is like this
$table->foreign('column')->references('id')->on('table_name')->onDelete('cascade');
Make sure to replace id with whatever field is applicable for you.
Now before running the seeding add this instead of trucate
DB::statement('DELETE FROM table_name');
It will delete all the data.
Hope this helps.

You could just drop it with.
$table->dropForeign('posts_user_id_foreign');

I'm using Laravel 7.x, this worked for me. Goes without saying that it should only be used in development. To read more, check it out here.
DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* #return void
*/
public function run()
{
// the Eloquent part and disabling and enabling of foreign keys is only intended for development
Eloquent::unguard();
//disable foreign key check for this connection before running seeders
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
$this->call(RolesTableSeeder::class);
$this->call(UsersTableSeeder::class);
// supposed to only apply to a single connection and reset it's self
// but I like to explicitly undo what I've done for clarity
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
}
}

Related

Database columns exclusive or relationship

In my project it would come in handy to put two boolean columns of a table in an 'exclusice or' like relationship, is that somehow possible in laravel? FYI: I use mysql as a driver if that matters.
A CHECK constraint can be used to check specific requirements for a table row. This logic be placed inside the up function of a migration.
public function up ()
{
Schema::create('order', function (Blueprint $table) {
$table->increments('id');
$table->boolean('in_transit');
$table->boolean('is_delivered');
});
// Add the constraint
DB::statement('ALTER TABLE order ADD CONSTRAINT chk_delivery CHECK ((in_transit AND NOT is_delivered) OR (is_delivered AND NOT in_transit));');
}

I need to update existing foreign key, but I can't drop it in order to update it

I've got a migration to correct existing foreign key, it looks like this
public function up()
{
Schema::table('content_term', function (Blueprint $table) {
$table->dropForeign('content_term_content_id_foreign');
$table->foreign('content_id')->references('id')->on('content')->onUpdate('cascade')->onDelete('cascade');
});
}
The original table and foregin key looked like this
public function up()
{
Schema::create('content_term', function (Blueprint $table) {
$table->increments('id');
$table->unsignedBigInteger('content_id')->unsigned();
$table->unsignedInteger('term_id');
$table->foreign('content_id')->references('id')->on('content')->onUpdate('cascade');
$table->foreign('term_id')->references('id')->on('terms')->onDelete('cascade')->onUpdate('cascade');
});
}
I forgot to add onDelete('cascade') for content_id foreign key creation and need to correct that in a new migration.
When I run php artisan migrate with thew latest migration I've added I get this
SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'content_term_content_id_foreign'; check that column/key exists (SQL: alter table content_term drop foreign key content_term_content_id_foreign)
MYSQL complains that the foreign key does not exist in content_term table. When I check that table using phpmyadmin I can clearly see that key exists. See attached pic for proof of that.
What's the problem with my new migration, why is this line causing a problem:
$table->dropForeign('content_term_content_id_foreign');

Switching primary key from id to a combination of values in a pivot table

I am trying to switch the primary key of a pivot table from "id" to a combination of two values using a migration inside a laravel project. My up method looks as follows and it works fine:
public function up() {
Schema::table('gallery_image', function (Blueprint $table) {
$table->dropColumn('id');
$table->primary(['image_id', 'gallery_id']);
});
}
However, when I declare the down method in order to undo the above changes like this:
public function down() {
Schema::table('gallery_image', function (Blueprint $table) {
$table->dropPrimary(['image_id', 'gallery_id']);
$table->increments('id');
});
It first gives me an error 1068 Multiple primary key defined, which tells me that the first line in the down method does not work as intended, but when I just run the dropPrimary line, it gives me an error errno: 150 - Foreign key constraint is incorrectly formed.
I am not quite sure as to what I am doing incorrectly.
You could try the following
$table->dropPrimary(); // without the parameters.
Or you could wrap the closure in the down method as following:
DB::statement('SET FOREIGN_KEY_CHECKS=0');
$table->dropPrimary(['image_id', 'gallery_id']);
$table->increments('id');
DB::statement('SET FOREIGN_KEY_CHECKS=1');
or
Schema::disableForeignKeyConstraints();
//code
Schema::enableForeignKeyConstraints();
This will temporarily set the FK Checks off, and turn it back on afterwards.
EDIT
You could look into https://laravel.com/docs/6.0/migrations#dropping-indexes and try out the various functions!

Laravel nullable migration with foreign key

I have items table with supplier_id column and the foreign key to that column. The column is not nullable and I want to make it nullable. So the up() method works:
$table->integer('supplier_id')->unsigned()->nullable()->change();
But I can't get down() method to work, always get the error:
Cannot change column 'supplier_id': used in a foreign key constraint 'items_supplier_id_foreign'
Latest attempt:
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('items', function (Blueprint $table) {
$table->dropForeign(['supplier_id']);
$table->integer('supplier_id')->unsigned()->nullable(false)->change();
$table->foreign('supplier_id')->references('id')->on('suppliers');
});
}
Any suggestions? I'm using Laravel 5.4
#apokryfos is right. See here
Before any changes are made to your table, it’s important to briefly go over what data can (and cannot) be specified within an existing column that you wish to alter to NOT NULL, ensuring that no row is allowed to have a NULL value in that column.
At first, you need fill all nullable values using seeds and then modify column. Or your can drop supplier_id and than using default() method fill all rows in the table.
Only dropping foreign key constraint is not enough if you're changing the column next, you have to also drop its index. Below is the migration down function
Schema::table('users', function (Blueprint $table) {
$table->dropForeign('users_role_id_foreign');
$table->dropIndex('users_role_id_foreign');
});
// Conflict with change on same Blueprint instance (strange)
Schema::table('users', function (Blueprint $table) {
$table->integer('role_id')->change();
});
Credits: Github

Laravel 5 migration - foreign key constraint fails

I'm having problems with running my migration. I have a mysql database with some tables. The specific table is product_blender. Some fields in the table are like this:
id (PK)
area_id (FK)
inhabitants (varchar)
heating_type_id (FK)
...
Now I would like to create another table called installateur_types. The table needs to contain a PK and a varchar field. I would also like to create a FK in product_blender table to the id of my newly created tabel.
This is what I've done:
Created migration to create a table:
public function up()
{
Schema::create('installateur_types', function(Blueprint $table)
{
$table->increments('id');
$table->string('type');
});
}
public function down()
{
Schema::drop('installateur_types');
}
Run the migration, this was successful. Table was created with correct fields.
Then I've created the migration to add a FK field to the product_blender table.
public function up()
{
Schema::table('product_blenders', function ($table) {
$table->integer('installateurtype_id')->unsigned();
$table->foreign('installateurtype_id')->references('id')->on('installateur_types')->onDelete('cascade');
});
}
public function down()
{
//
}
When I now run the migration I get the following error:
What am I doing wrong?
If your products_blender table is not empty, then when you add a new column which is not null (which is the default for eloquent), it will be assuming some default value on its own. This value may not be available in the table this new column is referring to, causing the foreign key constraint to fail.
One of the way to get around this is to give a default value to the new column or just make it nullable.
$table->integer('installateurtype_id')->unsigned()->nullable();
$table->foreign('installateurtype_id')->references('id')->on('installateur_types')->onDelete('cascade');
There is one other solution, which turns off this checks, which can be done using DB::statement('SET FOREIGN_KEY_CHECKS=0;'). Then again turn that one for future with DB::statement('SET FOREIGN_KEY_CHECKS=1;'). In you code you can do something like
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
$table->integer('installateurtype_id')->unsigned();
$table->foreign('installateurtype_id')->references('id')->on('installateur_types')->onDelete('cascade');
DB::statement('SET FOREIGN_KEY_CHECKS=1;');