Viewed   129 times

I'm trying to update Model which has two primary keys.

Model

namespace App;

use IlluminateDatabaseEloquentModel;

class Inventory extends Model
{
    /**
     * The table associated with the model.
     */
    protected $table = 'inventories';

    /**
     * Indicates model primary keys.
     */
    protected $primaryKey = ['user_id', 'stock_id'];
...

Migration

Schema::create('inventories', function (Blueprint $table) {
    $table->integer('user_id')->unsigned();
    $table->integer('stock_id')->unsigned();
    $table->bigInteger('quantity');

    $table->primary(['user_id', 'stock_id']);

    $table->foreign('user_id')->references('id')->on('users')
        ->onUpdate('restrict')
        ->onDelete('cascade');
    $table->foreign('stock_id')->references('id')->on('stocks')
        ->onUpdate('restrict')
        ->onDelete('cascade');
});

This is code which should update Inventory model, but it doesn't.

$inventory = Inventory::where('user_id', $user->id)->where('stock_id', $order->stock->id)->first();
$inventory->quantity += $order->quantity;
$inventory->save();

I get this error:

Illegal offset type

I also tried to use updateOrCreate() method. It doesn't work (I get same error).

Can anyone tell how Model with two primary key should be updated?

 Answers

5

I've run into this problem a couple of times. You need to override some properties:

protected $primaryKey = ['user_id', 'stock_id'];
public $incrementing = false;

and methods (credit):

/**
 * Set the keys for a save update query.
 *
 * @param  IlluminateDatabaseEloquentBuilder  $query
 * @return IlluminateDatabaseEloquentBuilder
 */
protected function setKeysForSaveQuery(Builder $query)
{
    $keys = $this->getKeyName();
    if(!is_array($keys)){
        return parent::setKeysForSaveQuery($query);
    }

    foreach($keys as $keyName){
        $query->where($keyName, '=', $this->getKeyForSaveQuery($keyName));
    }

    return $query;
}

/**
 * Get the primary key value for a save query.
 *
 * @param mixed $keyName
 * @return mixed
 */
protected function getKeyForSaveQuery($keyName = null)
{
    if(is_null($keyName)){
        $keyName = $this->getKeyName();
    }

    if (isset($this->original[$keyName])) {
        return $this->original[$keyName];
    }

    return $this->getAttribute($keyName);
}

Remember this code needs to reference Eloquent Builder class with

use IlluminateDatabaseEloquentBuilder;

I suggest putting those methods in a HasCompositePrimaryKey Trait so you can just use it in any of your models that need it.

Sunday, September 18, 2022
2

It's mostly raw queries:

DB::table('item_details')->selectRaw('GROUP_CONCAT(...) INTO @sql')->get();
DB::statement('SET @sql = CONCAT(...)');
DB::statement('PREPARE stmt FROM @sql');
DB::statement('EXECUTE stmt');
DB::statement('DEALLOCATE PREPARE stmt');

Try this:

DB::table('item_details')->selectRaw('GROUP_CONCAT(...) INTO @sql')->get();
$sql = DB::selectOne('select @sql')->{'@sql'};
ItemDetails::select('item_number', DB::raw('SUM(quantity) as total_quantity'))
    ->selectRaw($sql)
    ->groupBy('item_number')
    ->get();
Sunday, October 23, 2022
3

This can be done in (at least) 2 ways.

Using pure Eloquent model logic:

class Buy extends Model
{
  public function getTotalPrice() {
    return $this->buyDetails->sum(function($buyDetail) {
      return $buyDetail->quantity * $buyDetail->price;
    });
  }
}

The only issue here is that it needs to fetch all buy details from the database but this is something you need to fetch anyway to display details in the view.

If you wanted to avoid fetching the relation from the database you could build the query manually:

class Buy extends Model
{
  public function getTotalPrice() {
    return $this->buyDetails()->sum(DB::raw('quantity * price'));
  }
}
Thursday, September 1, 2022
 
3

You are talking about a legacy READONLY database then, perhaps you can create an external schema (views) with no multi-column PKs. For example you can concatenate field keys. Here and example:

For example:

Tables:

create table A (
  a1 int not null,
  a2 int not null,
  t1 varchar(100),
  primary key (a1, a2)
)

create table B (
  b1 int not null,
  b2 int not null,
  a1 int not null,
  a2 int not null,
  t1 varchar(100),
  primary key (b1, b2),
  constraint b_2_a foreign key (a1,a2) 
  references A (a1, a2)
)

External schema to be read by django:

Create view vA as 
select 
   a1* 1000000 + a2 as a, A.* 
from A

Create view vB as 
select 
   b1* 1000000 + b2 as b, 
   a1* 1000000 + a2 as a, B.* 
from B

django models:

class A(models.Model):
    a = models.IntegerField(  primary_key=True )
    a1 = ...
    class Meta(CommonInfo.Meta):
        db_table = 'vA'    

class B(models.Model):
    b = models.IntegerField(  primary_key=True )
    b1 = ...
    a = models.ForeignKey( A )
    a1 = ...
    class Meta(CommonInfo.Meta):
        db_table = 'vB'    

You can refine technique to make varchar keys to be able to work with indexes. I don't write more samples because I don't know what is your database brand.

More information:

Do Django models support multiple-column primary keys?

ticket 373

Alternative methods

Tuesday, August 30, 2022
5

If you declare the constraint separately (table level), it makes more sense

create table Machine-Part
(
  Machine_ID int NOT NULL ,
  Part_ID int NOT NULL ,
  Factory_Note varchar(30) NULL,

  PRIMARY KEY (Machine_ID, Part_ID),
  UNIQUE INDEX (Part_ID, Machine_ID),
  foreign key (Machine_ID) references (Machine.Machine_ID),
  foreign key (Part_ID) references (Part.Part_ID)
) 

Link tables almost always need a reverse index too

Saturday, October 29, 2022
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :