Подтвердить что ты не робот

Laravel - эффективный способ проверки параметров базы данных перед вставкой

Я заполняю форму, в которой каждое генерируемое текстовое поле основано на результате базы данных. Я просто называю каждое текстовое поле, используя id. Теперь, когда форма заполнена, я использую контроллер для ее сохранения. Но перед вставкой базы данных я петлю 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();
        }
    }

}
4b9b3361

Ответ 1

В словах эффективного подхода вы можете просто проверить/выбрать ТОЛЬКО все возможные строки из базы данных и проверить цикл, если строка уже вставлена. Также выберите только столбец iv_ipid, так как нам не нужны все столбцы из таблицы для проверки. Будет быстрее выбрать только нужный столбец. Вы можете напрямую использовать Fluent (Query Builder) над 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
        }
}

Это наиболее эффективный способ, предлагаемый до сих пор, потому что вы получаете сразу только записи, которые вас интересуют, а не все записи с сегодняшнего дня. Также вы получаете только один столбец, тот, который нам нужен для проверки. Eloquent ususlally дает много перегрева на производительности, поэтому в предлагаемом коде я использую непосредственно 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. Поэтому вы можете сделать что-то еще с этой моделью, и вы можете быть уверены, что она существует в базе данных после этого момента.

Ответ 2

Ваш код будет отправлять запрос в базу данных каждый раз, когда вам нужно проверить значение. Вместо этого найдите все значение этого дня, затем проверьте значение. Этот подход будет отправлять запрос в базу данных только один раз.

$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
}

Ответ 3

Первый подход (эффективность сначала)

Рассмотрите возможность использования простого запроса SQL INSERT IGNORE и используйте Fluent, т.е.

  • Создайте составной уникальный ключ, содержащий:

    • iv_inid
    • iv_ipid
    • created_time, до гранулярности в час, это важно, потому что created_at может иметь гораздо большую детализацию, чем ваша предполагаемая цель, и может немного замедлить работу.
  • Используйте DB, т.е.:

DB:: запроса (      "INSERT IGNORE INTO $yourTable VALUES (...)" );

Плюсы:
- Чрезвычайно быстро, вся необходимая проверка выполняется на сервере БД

Минусы:
- Вы не можете знать, какие значения вызвали дублирующее значение/уникальное нарушение ключа, поскольку связанные с ним ошибки рассматриваются как предупреждения.

Второй подход (сначала удобство)

Используйте 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

Плюсы:
- Простота реализации

Минусы:
- Несколько медленнее, чем простые запросы