数据库中的计算字段 - 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 virtualAsstoredAs 创建一个 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的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Eloquent ORM字段处理

Laravel 5.2 中的批量插入

如何访问laravel中的对象字段?

Laravel:添加到列中的现有字段数据

设置中的 Laravel-Backpack/Settings 表字段

在 Laravel 中如何使用 if 语句停止计算?