I have two float fields: fieldA and fieldB. I would like to store in DB float field fieldC, which will always have value: fieldA * fieldB.
I would like to achieve that with migrations. This is what I tried
$table->float('fieldC', 12, 2)->storedAs('fieldA * fieldB');
and
$table->float('fieldC', 12, 2)->storedAs('MULTIPLY(fieldA, fieldB)');
Both didn't work. Errors are Column not found: 1054 Unknown column 'fieldA' and General error: 1901 Function or expression 'MULTIPLY()' cannot be used.
Any suggestions?
Laravel migrations dosn't support that. But you can make trigger with raw statement.
Something like that:
DB::unprepared("
DELIMITER $$
CREATE TRIGGER after_update
AFTER UPDATE
ON tableName FOR EACH ROW
BEGIN
IF old.fieldA <> new.fieldA OR old.fieldB <> new.fieldB THEN
UPDATE tableName SET fieldC=fieldA+fieldB WHERE id=new.id;
END IF;
END$$
DELIMITER ;
");
You can make it more simple using Laravel model
<?php
class YourModel extends Model {
public static function boot()
{
parent::boot();
self::saving(function($model){
$model->fieldC = $model->fieldA + $model->fieldB;
});
}
}
Related
I am trying to change datatype of one of my field from float to string but i am facing an issue
SQLSTATE[42000]: Syntax error or access violation: 1064 You have
an error in your SQL syntax; check the manual that
corresponds to your MariaDB server version for the right syntax to use
near '' at line 1 (SQL: ALTER TABLE patient
MODIFY height varchar)
my migration:
public function up()
{
DB::statement('ALTER TABLE patient MODIFY height varchar');
}
How i can achieve my target:
Your help need here
public function up()
{
Schema::table('patient', function (Blueprint $table) {
$table->string('height')->change();
});
}
You can use laravel's change method :
public function up()
{
Schema::table('patient', function ($table) {
$table->string('height')->change();
});
}
Also your SQL syntax should be :
ALTER TABLE patient MODIFY height varchar(255);
Update :
As per the laravel's documentation :
Only the following column types can be "changed": bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer, json, longText, mediumText, smallInteger, string, text, time, unsignedBigInteger, unsignedInteger and unsignedSmallInteger.
So Laravel's change will not work directly as you have a float column which laravel internally makes as double(8,2). Please update your raw SQL syntax using what I have given and try again.
In an application I have a case of the Class Table Inheritance. The discriminator column is an ENUM:
/**
* Foo
*
* #ORM\Table(name="foos", ...)
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="`type`", type="string", columnDefinition="ENUM('bar', 'buz')")
* #ORM\DiscriminatorMap({
* "bar" = "Bar",
* "buz" = "Buz"
* })
*/
abstract class Foo
{
...
}
Doctrine works as expected (to begin with). The doctrine:migrations:diff command creates a migration for the tables and relationships and also defines the discriminator column correctly, as an ENUM.
Then I execute the migrations (doctrine:migrations:migrate). The schema looks well. But:
When I execute the diff command again (and expect no new migrations), I get a new migration generated:
final class Version20180619205625 extends AbstractMigration
{
public function up(Schema $schema) : void
{
$this->addSql('ALTER TABLE foos CHANGE type `type` ENUM(\'bar\', \'buz\')');
}
public function down(Schema $schema) : void
{
$this->addSql('ALTER TABLE tasks CHANGE `type` type VARCHAR(255) DEFAULT NULL COLLATE utf8mb4_unicode_ci');
}
}
Alright, I execute it. And try the diff command again. And get the same migration generated again... So, Doctrine seems to "think", the column is still VARCHAR.
I showed the issue here on example of an inheritance discriminator. But actually it doesn't matter, if the column is a discriminator or not -- this behavior is the same for every ENUM column.
How to solve this issue? Is there a way make Doctrine handle ENUM columns correctly?
I am using the following method to get the column names of the database tables of my Laravel (v5.6.24) project. I am using mysql and it was working fine as expected. But from this week the columns names are showing as ordered by name. Previously it was showing names as in order as the actual table.
How can i get the column names in same order as the table?
/*
* Get Table Column Names
*/
public function getTableColumns()
{
return $this->getConnection()->getSchemaBuilder()->getColumnListing($this->getTable());
}
Might help someone: this is a slightly modified version of #Jonas Staudenmeir answer - but tweaked to be reusable. Just feed it the table name and it will spit out an array of the table fields.
private function getTableColumns($table_name)
{
return DB::select(
(new \Illuminate\Database\Schema\Grammars\MySqlGrammar)->compileColumnListing()
.' order by ordinal_position',
[env('DB_DATABASE'), $table_name]
);
}
You'll have to order the columns by ordinal_position:
public function getTableColumns()
{
return $this->getConnection()->select(
(new \Illuminate\Database\Schema\Grammars\MySqlGrammar)->compileColumnListing()
.' order by ordinal_position',
[$this->getConnection()->getDatabaseName(), $this->getTable()]
);
}
What is the best practice to store calculated fields in database.
For example, lets say a table has fields height, weight, bmi
A user enters height weight values and bmi field is automatically filled. How to achieve this with a form.
Formula for bmi
$bmi = weight / (height * height)
Tried the following
Profile Model
protected $table = 'profiles';
protected $fillable = ['user_id', 'weight', 'height', 'dob', 'age', 'bmi'];
public function user(){
return $this->belongsTo(User::class, 'user_id');
}
protected static function boot() {
parent::boot();
static::saving(function($model){
$model->bmi = $model->weight / ($model->height * $model->height);
$model->age = (date('Y') - date('Y',strtotime($model->dob)));
});
}
Profile Controller
public function store(Request $request)
{
$profile = new Profile();
$profile->weight = $request->get('weight');
$profile->height = $request->get('height');
$profile->dob = $request->get('dob');
$profile->age;
$profile->bmi;
$profile->save();
return back()->with('success', 'Your profile has been updated.');
}
But im receiving an error
Illuminate \ Database \ QueryException (42S22)
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'weight' in 'field list' (SQL: insert into `users` (`weight`, `height`, `dob`, `bmi`, `age`, `created_by`, `updated_by`, `updated_at`, `created_at`) values (60, 175, 1988-04-03, 0.0019591836734694, 30, 2, 2, 2018-03-08 20:06:02, 2018-03-08 20:06:02))
You could do this in the boot method of the model:
protected static function boot() {
parent::boot();
static::saving(function($model){
$model->bmi = $model->weight / ($model->height * $model->height);
});
}
What is the best practice to store calculated fields in database.
In general, don't. It's redundant - your database already has all the information needed to compute it, so why store redundant data?
Instead, put an accessor on the model:
public function getBmiAttribute() {
return ($this->weight / ($this->height * $this->height));
}
You can then do $this->bmi to get the computed value.
If you must have it in the database, use an Eloquent event. You'd probably want to hook into the saving event.
As of Laravel 5.3 & MySQL 5.7, you can use Column Modifiers virtualAs and storedAs to create a VIRTUAL (evaluated when rows are read) or STORED (evaluated and stored when rows are inserted or updated) generated column for MySQL.
I've made use of virtualAs in the example below...
Schema::create('results', function (Blueprint $table) {
$table->bigIncrements('id');
...
$table->time('predicted_time')->nullable()->default(NULL);
$table->time('handicap')->nullable()->default(NULL);
$table->time('finish_time')->nullable()->default(NULL);
$table->time('actual_time')->virtualAs('TIMEDIFF(finish_time, handicap)');
$table->time('offset')->virtualAs('TIMEDIFF(actual_time, predicted_time)');
...
});
What is the best practice to store calculated fields in database.
It depends on your use case. If you're using a relational database, and your use case does not involve big data (in terms of volume, variety or velocity), the best practice is to not store calculated fields and calculate them on the fly.
If you're using a noSQL database (such as MongoDB, which is supported by Laravel) or Hadoop, the best practice is to store calculated fields to avoid computational time.
In A Nutshell
It's a tradeoff between time complexity and space/storage complexity.
For big data / noSQL systems, store calculated fields especially if
they are computationally complex. For a relational database, calculate
them on the fly and keep your data non-redundant
Laravel Solution for RDBMS:
Use accessors like so:
public function getBmiAttribute($value)
{
return ($this->weight / ($this->height * $this->height));
}
Laravel Solution for NoSql
Use model events like so:
protected static function boot() {
parent::boot();
static::saving(function($model){
$model->bmi = $model->weight / ($model->height * $model->height);
});
}
If your DBMS supports computed columns (aka generated columns), you might consider utilizing them.
Highlights:
Computed columns don't persist the data to the database (or if they do technically they'll be managed by the DBMS), so you're not duplicating any data or making anything redundant
The calculation is then available outside of the application's code base for anything to use. For example, if there is a need to develop reporting with raw SQL queries then the calculation will be available there.
Eloquent (Laravel's default ORM) will pick up the column without you necessarily needing to define it anywhere.
You could execute code to create it during a migration. For example, I had a use case where I wanted to simplify determining if something was currently published and I did it like this:
Schema::table('article', function (Blueprint $table) {
$table->dateTime('published_at')->nullable();
$table->dateTime('unpublished_at')->nullable();
});
$sql = "ALTER TABLE article ADD is_published AS CAST(CASE WHEN GETUTCDATE() BETWEEN ISNULL(published_at, '2050-01-01') AND ISNULL(unpublished_at, '2050-01-01') THEN 1 ELSE 0 END AS BIT);";
DB::connection()->getPdo()->exec($sql);
Then after retrieving a record from the database I can determine if it's published by simply checking...
if ($article->is_published) ...
And if I want to query it from the database I can easily do...
SELECT * FROM article WHERE is_published = 1
Note, this syntax is for T-SQL since the RDBMS for my project is MS SQL Server. However, other popular DBMS have similar features.
MS SQL Server: Computed Columns
ALTER TABLE dbo.Products ADD RetailValue AS (QtyAvailable * UnitPrice * 1.35);
MySQL: Generated Columns
ALTER TABLE t1 ADD COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED;
Oracle: Virtual Columns
ALTER TABLE emp2 ADD (income AS (salary + (salary*commission_pct)));
PostgreSQL doesn't seem to support it (yet), but this SO answer provides a decent workaround.
another way to make this
making a computed column in the migration
this working with all DB:-
$table->computed ('tax','price * 0.27');
but this working with MySQL DB:-
$table->integer('tax')->virtualAs('price * 0.27')->nullable();
$table->integer('discount')->storedAs('price - 100')->nullable();
I wish to increase decimal precision and scale for a decimal column.
I am aware that I can drop the column, and re-create it, but doing so will mean losing the data in the column.
Is there a way using Laravel Schema::table that I can alter the precision and scale of the column without dropping it?
e.g. something like:
Schema::table('prices', function(Blueprint $t) {
$t->buy_price->decimal(5,2);
});
this worked for me:
public function up()
{
Schema::table('prices', function(Blueprint $t) {
$t->decimal('buy_price', 5, 2)->change();
});
}
when rolling back use original precision values of 3, 1 instead
public function down()
{
Schema::table('prices', function(Blueprint $t) {
$t->decimal('buy_price', 3, 1)->change();
});
}
I avoid DB specific "raw" statements as they might fail when I change to another DBMS engine. So I let Laravel handle necessary syntax when working w/DB
Just create another migration and in the up method add following code:
public function up()
{
// Change db_name and table_name
DB::select(DB::raw('ALTER TABLE `db_name`.`table_name` CHANGE COLUMN `buy_price` `buy_price` decimal(10,2) NOT NULL;'));
}
Also in the down method just set the old value so you can roll-back:
public function down()
{
// Change db_name and table_name
DB::select(DB::raw('ALTER TABLE `db_name`.`table_name` CHANGE COLUMN `buy_price` `buy_price` decimal(5,2) NOT NULL;'));
}
Then migrate as usual from the terminal/command prompt using php artisan migrate.
Didn't work for me, using select gives me a General Error: 2053 because select expects aa array to be returned. I'm not sure if it's the version of MySQL, or a windows/linux thing.
I had to use DB:statement instead:
public function up()
{
// Change db_name and table_name
DB::statement(DB::raw('ALTER TABLE `db_name`.`table_name` CHANGE COLUMN `buy_price` `buy_price` decimal(10,2) NOT NULL;'));
}
and
public function down()
{
// Change db_name and table_name
DB::statement(DB::raw('ALTER TABLE `db_name`.`table_name` CHANGE COLUMN `buy_price` `buy_price` decimal(5,2) NOT NULL;'));
}
Hope this helps anyone who comes across this.