Upsert feature
wakjoko opened this issue · comments
Hi @yajra
Upsert feature is not yet available and i'm looking into this since few days ago.
But wondering if you would like to introduce upsert
feature into this awesome package.
Proposal
By far this is what's working for me and wanna get feedback from anyone before submitting PR.
Btw, i only have Oracle 11g while adding this feature.
Oci8/Query/Grammars/OracleGrammar.php
/**
* Compile an "upsert" statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @param array $uniqueBy
* @param array $update
* @return string
*/
public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
{
$source = 'laravel_source';
$destination = $query->from;
$sql = "merge into {$this->wrapTable($destination)} ";
$sourceValues = null;
foreach ($values as $forUpsert) {
$row = DB::table('dual');
foreach ($forUpsert as $key => $value) {
$row->selectRaw($this->parameter($value) .' '. $this->wrap($key));
}
$sourceValues = !$sourceValues ? $row : $sourceValues->unionAll($row);
}
$sql .= "using ({$sourceValues->toSql()}) {$this->wrapTable($source)} ";
$on = collect($uniqueBy)->map(function ($column) use ($source, $destination) {
return "{$this->wrap("{$destination}.{$column}")} = {$this->wrap("{$source}.{$column}")}";
})->implode(' and ');
$sql .= "on ({$on}) ";
if ($update) {
$update = collect($update)->map(function ($value, $key) use ($source, $destination) {
return is_numeric($key)
? "{$this->wrap("{$destination}.{$value}")} = {$this->wrap("{$source}.{$value}")}"
: "{$this->wrap("{$source}.{$key}")} = {$this->parameter($value)}";
})->implode(', ');
$sql .= "when matched then update set {$update} where {$on} ";
}
// remove unique columns when inserting new record
// intended usage only for $uniqueBy is a pk columns which is being used in "ON" clause
$columns = array_values(array_diff(array_keys(reset($values)), $uniqueBy));
$sourceColumns = collect($columns)->implode(fn ($column) => "{$this->wrap("{$source}.{$column}")}", ', ');
$destinationColumns = collect($columns)->implode(fn ($column) => "{$this->wrap("{$destination}.{$column}")}", ', ');
$sql .= "when not matched then insert ({$destinationColumns}) values ({$sourceColumns})";
return $sql;
}
My usage example:
// id is primary key
$values = [
[
"id" => 1,
"type_id" => "1",
"fraction_id" => "1",
"frequency_id" => "1",
"amount" => 170,
],
[
"id" => 3,
"type_id" => "1",
"fraction_id" => "2",
"frequency_id" => "4",
"amount" => 600,
],
[
"id" => 4,
"type_id" => "1",
"fraction_id" => "3",
"frequency_id" => "5",
"amount" => 900,
],
[
"id" => 11,
"type_id" => "1",
"fraction_id" => "4",
"frequency_id" => "3",
"amount" => 900,
],
[
"id" => 2,
"type_id" => "1",
"fraction_id" => "5",
"frequency_id" => "1",
"amount" => 2000,
],
[
"id" => 5,
"type_id" => "1",
"fraction_id" => "7",
"frequency_id" => "4",
"amount" => 200,
]
];
$query = MyModel::query();
$query->upsert($values, [$query->getModel()->getKeyName()], $query->getModel()->fillable);
In my case, the compileUpsert()
produced:
merge into "MY_TABLE"
using (
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
union all
(
select
? "AMOUNT",
? "FRACTION_ID",
? "FREQUENCY_ID",
? "ID",
? "TYPE_ID"
from
"DUAL"
)
) "LARAVEL_SOURCE"
on (
"MY_TABLE"."ID" = "LARAVEL_SOURCE"."ID"
)
when matched then
update set
"MY_TABLE"."TYPE_ID" = "LARAVEL_SOURCE"."TYPE_ID",
"MY_TABLE"."FRACTION_ID" = "LARAVEL_SOURCE"."FRACTION_ID",
"MY_TABLE"."FREQUENCY_ID" = "LARAVEL_SOURCE"."FREQUENCY_ID",
"MY_TABLE"."AMOUNT" = "LARAVEL_SOURCE"."AMOUNT"
where
"MY_TABLE"."ID" = "LARAVEL_SOURCE"."ID"
when not matched then
insert (
"MY_TABLE"."AMOUNT",
"MY_TABLE"."FRACTION_ID",
"MY_TABLE"."FREQUENCY_ID",
"MY_TABLE"."TYPE_ID"
)
values
(
"LARAVEL_SOURCE"."AMOUNT",
"LARAVEL_SOURCE"."FRACTION_ID",
"LARAVEL_SOURCE"."FREQUENCY_ID",
"LARAVEL_SOURCE"."TYPE_ID"
)
System details
- Operating System: Ubuntu 20.04
- PHP Version: 8.1
- Laravel Version: 10.20.0
- Laravel-OCI8 Version: 10.3.2
- Oracle: 11g
@wakjoko thanks for this. Go ahead and submit the PR, please.
This issue is stale because it has been open for 30 days with no activity.
This issue was closed because it has been inactive for 7 days since being marked as stale.