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
This also handles multiple simultaneous inserts (instead of one record at a time).
Warning: Eric's comment below is probably correct. This code worked for my past project, but before using this code again, I'd take a closer look at it and add test cases and adjust the function until it always works as intended. It might be as simple as moving the TODO line down outside the
if
braces.
Either put this in your model's class or in a BaseModel class that your model extends:
/**
* @see https://stackoverflow.com/a/25472319/470749
*
* @param array $arrayOfArrays
* @return bool
*/
public static function insertIgnore($arrayOfArrays) {
$static = new static();
$table = with(new static)->getTable(); //https://github.com/laravel/framework/issues/1436#issuecomment-28985630
$questionMarks = '';
$values = [];
foreach ($arrayOfArrays as $k => $array) {
if ($static->timestamps) {
$now = \Carbon\Carbon::now();
$arrayOfArrays[$k]['created_at'] = $now;
$arrayOfArrays[$k]['updated_at'] = $now;
if ($k > 0) {
$questionMarks .= ',';
}
$questionMarks .= '(?' . str_repeat(',?', count($array) - 1) . ')';
$values = array_merge($values, array_values($array));//TODO
}
}
$query = 'INSERT IGNORE INTO ' . $table . ' (' . implode(',', array_keys($array)) . ') VALUES ' . $questionMarks;
return DB::insert($query, $values);
}
Use like this:
Shop::insertIgnore([['name' => 'myShop'], ['name' => 'otherShop']]);
For the job you need to create a new Grammar that will have the right string in there:
grammar.php (1)
The grammar is a public property of the DB
or in this case Database
stored connection. This is not really straight forward, but from the visibility of the properties you should be able to inject your special grammar into the database layer.
I also suggest you bring the issue up with the project, they probably have got a better idea how to make that more flexible for cases like these.
(1) This was a former, to the date of the answer reference. If you see this today, you need to adopt to the Laravel version you use, e.g. Grammar.php for 4.0, these classes have moved into laravel/framework
.
$your_array = array('column' => 'value', 'second_column' => 'value');
DB::table('your_table')->insert($your_array);
Keep in mind, I don't know where your data is coming from, but you should sanitize it, always. If you have more than one record, just iterate over in a loop.
As far as the INSERT IGNORE
, find the INSERT
method in the fluent library, make a new method called insert_ignore the exact same way as insert and just modify with the IGNORE
.
Not sure if helpful for anybody but recently I have adapted hakre's approach to Laravel 5:
You have to change following 3 files to have your Insert Ignore working:
In Builder.php (vendor/laravel/framework/src/illuminate/database/query/Builder.php) you have to clon the function insert, with the change in name to insertIgnore and change in the grammar call function to: $sql = $this->grammar->compileInsertIgnore($this, $values);)
In Grammar.php (vendor/laravel/framework/src/illuminate/database/query/grammars/Grammar.php) you have to clone the compileInsert function and rename it to compileInsertIgnore, where you change return to: return "insert ignore into $table ($columns) values $parameters";
In Connection.php (vendor/laravel/framework/src/illuminate/database/Connection.php) you have to simply clone the function insert and rename it to insertIgnore
Now you should be done, connection is able to recognise the function insertIgnore, builder is able to point it to correct grammar and grammar includes 'ignore' in the statement. Please note this works well for MySQL, might not be this smooth for other databases.
Try this magic, in your model:
public static function insertIgnore($array){
$a = new static();
if($a->timestamps){
$now = \Carbon\Carbon::now();
$array['created_at'] = $now;
$array['updated_at'] = $now;
}
DB::insert('INSERT IGNORE INTO '.$a->table.' ('.implode(',',array_keys($array)).
') values (?'.str_repeat(',?',count($array) - 1).')',array_values($array));
}
Use like this:
Shop::insertIgnore(array('name' => 'myshop'));
This is a great way to prevent constraint violations that may occur with firstOrCreate in a multi-user environment, if that 'name' property was a unique key.
Option to avoid writing code is: https://github.com/guidocella/eloquent-insert-on-duplicate-key
I have tested it just now - it works with my 5000 inserts at a time sometimes with duplicates...
With it you will get these functions:
User::insertOnDuplicateKey($data);
User::insertIgnore($data);