Is there a quick way to modify a SQL query generated by Laravel\'s Fluent to have an INSERT IGNORE
instead of the usual INSERT
?
I\'m trying
If anyone reads this nowadays: there's no need for any hacks or Query Builder extensions. The query builder natively provides an insertOrIgnore
method that does just that.
Just use
DB::table('tablename')->insertOrIgnore([
['column_name' => 'row1', 'column2_name' => 'row1'],
['column_name' => 'row2', 'column2_name' => 'row2']
]);
See the documentation or the API docs for details.
Add the follow method insertIgnore to your Model
<?php
namespace App;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'users';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name', 'email', 'password'];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = ['password', 'remember_token'];
public static function insertIgnore(array $attributes = [])
{
$model = new static($attributes);
if ($model->usesTimestamps()) {
$model->updateTimestamps();
}
$attributes = $model->getAttributes();
$query = $model->newBaseQueryBuilder();
$processor = $query->getProcessor();
$grammar = $query->getGrammar();
$table = $grammar->wrapTable($model->getTable());
$keyName = $model->getKeyName();
$columns = $grammar->columnize(array_keys($attributes));
$values = $grammar->parameterize($attributes);
$sql = "insert ignore into {$table} ({$columns}) values ({$values})";
$id = $processor->processInsertGetId($query, $sql, array_values($attributes));
$model->setAttribute($keyName, $id);
return $model;
}
}
You can use:
App\User::insertIgnore([
'name' => 'Marco Pedraza',
'email' => 'mpdrza@gmail.com'
]);
The next query it will be executed:
insert ignore into `users` (`name`, `email`, `updated_at`, `created_at`) values (?, ?, ?, ?)
This method automatically add/remove the Eloquent timestamps if you have enabled or disabled.
I lastly found this one https://github.com/yadakhov/insert-on-duplicate-key which helped me alot
User::insertIgnore($users); this is the method I am using, giving array of rows to it and its returning effected rows
install it through composer: composer require yadakhov/insert-on-duplicate-key
I couldn't monkey patch as suggested in Rastislav's answer.
This is what worked for me:
Override compileInsert
method in a custom Query Grammar class, which extends the framework's MySqlGrammar class.
Use an instance of this custom grammar class by calling the setQueryGrammar
method from the DB connection instance.
So, the class code is like this:
<?php
namespace My\Namespace;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\MySqlGrammar;
/**
* Changes "INSERT" to "INSERT IGNORE"
*/
class CustomMySqlGrammar extends MySqlGrammar
{
/**
* Compile an insert statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileInsert(Builder $query, array $values)
{
// Essentially we will force every insert to be treated as a batch insert which
// simply makes creating the SQL easier for us since we can utilize the same
// basic routine regardless of an amount of records given to us to insert.
$table = $this->wrapTable($query->from);
if (! is_array(reset($values))) {
$values = [$values];
}
$columns = $this->columnize(array_keys(reset($values)));
// We need to build a list of parameter place-holders of values that are bound
// to the query. Each insert should have the exact same amount of parameter
// bindings so we will loop through the record and parameterize them all.
$parameters = collect($values)->map(function ($record) {
return '('.$this->parameterize($record).')';
})->implode(', ');
return "insert ignore into $table ($columns) values $parameters";
}
}
I copied the compileInsert
method from the framework's class and then, inside the method, I have only changed insert
to insert ignore
. Everything else has been kept the same.
Then, in the specific spot of code, in the application (a scheduled task), where I needed "insert ignore", I have simply done as follows:
<?php
use DB;
use My\Namespace\CustomMySqlGrammar;
class SomeClass
{
public function someMethod()
{
// Changes "INSERT" to "INSERT IGNORE"
DB::connection()->setQueryGrammar(new CustomMySqlGrammar());
// et cetera... for example:
ModelClass::insert($data);
}
}