在插入前检查数据库参数的高效方法


Laravel - efficient way to check database parameters before insert

我已经填充了一个表单,其中生成的每个文本字段都基于数据库结果。我只是使用id命名每个文本字段。现在,当表单填好后,我使用controller来保存它。但是在插入数据库之前,我循环Request::input()来检查每个条目是否存在。我只是想知道是否有有效的方法来检查循环中的每个项目以将其插入db。这是我的代码

public function store(Request $request, $id, $inid)
{       
    $startOfDay = Carbon::now()->startOfDay();
    $endOfDay = Carbon::now()->endOfDay();
    $instruments = InstrumentReading::whereBetween('created_at', [$startOfDay, $endOfDay])
                                    ->where('iv_inid', '=', $inid)
                                    ->get();
    foreach ($request->input() as $k => $v) {
        $read = new InstrumentReading;
        $read->iv_inid = $inid;
        $read->iv_ipid = $k;
        $read->iv_usid = Auth::user()->id;
        $read->iv_reading = $v;
        $read->save();
    }
    if ($instruments->count() > 0) {            
        //to filter the iv_ipid...
        foreach($instruments as $instrument)
        {
            $instrument->iv_status = "VOID";
            $instrument->save();
        }
    }
}

在有效的方法中,您可以做的是简单地检查/从数据库中获取ONLY所有可能的行,如果行已经插入,则在循环中检查。也只取iv_ipid列,因为我们不需要表中的所有列来做我们的检查。只选择我们需要的列会更快。您可以直接使用Fluent(查询生成器)而不是Eloquent从数据库中提取数据,因为它大大提高了这样一个简单查询的性能。

public function store(Request $request, $id, $inid)
{
    // Search only records with submitted iv_ipid, iv_inid and created today
    $alreadyInserted = DB::table('instrument_readings')
                   ->whereBetween('created_at', [
                       Carbon::now()->startOfDay(), 
                       Carbon::now()->endOfDay()
                   ])
                   // Get only records with submitted iv_ipid
                   ->whereIn('iv_ipid', array_keys($request->input()))
                   // Get records with given iv_inid only
                   ->where('iv_inid', $inid)
                   // For our check we need only one column, 
                   // no need to select all of them, it will be fast
                   ->select('iv_ipid')
                   // Get the records from DB
                   ->lists('iv_ipid');
    foreach ($request->input() as $k => $v) {
        // Very simple check if iv_ipid is not in the array
        // it does not exists in the database
        if (!in_array($k, $alreadyInserted)) {
            $read = new InstrumentReading;
            $read->iv_inid = $inid;
            $read->iv_ipid = $k;
            $read->iv_usid = Auth::user()->id;
            $read->iv_reading = $v;
            $read->save();
        } else {
            //todo
        }
}

这是迄今为止建议的最有效的方法,因为您只获取您感兴趣的记录,而不是今天的所有记录。而且,您只获取一列,即我们需要进行out检查的那一列。Eloquent通常会给性能带来很多过热,所以在建议的代码中,我直接使用Fluent,这将提高这部分代码执行速度约20%。

你在原始代码中的错误是你每次都在循环中进行数据库调用。当你需要像检查这样简单的任务时,永远不要将数据库调用、查询等放在循环中。这太过分了。相反,在循环之前选择所有需要的数据,然后进行检查。

现在这是在你只需要保存新记录到数据库的情况下。如果你想在循环中操作每条记录,假设你需要循环遍历每条提交的条目,获取获取模型,如果不存在则创建模型然后对模型进行其他操作,那么最有效的方法是这样的:

public function store(Request $request, $id, $inid)
{
    foreach ($request->input() as $k => $v) {
       // Here you search for match with given attributes
       // If object in DB with this attributes exists
       // It will be returned, otherwise new one will be constructed
       // But yet not saved in DB
       $model = InstrumentReading::firstOrNew([
           'iv_inid' => $inid,
           'iv_ipid' => $k,
           'iv_usid' => Auth::user()->id
       ]);
       // Check if it is existing DB row or a new instance
       if (!$model->exists()) {
           // If it is a new one set $v and save
           $model->iv_reading = $v;
           $model->save();
       }
    // Do something with the model here
    .....   
}

这样Laravel将检查模型与传递的参数是否已经存在于数据库中,如果是,它将返回给你。如果它不存在,它将创建它的新实例,因此您可以设置$v并保存到db。这样你就可以用这个模型做其他事情了你可以确保它在数据库中存在

第一种方法(效率优先)

考虑使用一个简单的SQL INSERT IGNORE查询并使用Fluent,即:

  • 创建一个复合唯一键,包含:

    1. iv_inid
    2. iv_ipid
    3. created_time,长达一个小时的粒度,这很重要,因为created_at的粒度可能比您的预期目的大得多,并且可能会减慢速度。
  • 使用DB,即:

DB:查询("将IGNORE插入到$yourTable VALUES(…)"

);

优点:
-非常快,所有必要的检查都在DB服务器上完成

缺点:
-您无法知道哪些值触发了重复值/唯一键冲突,因为相关错误被视为警告。

第二种方法(方便第一)

使用firstOrFail,即:

$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
// ... for
try {
    InstrumentReading::where('iv_inid', $inid)
        ->where('iv_ipid', $k)
        ->whereBetween('created_at', [$startOfDay, $endOfDay])
        ->firstOrFail();
    continue;
} catch (ModelNotFoundException $e) {
    $instrumentReading = InstrumentReading::create([
        // your values
    ]);
}
// ... endfor

优点:
-易于实现

缺点:
-略慢于简单查询

每次需要检查值时,代码都会向数据库发送请求。相反,搜索当天的所有值,然后检查值。此方法将只向数据库发送一次请求。

$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
// Search only this day
$instruments = InstrumentReading::whereBetween('created_at', [$startOfDay, $endOfDay])->get();
foreach($instruments as $instrument)
{
    // Check the value
}