12

有没有一种快速的方法来修改由Laravel 的 Fluent生成的 SQL 查询,使其具有一个INSERT IGNORE而不是通常的INSERT?

我正在尝试插入一个包含五十个元素的数组。手动写出整个查询会使代码膨胀并使其更容易受到人为错误的影响。

4

10 回答 10

15

在你的模型中试试这个魔法:

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'));

如果该“名称”属性是唯一键,则这是防止在多用户环境中使用 firstOrCreate 可能发生的约束冲突的好方法。

于 2014-08-24T13:43:34.423 回答
5

我无法按照 Rastislav 的回答中的建议进行修补。

这对我有用:

  1. 自定义查询语法类中的重写compileInsert方法,它扩展了框架的 MySqlGrammar 类。

  2. setQueryGrammar通过从数据库连接实例调用方法来使用此自定义语法类的实例。

所以,类代码是这样的:

<?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方法,然后在方法内部,我只更改insertinsert 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);
    }
}
于 2017-07-17T18:58:50.773 回答
4

2018 年 Laravel Eloquent 的更新答案

这也处理多个同时插入(而不是一次一个记录)。

警告:埃里克在下面的评论可能是正确的。此代码适用于我过去的项目,但在再次使用此代码之前,我会仔细查看它并添加测试用例并调整功能,直到它始终按预期工作。这可能就像将 TODO 行向下移动到if大括号外一样简单。

将其放入模型的类或模型扩展的 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']]);

于 2018-07-19T20:14:25.257 回答
3

Laravel 5.8.33+ 的答案

如果现在有人读到这个:不需要任何黑客或查询生成器扩展。查询构建器本机提供了一个insertOrIgnore方法来做到这一点。

只需使用

DB::table('tablename')->insertOrIgnore([
    ['column_name' => 'row1', 'column2_name' => 'row1'],
    ['column_name' => 'row2', 'column2_name' => 'row2']
]);

有关详细信息,请参阅文档API 文档

于 2020-08-19T15:57:28.217 回答
1

对于这项工作,您需要创建一个新的语法,其中包含正确的字符串:

语法.php (1)

语法是DBor 在这种情况下Database存储连接的公共属性。这不是很直接,但从属性的可见性,您应该能够将您的特殊语法注入数据库层。

我还建议您提出该项目的问题,他们可能对如何使此类情况更加灵活有更好的想法。


(1)这是以前的,到答案参考日期为止。如果你今天看到这个,你需要适应你使用的 Laravel 版本,例如Grammar.php for 4.0,这些类已经移到laravel/framework.

于 2012-09-27T13:31:49.383 回答
1

将以下方法 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 时间戳。

于 2016-11-02T02:53:16.753 回答
1

不确定是否对任何人有帮助,但最近我已经将 hakre 的方法应用于 Laravel 5:

您必须更改以下 3 个文件才能使插入忽略工作:

  1. 在 Builder.php (vendor/laravel/framework/src/illuminate/database/query/Builder.php) 中,您必须克隆函数 insert,将名称更改为 insertIgnore,并将语法调用函数更改为:$sql = $this->grammar->compileInsertIgnore($this, $values);)

  2. 在 Grammar.php (vendor/laravel/framework/src/illuminate/database/query/grammars/Grammar.php) 中,您必须克隆 compileInsert 函数并将其重命名为 compileInsertIgnore,您将在此处更改返回:return "insert ignore into $table ($columns) values $parameters";

  3. 在 Connection.php (vendor/laravel/framework/src/illuminate/database/Connection.php) 你必须简单地克隆函数 insert 并将其重命名为 insertIgnore

现在您应该完成了,connection 能够识别函数 insertIgnore,builder 能够将其指向正确的语法,并且语法在语句中包含“ignore”。请注意,这适用于 MySQL,但对于其他数据库可能不那么顺利。

于 2016-08-16T16:09:13.997 回答
0

我终于找到了这个https://github.com/yadakhov/insert-on-duplicate-key这对我有很大帮助

用户::insertIgnore($users); 这是我正在使用的方法,为其提供行数组及其返回的受影响行

通过 composer 安装它: composer require yadakhov/insert-on-duplicate-key

于 2018-05-10T05:05:13.360 回答
0

避免编写代码的选项是: https ://github.com/guidocella/eloquent-insert-on-duplicate-key

我刚刚测试过它 - 它一次可以与我的 5000 个插入一起使用,有时会重复......

有了它,您将获得以下功能:

User::insertOnDuplicateKey($data);
User::insertIgnore($data);
于 2019-07-15T09:45:52.903 回答
-18
$your_array = array('column' => 'value', 'second_column' => 'value');

DB::table('your_table')->insert($your_array);

请记住,我不知道您的数据来自哪里,但您应该始终对其进行清理。如果您有多个记录,只需在循环中进行迭代。

至于INSERT IGNORE,在 fluent 库中找到INSERT方法,创建一个名为 insert_ignore 的新方法,方法与 insert 完全相同,只需使用IGNORE.

于 2012-09-27T13:26:40.647 回答