yajra / laravel-oci8

Oracle DB driver for Laravel via OCI8

Home Page:https://yajrabox.com/docs/laravel-oci8

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.