I need to make a composite constraint in Laravel, which only consider non soft-deleted rows. So if the entity was deleted, the deleted_at is not null and data_1, data_2, data_3, data_4, data_5 can be inserted again.
Best I could come up with is this (table and column names were defined in previous migrations):
public function up()
{
Schema::table('table_name', function (Blueprint $table) {
$table->unique(['data_1', 'data_2', 'data_3', 'data_4', 'data_5'])
->whereNull('deleted_at');
});
}
But it doesn't work. The constraint applies even if deleted_at is not null.
I've been looking through other post with similar issues but their solutions have different approaches non related with a migration.
Turns out that making a constraint wasn't the correct approach for the problem.
A unique index allows to make a composition of columns unique, and also admit conditions.
In raw sql statement it is:
public function up()
{
DB::statement('CREATE UNIQUE INDEX table_unique_preferredNameOfIndex ON schema.table
USING btree (data_1, data_2, data_3, data_4, data_5) WHERE (deleted_at IS NULL)');
}
Not strictly the same but it gets the job done.
If a rollback is needed, just drop the index:
public function down()
{
DB::statement('DROP INDEX schema.table_unique_preferredNameOfIndex');
}
Related
My laravel migration is like below
public function up()
{
Schema::create('account_main', function (Blueprint $table) {
$table->increments('user_sn')->primary();
$table->string('member_username', 20);
$table->string('login_password', 255);
$table->integer('login_count')->default('0')->unsigned();
});
}
When I ran "php artisan migrate", show error "1068 Multiple primary key".
Could someone help to find the problem.
You don't need the ->primary() because already ->increments('...') includes it.
It's like if in MySQL you write this:
PK INT AUTO_INCREMENT PRIMARY KEY;
PRIMARY KEY(PK)
You are declaring two times the same primary key
In Laravel Eloquent ORM the type increments will be defined as primary key automatically. So don't need to use primary() method.
If the column is integer.
$table->increments('user_sn');
If the column is string
$table->string('user_sn')->primary();
If you want any other column to be unique (instead of primary key)
$table->increments('user_sn');
$table->string('member_username', 20)->unique(); // cannot contain duplicate values
An example for this problem:
Imagine you have a table projects. The table projects already
contains data. Now you want to add a project_type_id column to the
table, that can't be NULL and has a foreign key to the project_types
table.
The database sees the project_type_id can't be NULL, so it inserts a
0 in the column for all the existing rows. The 0 does not exist in
the project_types table, so the foreign key constraint fails. This
means you can't add the foreign key.
I thought about adding a first "default" row to the project_types table and link all the projects to that type. This isn't a good solution, because the user has to change all the types from "default" to the correct type.
What is your solution for this problem?
I think the best way to solve this issue is:
Create a new migration in order to add project_type_id column to projects table:
php artisan make:migration add_project_type_column_to_projects_table --table=projects
public function up()
{
Schema::table('projects', function (Blueprint $table) {
$table->integer("project_type_id")->unsigned();
});
}
public function down()
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn("project_type_id");
});
}
It's going to add 0 value as default in each column but no problem because it's not foreign key yet.
2.Define the relationship between Project and ProjectType models
//Your Project class
class Project extends Model
{
protected $fillable=["your columns..."];
public function type()
{
return $this->belongsTo(ProjectType::class);
}
}
//Your ProjectType class
class ProjectType extends Model
{
protected $fillable=["Your columns..."];
public function projects()
{
return $this->hasMany(Project::class);
}
}
Now you must go and set the correct values for project_type_id either manually or from your admin panel.
Now your column is ready to be set as foreign key, so go ahead and make a new migration to set the project_type_id as foreign key.
php artisan make:migration set_project_type_id_column_as_foreign_key --table=projects
public function up()
{
Schema::table('projects', function (Blueprint $table) {
$table->foreign("project_type_id")
->references("id")
->on("project_types")
->onDelete("cascade")//is up to you
->onUpdate("cascade");// is up to you
});
}
public function down()
{
Schema::table('projects', function (Blueprint $table) {
$table->dropForeign("projects_project_type_id_foreign"); //This one could be different
});
}
Now your table has a new (NOT NULLABLE) foreign key with correct values.
You should have a valid foreign key that exists in DB, for example: 1
Then Create a new migration :
$table->integer('project_type_id')->unsigned()->default(1);
$table->foreignkey('project_type_id')->references('id')->on('project_types');
Then user has to manually change every one of them or change them randomly (random valid ids) with foreach.
I think one workaround here would be to first add the new column without a foreign key reference. Then update the values of that column, and afterwards add the foreign key constraint. Something like this:
ALTER TABLE projects ADD COLUMN project_type_id INT;
-- now update with correct values
ALTER TABLE projects ADD CONSTRAINT fk_pt_id FOREIGN KEY (project_type_id)
REFERENCES yourTable(parent_id);
If you have to deploy to different environments, you could add a default value to the project_type_id (as #Ahmad Mobaraki suggested). Of course, you must have that value in the project_type table.
Then, create an Artisan command that fetches all rows and that updates the project_type_id to the right value, according to your business logic.
Finally, after running the migration, run that Artisan custom command.
At this point, you have a non-nullable foreign key with the right values.
I had the same issue (yes, I know, 2019) and solved that way. After deployment in other environments, just run the update script.
I use to create Artisan commands that are run manually after the automated deployment. Most of the time, automated deployments are just fine, but when there are some tasks like this, the best solution I have found is to have a custom Artisan command that handles that extra work.
Other solution I have explored in the past is to handle that updating in the same migration file, but that mixes things up. Database structure and data, and, on top of that, it assumes the database has data. With this approach, even when it is not fully automated, you have a clear separation between structure and data.
I have the following migration to create my MySQL database table
public function up()
{
Schema::create('project_auth_keys', function (Blueprint $table) {
$table->increments('id');
$table->integer('project_id')->unsigned();
$table->string('key', 800);
$table->string('first_name', 80);
$table->string('last_name', 80);
$table->timestamps();
// link project id with real project id
$table->foreign('project_id')->references('id')->on('projects');
});
}
Now what I want ot accomplish is make 'key' "unique"... However I know that I can do that by simply changing
$table->string('key', 800)->unique();
But I don't want to make it unique for the whole table I want to make it unique based on project_id.
For example a entry with project_id of 1 and a entry with project_id of 2 can have the same key, however there cannot be 2 same keys in the same project_id.
Is this something I can do within MySQL or will I have to make this as a rule within my controller? I can do it in my controller not a problem, but I would rather do it in my database if possible.
EDIT
Tried adding
$table->primary(array('project_id', 'key'));
Added it right below $table->primary(array('project_id', 'key'));
However after doing this I am getting a error when trying to run the migration.
Something like this?
$table->primary(array('project_id', 'key'));
You can find the same in the documentation here.
Solution 1: Since you already have a increment column defined, it is taking the same as a default primary key.
To use a composite primary key, you can either remove that column or calculate that column differently, otherwise it would throw you a duplicate primary key exception.
Also, the limitation of the length of primary key is "767" and hence you should reduce your key length to lower than that if possible.
I just tried the following and it works:
Schema::create('test', function(Blueprint $table)
{
$table->integer('project_id')->unsigned();
$table->string('key', 100); // make sure the key length is within sql standards(767), "800" is not acceptable for primary key
$table->string('first_name', 80);
$table->string('last_name', 80);
$table->timestamps();
// Add primary
$table->primary( array( 'key', 'project_id' ) );
});
Solution 2: You can just perform the validation in controller, that gives you more flexibility I believe and have the same structure, with just id as primary key. However, I am not very confident about performance. You will have to check on that.
Hope this helps.
I have to add a new column to an existing table that already has data in it and running into a bit of a hiccup.
How can I add this column? Its going to be a Not Nullable field. I'm ok with populating it with default data for right now and going back and updating later. so if we need to drop constraints while adding. I'm assuming I'm going to need to utilize straight SQL queries.
Make this work w/ PHPUnit and SQLite, currently I'm getting an error SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL column with default value NULL (SQL: alter table "tracks" add column "short_description" text not null)
How would I modify this migration?
public function up()
{
Schema::table('tracks', function(Blueprint $table)
{
$table->text('short_description')->after('description');
});
}
You have to set default value:
public function up()
{
Schema::table('tracks', function(Blueprint $table)
{
$table->text('short_description')->after('description')->default('default_value');
});
}
Why do I have a problem creating a table using Laravel Migrations Schema Builder?
The problem occurs with a table with a self-referencing foreign key.
Schema::create('cb_category', function($table)
{
$table->integer('id')->primary()->unique()->unsigned();
$table->integer('domain_id')->unsigned();
$table->foreign('domain_id')->references('id')->on('cb_domain');
$table->integer('parent_id')->nullable();
$table->foreign('parent_id')->references('id')->on('cb_category')->onUpdate('cascade')->onDelete('cascade');
$table->string('name');
$table->integer('level');
});
SQLSTATE[HY000]: General error: 1005 Can't create table 'eklik2.#sql-7d4_e' (errno: 150) (SQL: alter table `cb_category` add constraint cb_category_parent_id_foreign foreign key (`parent_id`) references `cb_category` (`id`) on delete cascade on update cascade) (Bindings: array ())
[PDOException]
SQLSTATE[HY000]: General error: 1005 Can't create table 'eklik2.#sql-7d4_e' (errno: 150)
You have to break this into two Schema blocks, one creating the columns, the other adding the FKs. mysql can't do both at the same time.
Two querys work :
Schema::create('cb_category', function($table)
{
$table->integer('id')->primary()->unique()->unsigned();
$table->integer('parent_id')->nullable();
});
Schema::table('cb_category', function (Blueprint $table)
{
$table->foreign('parent_id')->references('id')->on('cb_category')->onUpdate('cascade')->onDelete('cascade');
});
I may be too late for the party, but the official docs claim that the foreign key, in case of integer, must be ->unsigned();
http://laravel.com/docs/4.2/schema#foreign-keys
Note: When creating a foreign key that references an incrementing
integer, remember to always make the foreign key column unsigned.
Also, Artisan does not fail if you (as I have) misspell unsigned() and I have spent quite a few hours trying to figure out why the key was not created.
So two things:
1. Always make the foreign key column unsigned in case of incrementing integers
2. Check the spelling of unsigned()
Also a late response but probably a more idiomatic way for Laravel 8:
use App\Models\CbCategory;
...
Schema::create("cb_category", function(Blueprint $table)
{
$table->id();
$table->foreignIdFor(CbCategory::class, "parent_id")
->constrained()
->cascadeOnUpdate()
->cascadeOnDelete()
->nullable();
});
Please Note: I guessed the class name of CbCategory here. Using the class reference firsthand (instead of the former table name string) enables your static code checkers to pick up on future class name changes.
Also the _id-suffix at the parent_id column name is important.
May the following resources quench your thirst for knowledge:
id(): https://laravel.com/docs/8.x/migrations#column-method-id
foreignIdFor(): https://laravel.com/api/8.x/Illuminate/Database/Schema/Blueprint.html
cascadeOnDelete() & cascadeOnUpdate(): https://laravel.com/api/8.x/Illuminate/Database/Schema/ForeignKeyDefinition.html
Schema::create('cb_category', function (Blueprint $table) {
$table->increments('id')->unsigned();
$table->integer('domain_id')->unsigned();
$table->foreign('domain_id')->references('id')->on('cb_domain');
$table->integer('parent_id')->nullable();
$table->foreign('parent_id')->references('id')->on('cb_category')->onUpdate('cascade')->onDelete('cascade');
$table->string('name');
$table->integer('level');
});
Try this
I think you have another table that references the current table that you want to create.
I had this problem and remove that table and my problem was solved
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->integer('parent_id')->unsigned();
$table->foreign('parent_id')->on('categories')->references('id');
});
i got same error when i used this code, after change "$table->integer('parent_id')->unsigned()" to "$table->bigInteger('parent_id');"
my problem solved.
The point here is to make sure that the type of foreign key is the same as the primary key.
Since Laravel 8+ you don't have to break into two Schema blocks. You can use
foreignIdFor(CbCategory::class, 'cb_category_id') and it will create a column named cb_category_id
Ex.
Schema::create("cb_category", function(Blueprint $table)
{
$table->id();
$table->foreignIdFor(CbCategory::class, 'cb_category_id')->nullable()->constrained()->cascadeOnUpdate()->cascadeOnDelete();
});
Any additional column modifiers (Ex. nulleable) must be called before the constrained method.
You can use a second parameter in foreignIdFor for the referencing column name (in case it isn't 'id') NOT for the name you want it to have, in your case the name will automatically be 'cb_category_id'