How to make combination of two columns as a unique key? - laravel

I have this table :
public function up()
{
Schema::create('edition', function (Blueprint $table) {
$table->increments('id');
$table->integer('volume');
$table->text('cover')->nullable();
$table->enum('number', ['1', '2']);
$table->timestamps();
});
Schema::table('journal', function(Blueprint $table) {
$table->foreign('id_edition')->references('id')->on('edition')->onDelete('cascade')->onUpdate('cascade');
});
}
I wanted to make the combination of column Volume and Number as a unique key. For example, there is a data "Volume 1, Number 1" and when user insert the same combination it will throw an error message that the data already exist. Is there a way I can do this in Laravel ?

Just include the following snippet inside the up() method
$table->unique(['volume','number']);
By having this constraint set, if you insert 'Volume 1 , Number 1` once it'll be fine. However, if you try to do the same again, it'll throw an error.
Conditions:
You are using some good DBMS, such as MySQL and not SQLITE
You successfully migrated this change into the database.

You can also use uniquewith-validator package that allows you to validate a unique combination of fields like this
'field1' => 'required|unique_with:table_name,field2'
The above code will check if the combination of field1 and field2 is unique in the table with the given table_name.

Related

update content of stored column in laravel controller

I have a table with 3 columns:
firstname
lastname
fullname
in migration:
Schema::create('owners', function (Blueprint $table) {
$table->id();
$table->string('firstname',20);
$table->string('lastname', 20);
$table->string('fullname')->storedAs('CONCAT(firstname,lastname)');
$table->timestamps();
});
the problem is that i want to change the concatenation order in the controller i tried to use db statement but it doesn't work
-in the controller:
$owners= Owner::findOrFail($id);
$owners->update([
'firstname'=>$request['firstname'],
'lastname' =>$request['lastname'],
]);
DB::statement('UPDATE owners SET fullname AS CONCAT(lastname,firstname) STORED WHERE ID=1 ');
I don't want to just use a simple concatenation because the user can change the firstname or the lastname and the order that's why I used storedAs()
any ideas please?
The storedAs method in the migration creates a generated column in mysql. The value is automatically generated from the column values of the firstname and the lastname. There's no way you can change this via an UPDATE statement. You'd have to use an ALTER TABLE statement, which would be horrifically bad practice.
If I were you, I'd keep full name display as a model method so you could access it by using $owner->fullNameFirstLast() or $owner->fullNameLastFirst()
What you should do is create a new migration in order to change the column, the code would be something like this:
Schema::table('owners', function (Blueprint $table) {
$table->string('fullname')->storedAs('CONCAT(lastname,firstname)');
});
This way the column will be changed on a database level, and no need for the controller query you have added
Simply try this
1- update your migration to
Schema::create('owners', function (Blueprint $table) {
$table->id();
$table->string('firstname',20);
$table->string('lastname', 20);
$table->string('fullname', 56);
$table->timestamps();
});
2- in your controller
$owners= Owner::findOrFail($id);
$first_name = $request->firstname ?? $owners->firstname;
$last_name = $request->lastname ?? $owners->lastname;
$full_name = $first_name.' '.$last_name;
$owners->update([
'firstname'=>$first_name,
'lastname' =>$last_name,
'fullname' =>$full_name,
]);
You can also write it this way
DB::statement(DB::raw("UPDATE owners SET firstname = '".$first_name."', lastname = '".$last_name."', fullname = '".$full_name."' WHERE id = $id"));
And the same way for your Create function as well

Laravel query where column value is greater than sum of related model column values

I have two tables transactions and transaction_allocations. One transaction can have many allocations.
Transaction Model
Schema::create('transactions', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->foreignId('contact_id')->constrained();
$table->decimal('amount',19,4)->nullable();
});
Allocation Model
Schema::create('transaction_allocations', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamp('created_at');
$table->foreignId('transaction_id')->nullable()->constrained();
$table->foreignId('bill_id')->nullable()->references('id')->on('bills');
$table->decimal('amount',19,4)->nullable();
});
Relationship in Transaction Model
public function allocations(){
return $this->hasMany(TransactionAllocation::class);
}
I need to query all transactions where the transaction amount is greater than the sum of all allocated amounts for that transaction. (Basically finding transaction which has unallocated balances).
Transaction::where('contact_id',$id)->where('amount','>',sum of allocations)->get();
How do I achieve it?
I was able to create an accessor to do this calculation and find an unallocated amount. But seems like accessors cannot be used in where. I don't want to load all transactions and then filter it as it will be too much.
I want to query and get filtered lines directly. How do I do it?
I think Having Raw is better to compare two column.
Transaction::where('contact_id',$id)
->with('allocations')
->whereHas("allocations",function ($query){
$query->havingRaw('transactions.credit>sum(amount)');
$query->groupBy('transaction_id');
})->orDoesntHave("allocations")
->get();
if query fails then try to 'strict' => false in database.php for mysql connection

Instert data in two tables with relation Laravel

I want to insert with a form into two tables that are related with a foreign key,
First you need to create an order with customername, and then be forwarded to next form to insert multiple articles.
This is how my tables are made:
Schema::create('returnorders', function (Blueprint $table) {
$table->increments('id');
$table->string('firmaname');
$table->string('contacperson');
$table->string('email')->unique();
$table->string('ordernumber');
$table->string('customername');
$table->timestamps();
});
Schema::create('returnarticles', function (Blueprint $table) {
$table->increments('id');
$table->integer('order_id')->unsigned();
$table->foreign('order_id')->references('id')->on('returnorders');
$table->string('articlenumber');
$table->string('return_quantity');
$table->string('return_quality');
$table->string('return_reason');
$table->string('images');
});
My question is now, can i make one form where i can put multiple articles or should I make two forms, and that you will be forwarded from the first one to the next one?
My second question is, can I do this in one function in my controller or should i make two functions?
Help me out please, and if it's possible show me how to make my function.
Thanks
If your question is how to save these related models, then you should look at the documentation on Inserting and Updating related models:
Documentation
Your code could look something like this:
$returnOrder = ReturnOrder::create([
'firmaname' => $validatedInput['firmaname'],
'contactperson' => $validatedInput['contactperson'],
....
]);
$returnOrder->returnArticles()->save(ReturnArticle::create([
'articlenumber' => $validatedInput['articleNumber'],
...
]));

Eloquent using wrong key for some tables

Using: Laravel 5.5
I Constructing addresses using some address elements (like: district, area, zip etc) as dropdowns & also some user inputs.
I have 5 address element & one of their schema is:
Schema::create('address_districts', function (Blueprint $table) {
$table->increments('id');
$table->integer('admin_id')->unsigned();
$table->string('name');
$table->timestamps();
$table->foreign('admin_id')->references('id')->on('admins');
});
This is for Districts, & I have another 3 exactly same like this called, address_thanas, address_areas, address_building_names & address_zips;
The only exception for the last one is that has code instead of name on other tables:
Schema::create('address_zips', function (Blueprint $table) {
$table->increments('id');
$table->integer('admin_id')->unsigned();
$table->string('code'); // Look other table has name here........
$table->timestamps();
$table->foreign('admin_id')->references('id')->on('admins');
});
I store constructed addresses on the table called addresses
Schema::create('addresses', function (Blueprint $table) {
$table->increments('id');
$table->integer('district_id')->unsigned();
$table->integer('thana_id')->unsigned();
$table->integer('area_id')->unsigned();
$table->integer('zip_id')->unsigned();
$table->integer('building_name_id')->nullable()->unsigned();
$table->string('building');
$table->integer('floor');
$table->string('apt')->nullable();
$table->text('comment')->nullable();
$table->timestamps();
$table->foreign('district_id')->references('id')->on('address_districts');
$table->foreign('thana_id')->references('id')->on('address_thanas');
$table->foreign('area_id')->references('id')->on('address_areas');
$table->foreign('zip_id')->references('id')->on('address_zips');
$table->foreign('building_name_id')->references('id')->on('address_building_names');
});
In Address Model I've defined relationships like:
public function district() {
return $this->belongsTo(AddressDistrict::class, 'district_id');
}
public function thana() {
return $this->belongsTo(AddressThana::class, 'thana_id');
}
public function area() {
return $this->belongsTo(AddressArea::class, 'area_id');
}
public function building_name() {
return $this->belongsTo(AddressBuildingName::class, 'building_name_id');
}
public function zip() {
return $this->belongsTo(AddressZip::class, 'zip_id', 'id');
}
Then when I try to create a new address using Address::create($data)
I get error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'name' in 'where clause' (SQL: select count(*) as aggregate from `address_zips` where `name` = 2)
Here we can see that it is comparing the key name instead of id
I noticed that this is not reporting that the data cannot be inserted or something like that, it fails to count related model & for that aborts insertion of data
Why is that?
But the strange thing is I can retrieve data by (inserted a row manually into db for testing to see if that can retrieve data)
$addresses = Address::orderByDesc('created_at')->get();
//loop as $address
$address->district->name
$address->zip->code
...
& this works perfect
When I am creating a new record I need that query look like:
select count(*) as aggregate from `address_zips` where `id` = 2
Any help will be highly appreciated.
Thanks for reading this long question.
The problem is in your $data array. Probably you copied a form with an input name, you need to rename it to code in order to save the values automatically.
Alternatively, you can set the input manually
$address = new Address();
$address->code = request('name');
$address->save();

Laravel integer v biginteger in foreign keys

I'm trying to assign big integers in my columns but it won't allow me to define the relationship unless I change it to standard integer. No biggy, but would be interested to know why this throws up migration errors for me.
MIGRATION (working)
public function up()
{
//create the table
Schema::create(self::TBL_eportfoliouservalues, function($table){
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('user')->unsigned()->index();
$table->integer('document')->unsigned()->index();
$table->integer('section')->unsigned()->index();
$table->integer('element')->unsigned()->index();
$table->integer('parent');
$table->text('value');
// foreign indexes
$table->foreign('user')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('document')->references('id')->on('eportfoliodocuments')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('section')->references('id')->on('eportfoliosections')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('element')->references('id')->on('eportfolioformelements')->onDelete('cascade')->onUpdate('cascade');
});
}
MIGRATION (not working)
public function up()
{
//create the table
Schema::create(self::TBL_eportfoliouservalues, function($table){
$table->engine = 'InnoDB';
$table->increments('id');
$table->bigInteger('user')->unsigned()->index();
$table->bigInteger('document')->unsigned()->index();
$table->bigInteger('section')->unsigned()->index();
$table->bigInteger('element')->unsigned()->index();
$table->bigInteger('parent');
$table->text('value');
// foreign indexes
$table->foreign('user')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('document')->references('id')->on('eportfoliodocuments')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('section')->references('id')->on('eportfoliosections')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('element')->references('id')->on('eportfolioformelements')->onDelete('cascade')->onUpdate('cascade');
});
}
ERROR
[Exception]
SQLSTATE[HY000]: General error: 1005 Can't create table 'drillers.#sql-1a4_2c2' (errno: 150) (SQL:
alter table `eportfoliouservalues` add constraint eportfoliouservalues_user_foreign foreign key (
`user`) references `users` (`id`) on delete cascade on update cascade) (Bindings: array (
))
How are the IDs defined on the other tables? If you use $table->increments('id') then that will create columns of the type integer, and as the foreign key field has to match the primary key field on the other table, you won't be able to create a relationship.
The way to get around this would be to use $table->bigIncrements('id') on the other tables.
If primary key in tableOne migration (by default it is ) is BigInteger
So the foreign key in tableTwo migration should be bigInteger with unsigned() function.
Codes for example.
Migration for tableOne
public function up()
{
Schema::create('tableOne', function (Blueprint $table) {
$table->bigIncrements('id');
}
Migration for tableTwo
public function up()
{
Schema::create('tableTwo', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('fk_id')->unsigned()->nullable() ;
$table->bigInteger('fk_id')->default(20)->change();
$table->foreign('fk_id')->references('id')->on('tableOne');
}
Hope this helps someone.
This is late to the party but I thought should be shared somewhere.
This was a perfectly valid upgrade that was not so well documented. The type needs to match for ids and foreign keys.
This script outputs the migration you need.
Use at your own risk having BACKED UP YOUR DATABASE. Make a migration and add the following to up(). Leave down() empty so you can rollback and tweak as needed. It will not actually do anything but will output what needs to be done. You can then paste the output into your migration file once you are happy with what you see.
If you uncomment the echos you can see what was looked at and determined needs changing.
$tableName = $table->Tables_in_yourdatabasename; //eg Tables_in_myproject
Modify the above line in the code below
$tables = \DB::select('SHOW TABLES');
// this just to get the tabs right in sublime;
$i = 0;
foreach($tables as $table) {
// ### Change yourdatabasename below ###
$tableName = $table->Tables_in_yourdatabasename;
// echo "#######################\n";
// echo '########## '.$tableName."##########\n\n";
$columns = \DB::select('show columns from ' . $tableName);
foreach ($columns as $value) {
if((substr($value->Field, -2) === 'id') && (substr($value->Type,0,3) === "int" || substr($value->Type,0,6) === "bigint")) {
// echo "$value->Field : type is '$value->Type'\n" ;
if(substr($value->Type,0,6) === "bigint") {
// echo "No Change needed\n";
}
if(substr($value->Type,0,3) === "int") {
// echo "To Update $value->Field to 'BIGINT':\n\n";
if($i === 0) {
echo 'Schema::table(\''.$tableName.'\', function (Blueprint $table) {'."\n";
$i++; //only matters for the first row. For the tabs.
}else{
echo "\t\t".'Schema::table(\''.$tableName.'\', function (Blueprint $table) {'."\n";
}
// maintain the auto increment
if($value->Field === 'id') {
echo "\t\t".' $table->bigIncrements(\'id\')->change();'."\n\t\t";
}else{
echo "\t\t".' $table->bigInteger(\''.$value->Field.'\')->unsigned()->change();'."\n\t\t";
}
echo '});'."\n";
}
}
}
// echo "\n\n";
}
Be careful and examine the output carefully as you may not want to change all the tables. I would skip the migrations table for example as I doubt you will reference it.
In case I didn't say it. BACK UP YOUR DATABASE FIRST. Good Luck

Resources