数据库中的计算字段 - Laravel



在数据库中存储计算字段的最佳实践是什么?

例如,假设一个表具有字段高度、权重、bmi

用户输入身高体重值,bmi 字段会自动填充。如何使用表单实现此目的。

暴模指数公式 $bmi = 体重/(身高 * 身高)

已尝试以下操作 轮廓模型

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

配置文件控制器

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.');
}

但是我收到错误

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))

在数据库中存储计算字段的最佳做法是什么?

一般来说,不要。它是冗余的 - 您的数据库已经拥有计算它所需的所有信息,那么为什么要存储冗余数据呢?

相反,在模型上放置一个访问器:

public function getBmiAttribute() {
return ($this->weight / ($this->height * $this->height));
}

然后,可以执行$this->bmi来获取计算值。

如果必须在数据库中拥有它,请使用 Eloquent 事件。您可能希望挂钩saving事件。

您可以在模型的boot方法中执行此操作:

protected static function boot() {
parent::boot();
static::saving(function($model){
$model->bmi = $model->weight / ($model->height * $model->height);
}); 
}

从Laravel 5.3和MySQL 5.7开始,您可以使用列修饰符virtualAsstoredAs为MySQL创建VIRTUAL(读取行时评估)或STORED(在插入或更新行时评估和存储)生成的列。

我在下面的示例中使用了virtualAs...

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)');
...        
});

在数据库中存储计算字段的最佳做法是什么?

这取决于您的用例。如果您使用的是关系数据库,并且您的用例不涉及大数据(在数量、种类或速度方面),则最佳做法是不存储计算字段并动态计算它们。

如果您使用的是noSQL数据库(例如Laravel支持的MongoDB)或Hadoop,则最佳做法是存储计算字段以避免计算时间。

简而言之

这是时间复杂度和空间/存储复杂性之间的权衡。 对于大数据/noSQL系统,存储计算字段,特别是如果 它们在计算上很复杂。对于关系数据库,计算 它们即时运行,并保持您的数据非冗余

Laravel RDBMS解决方案:

使用如下访问器:

public function getBmiAttribute($value)
{
return ($this->weight / ($this->height * $this->height));
}

Laravel Solution for NoSql

使用如下所示的模型事件:

protected static function boot() {
parent::boot();
static::saving(function($model){
$model->bmi = $model->weight / ($model->height * $model->height);
}); 
}

如果您的 DBMS 支持计算列(也称为生成的列),则可以考虑使用它们。

突出:

  • 计算列不会将数据保存到数据库(或者,如果它们在技术上持久保存,它们将由 DBMS 管理),因此您不会复制任何数据或使任何内容变得多余
  • 然后,计算可在应用程序的代码库之外使用,以供任何使用。例如,如果需要使用原始 SQL 查询开发报告,则可以在那里进行计算。
  • Eloquent (Laravel的默认ORM)将拾取该列,而您不必在任何地方定义它。

您可以在迁移期间执行代码来创建它。例如,我有一个用例,我想简化确定当前是否发布了某些内容,我这样做了:

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

然后,从数据库中检索记录后,我可以通过简单地检查来确定它是否已发布......

if ($article->is_published) ...

如果我想从数据库中查询它,我可以很容易地做到......

SELECT * FROM article WHERE is_published = 1

请注意,此语法适用于T-SQL,因为我项目的RDBMS是MS SQL Server。但是,其他流行的DBMS具有类似的功能。

  • MS SQL Server:计算列

    更改表 DBO。产品增加零售价值AS(数量可用*单价* 1.35);

  • MySQL:生成的列

    更改表 T1 添加列 C2 INT 生成始终作为 (c1 + 1) 存储;

  • 甲骨文:虚拟列

    更改表 emp2 添加(收入为 (工资 + (薪水*commission_pct)));

PostgreSQL似乎(还)不支持它,但是这个SO答案提供了一个不错的解决方法。

另一种方法来实现这一点 在迁移中创建计算列 这适用于所有数据库:-

$table->computed ('tax','price * 0.27');

但这与 MySQL 数据库一起工作:-

$table->integer('tax')->virtualAs('price * 0.27')->nullable();
$table->integer('discount')->storedAs('price - 100')->nullable();

最新更新