数据库中的计算字段 - Laravel
Posted
技术标签:
【中文标题】数据库中的计算字段 - Laravel【英文标题】:Calculated fields in database - Laravel 【发布时间】:2018-03-08 17:52:54 【问题描述】:将计算字段存储在数据库中的最佳做法是什么。
例如,假设一个表格具有高度、重量、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))
【问题讨论】:
在将值插入表之前,您需要手动计算bmi并将其分配给$yourObject->bmi = $calculatedBmi
那么我可以使用相同的论坛吗la $calculateBmi = $request->weight / ($request->height * $request->height)
是的,您可以使用相同的 $request 对象值
【参考方案1】:
您可以在模型的boot
方法中执行此操作:
protected static function boot()
parent::boot();
static::saving(function($model)
$model->bmi = $model->weight / ($model->height * $model->height);
);
【讨论】:
我们为什么要在boot
中使用这个?
您可以不必在控制器中的多个位置处理计算,而只需在引导方法中执行一次。
@KPK 这是在 Eloquent 模型上设置观察者的方法之一。
@Ohgodwhy 我编辑为 static::saving
而不是 static::creating
,因为您希望 BMI 会在身高/体重时更新。
@SrikanthGopi 您可以使用static
,也可以使用模式的名称。由你决定。【参考方案2】:
将计算字段存储在数据库中的最佳做法是什么。
一般情况下,不要。它是冗余的 - 您的数据库已经拥有计算它所需的所有信息,那么为什么要存储冗余数据呢?
而是将an accessor 放在模型上:
public function getBmiAttribute()
return ($this->weight / ($this->height * $this->height));
然后您可以执行$this->bmi
来获取计算值。
如果您必须将其保存在数据库中,请使用an Eloquent event。您可能想要挂钩saving
事件。
【讨论】:
我目前正在通过模型执行相同的操作以在视图中显示 bmi 值。但我正在寻找的是在数据库中保存 bmi 值,以便我可以将用户的 bmi 值与日期进行比较。例如,显示用户,您的 bmi 水平上个月为 21.0,今天为 20.0。每周都会向用户显示一个表单。 @SrikanthGopi 请记住,您仍在为每个用户在数据库中保存 1 bmi 值。这不会帮助您比较月份值。无论您是否使用 Eloquent 模型事件保存值,您都需要有一个单独的日志表来执行此操作 @Paras 我在主要问题中提到的代码为用户保存了多个配置文件。所以我可以每周获取配置文件并将其与以前的配置文件进行比较。我是第一次这样做。 哦,所以 profile 表就像是 users 表在给定时间点的快照副本。那么您应该考虑以下几点:1)您是否要复制所有字段(例如dob,不及时更改的名称)? 2)您希望用户触发配置文件的保存还是使用任务调度来代替? 3)在您的store
方法中,您没有将用户 ID 链接到配置文件,也许您错过了它
@SrikanthGopi 尝试搜索何时使用 normalized
v/s denormalized
数据。此外,尝试为logging
搜索良好的数据库结构。对于您的用例而言,缓存可能为时过早,但如果您想进一步学习,那就去吧【参考方案3】:
从 Laravel 5.3 和 mysql 5.7 开始,您可以使用 Column Modifiers virtualAs
和 storedAs
创建一个 VIRTUAL
(在读取行时评估) 或 STORED
(在插入或更新行时评估和存储) 为 MySQL 生成列。
我在下面的例子中使用了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)');
...
);
【讨论】:
【参考方案4】:将计算字段存储在数据库中的最佳做法是什么。
这取决于您的用例。如果您使用的是关系数据库,并且您的用例不涉及大数据(在数量、种类或速度方面),最佳做法是不存储计算字段并在飞。
如果您使用的是 noSQL 数据库(例如 Laravel 支持的 MongoDB)或 Hadoop,最佳做法是存储计算字段以避免计算时间。
简而言之
这是时间复杂度和空间/存储复杂度之间的权衡。 对于大数据/noSQL 系统,存储计算字段,特别是如果 它们在计算上很复杂。对于关系数据库,计算 它们在运行中并保持您的数据非冗余
用于 RDBMS 的 Laravel 解决方案:
像这样使用accessors:
public function getBmiAttribute($value)
return ($this->weight / ($this->height * $this->height));
NoSql 的 Laravel 解决方案
像这样使用model events:
protected static function boot()
parent::boot();
static::saving(function($model)
$model->bmi = $model->weight / ($model->height * $model->height);
);
【讨论】:
【参考方案5】:如果您的 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 服务器:Computed Columns
ALTER TABLE dbo.Products ADD RetailValue AS (QtyAvailable * UnitPrice * 1.35);
MySQL:Generated Columns
ALTER TABLE t1 添加列 c2 INT 生成始终为 (c1 + 1) 存储;
甲骨文:Virtual Columns
ALTER TABLE emp2 ADD (income AS (salary + (salary*commission_pct)));
PostgreSQL 似乎不支持它(还),但this SO answer 提供了一个不错的解决方法。
【讨论】:
以上是关于数据库中的计算字段 - Laravel的主要内容,如果未能解决你的问题,请参考以下文章