Skip to content

Instantly share code, notes, and snippets.

@tomcastleman
Last active March 18, 2020 09:32
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomcastleman/7285a59ee242f929e0cbe6974bc4b799 to your computer and use it in GitHub Desktop.
Save tomcastleman/7285a59ee242f929e0cbe6974bc4b799 to your computer and use it in GitHub Desktop.
Laravel Query Builder Upsert Macro for MySQL
<?php
namespace App\Database;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection;
class UpsertBuilder
{
/** @var Builder */
protected $builder;
public function __construct(Builder $builder)
{
$this->builder = $builder;
}
/**
* @param Collection $values
*
* @return Collection
*/
protected function getWrappedColumns(Collection $values): Collection
{
return collect($values->first())->keys()->map([$this->builder->grammar, 'wrap']);
}
/**
* @param Collection $values
*
* @return Collection
*/
protected function getQuotedValues(Collection $values): Collection
{
return $values->map(function ($row) {
return collect($row)->map(function ($value) {
if (is_null($value)) {
return 'NULL';
}
return $this->builder->connection->getPdo()->quote($value);
});
});
}
/**
* @param Collection $values
*
* @return string
*/
protected function generateInsertValues(Collection $values): string
{
return $values->map(function (Collection $row) {
return '(' . $row->implode(',') . ')';
})->implode(',');
}
/**
* @param Collection $columns
*
* @return string
*/
protected function generateUpdateValues(Collection $columns): string
{
return $columns->map(function ($column) {
return sprintf(
'%s=%s',
$column,
"VALUES({$column})"
);
})->implode(',');
}
/**
* @param array $values
*
* @return string
*/
public function getQuery(array $values): string
{
$values = collect($values);
$wrappedColumns = $this->getWrappedColumns($values);
return sprintf(
'INSERT INTO %s (%s) VALUES %s ON DUPLICATE KEY UPDATE %s',
$this->builder->grammar->wrapTable($this->builder->from),
$wrappedColumns->implode(','),
$this->generateInsertValues($this->getQuotedValues($values)),
$this->generateUpdateValues($wrappedColumns)
);
}
}
<?php
namespace App\Providers;
use App\Database\UpsertBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\ServiceProvider;
class UpsertServiceProvider extends ServiceProvider
{
public function boot()
{
//
}
public function register()
{
Builder::macro('upsert', function (array $values) {
/** @var Builder $this */
$builder = new UpsertBuilder($this);
return $this->connection->insert($builder->getQuery($values));
});
}
}
@tomcastleman
Copy link
Author

tomcastleman commented Sep 13, 2017

Usage:

DB::table('posts')->upsert([
    ['title' =>'foo', 'category_id' => 1]
]);

@mpdx
Copy link

mpdx commented Jul 19, 2018

How does it differ from updateOrCreate builder method? 🤔

@sahilsharma011
Copy link

@mpdx there is no updateOrCreate method for Laravel Query Builder, it exists in Eloquent and also that method is not as atomic as ON DUPLICATE KEY UPDATE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment