LaravelのFluentによって生成されたSQLクエリをINSERT IGNORE
通常の代わりに変更する簡単な方法はありますINSERT
か?
50個の要素を持つ配列を挿入しようとしています。クエリ全体を手動で書き出すと、コードが肥大化し、人為的エラーの影響を受けやすくなります。
LaravelのFluentによって生成されたSQLクエリをINSERT IGNORE
通常の代わりに変更する簡単な方法はありますINSERT
か?
50個の要素を持つ配列を挿入しようとしています。クエリ全体を手動で書き出すと、コードが肥大化し、人為的エラーの影響を受けやすくなります。
モデルでこの魔法を試してください:
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));
}
このように使用します:
Shop::insertIgnore(array('name' => 'myshop'));
これは、「name」プロパティが一意のキーである場合に、マルチユーザー環境でfirstOrCreateで発生する可能性のある制約違反を防ぐための優れた方法です。
Rastislavの回答で示唆されているように、モンキーパッチを適用できませんでした。
これは私のために働いたものです:
compileInsert
フレームワークのMySqlGrammarクラスを拡張するカスタムQueryGrammarクラスのオーバーライドメソッド。
setQueryGrammar
DB接続インスタンスからメソッドを呼び出して、このカスタム文法クラスのインスタンスを使用します。
したがって、クラスコードは次のようになります。
<?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";
}
}
フレームワークのクラスからメソッドをコピーしたcompileInsert
後、メソッド内でに変更insert
しただけinsert ignore
です。他のすべては同じに保たれています。
次に、コードの特定の場所で、「挿入無視」が必要なアプリケーション(スケジュールされたタスク)で、次のように実行しました。
<?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);
}
}
これは、(一度に1つのレコードではなく)複数の同時挿入も処理します。
警告:以下のエリックのコメントはおそらく正しいです。このコードは私の過去のプロジェクトでは機能しましたが、このコードを再度使用する前に、コードを詳しく調べてテストケースを追加し、常に意図したとおりに機能するまで関数を調整しました。
if
TODO行を中括弧の外側に移動するのと同じくらい簡単かもしれません。
これをモデルのクラスまたはモデルが拡張するBaseModelクラスに配置します。
/**
* @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);
}
このように使用します:
Shop::insertIgnore([['name' => 'myShop'], ['name' => 'otherShop']]);
最近誰かがこれを読んだら:ハックやQueryBuilder拡張機能は必要ありません。クエリビルダーは、insertOrIgnore
まさにそれを行うメソッドをネイティブに提供します。
使用するだけ
DB::table('tablename')->insertOrIgnore([
['column_name' => 'row1', 'column2_name' => 'row1'],
['column_name' => 'row2', 'column2_name' => 'row2']
]);
詳細については、ドキュメントまたはAPIドキュメントを参照してください。
この仕事では、正しい文字列を含む新しい文法を作成する必要があります。
grammar.php (1)
DB
文法は、またはこの場合はDatabase
保存された接続のパブリックプロパティです。これは実際には簡単ではありませんが、プロパティの可視性から、データベースレイヤーに特別な文法を挿入できるはずです。
また、プロジェクトで問題を提起することをお勧めします。彼らはおそらく、このような場合にそれをより柔軟にする方法についてより良いアイデアを持っているでしょう。
(1)これは、回答参照の日付までは前者でした。これが今日見られる場合は、使用するLaravelバージョン(4.0のGrammar.phpなど)を採用する必要があります。これらのクラスはに移動しましlaravel/framework
た。
誰かに役立つかどうかはわかりませんが、最近、hakreのアプローチをLaravel5に適応させました。
Insert Ignoreを機能させるには、次の3つのファイルを変更する必要があります。
Builder.php(vendor / laravel / framework / src / illuminate / database / query / Builder.php)では、名前をinsertIgnoreに変更し、文法呼び出し関数を次のように変更して、関数insertをクローン化する必要があります。$sql = $this->grammar->compileInsertIgnore($this, $values);)
Grammar.php(vendor / laravel / framework / src / illuminate / database / query / grammars / Grammar.php)で、compileInsert関数のクローンを作成し、名前をcompileInsertIgnoreに変更する必要があります。ここで、returnを次のように変更します。return "insert ignore into $table ($columns) values $parameters";
Connection.php(vendor / laravel / framework / src / illuminate / database / Connection.php)では、関数insertのクローンを作成し、名前をinsertIgnoreに変更するだけです。
これで完了です。接続は関数insertIgnoreを認識でき、ビルダーはそれを正しい文法に向けることができ、文法にはステートメントに「ignore」が含まれています。これはMySQLでうまく機能することに注意してください。他のデータベースでは、これほどスムーズではない可能性があります。
次のメソッドinsertIgnoreをモデルに追加します
<?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;
}
}
次を使用できます。
App\User::insertIgnore([
'name' => 'Marco Pedraza',
'email' => 'mpdrza@gmail.com'
]);
次のクエリが実行されます。
insert ignore into `users` (`name`, `email`, `updated_at`, `created_at`) values (?, ?, ?, ?)
このメソッドは、有効または無効にした場合、Eloquentタイムスタンプを自動的に追加/削除します。
私は最後にこれを見つけましたhttps://github.com/yadakhov/insert-on-duplicate-keyこれは私を大いに助けました
User :: insertIgnore($ users); これは私が使用しているメソッドであり、行の配列とその返される影響を受けた行を提供します
作曲家を通してそれをインストールしてください:作曲家はyadakhov/insert-on-duplicate-keyを必要とします
コードの記述を回避するオプションは次のとおりです: https ://github.com/guidocella/eloquent-insert-on-duplicate-key
私は今それをテストしました-それは時々重複して私の5000のインサートで動作します...
これを使用すると、次の機能を利用できます。
User::insertOnDuplicateKey($data);
User::insertIgnore($data);
$your_array = array('column' => 'value', 'second_column' => 'value');
DB::table('your_table')->insert($your_array);
データがどこから来ているのかわかりませんが、常にサニタイズする必要があります。複数のレコードがある場合は、ループで繰り返します。
までは、流暢なライブラリでメソッドをINSERT IGNORE
見つけ、INSERT
insert_ignoreという新しいメソッドを挿入とまったく同じ方法で作成し、。で変更しIGNORE
ます。