11

I unfortunately need to import data from a third-party vendor and use their non-standard database schema with my laravel project. In addition, I need to store multiple "firms," each with their own set of users in my database.

I'm trying to figure out the best way (if it can be done) to use Eloquent to handle the relationships between these tables. So for instance, with my table structure like this:

 BmPerson
  'id',
  'firmId',
  'personId'

BmCoverage
  'id',
  'firmId',
  'personId',
  'securityId'

BmSecurity
  'id',
  'firmId',
  'securityId'

... for instance, I need to associate a "BmPerson" with many "BmSecurity" through the "BmCoverage" table.

But I need to somehow use composite keys, because I am storing multiple "firms" in each table (per the 3rd party vendor's database schema).

One approach I've used so far is scoping, e.g.: for my BmCoverage model:

 public function scopeFromFirm($query,$firmId){
  return $query->where('firmId','=',$firmId);//->where('personId','=',$personId);}

public function scopeFromPerson($query,$personId){
  return $query->where('personId','=',$personId);//->where('personId','=',$personId);}

Then I can retrieve the coverage list for an individual person, but I still need to somehow be able to associate the "BmCoverage" with the "BmSecurities." I suppose I could just add a scope the BmSecurities class too, but it would be nicer to just use Eloquent.

Has anyone come up with a good way to use composite keys in laravel model relationships, or should I just stick with the scoping method?

Ben Wilson
  • 2,271
  • 3
  • 26
  • 35
  • What do your model relationships look like? – Mike Barwick Jul 11 '16 at 02:54
  • Why do you have 2 BmCoverage?. – Joshua Belarmino Jul 11 '16 at 03:07
  • Sorry, I've deleted the second one -- was a duplicate. Right now I can't relate these models because I would need to do so across 2 keys (e.g. firmId and personId in the case of relating BmPerson to BmCoverage). Does that make sense? I have a unique id column, but I need to be able to relate based on 2 keys instead of one. – Ben Wilson Jul 11 '16 at 13:45
  • Here is another way to phrase this: the third party vendor I am using supplies, for instance a "firmId" and a "securityId." However, the securityIds can repeat. So firmId 1 can have securityIds 1,2,3. firmId 2 could also have securityIds 1,2,3. Right now I am storing the securities in a table with the firmIds in a separate column, which doesn't work with laravel's relations tools. Is there a way I can change the database structure to make it compatible, or should I leave it with composite keys and use manual queries? Thanks. – Ben Wilson Jul 15 '16 at 02:52
  • 1
    Although BmCoverage is the pivot model and you can use that https://laravel.com/docs/5.0/eloquent#working-with-pivot-tables, But I would use Query Scopes concept https://laravel.com/docs/5.2/eloquent#query-scopes – Vanya Avchyan Jul 21 '16 at 19:36
  • Is it possible that 2 differen `BmPerson` could have the same `firmId` and same `personId` field? – Adam May 16 '19 at 16:04

5 Answers5

5

There is a package here that seems to be perfect for your case:

Compoships offers the ability to specify relationships based on two (or more) columns in Laravel 5's Eloquent. The need to match multiple columns in the definition of an Eloquent relationship often arises when working with third party or pre existing schema/database.

You would use it like this:

class BmPerson extends Model
{
    use \Awobaz\Compoships\Compoships;

    public function bmCoverages()
    {
        return $this->hasMany('App\BmCoverage', ['firmId', 'personId'], ['firmId', 'personId']);
    }
}

If every BmSecurity belongs to exactly one BmCoverage, and every BmCoverage belongs to exactly one BmPerson its probably easier to replace 'firmId', 'personId' with bmperson_id in BmCoverage DB; and 'firmId', 'securityId' with bmcoverage_id in BmSecurity. Then you can use default hasMany relations with one key.

Adam
  • 25,960
  • 22
  • 158
  • 247
  • Using compoship, how can I implement hasManyThrough()? – Delowar Hossain Mar 14 '20 at 06:30
  • @DelowarHossain does not seem to be possible. But to be sure, you may ask the repository owner – Adam Mar 14 '20 at 11:49
  • Hey @Adam, it would be great if you could hepl me to understand `Compoships` usage. The example provided in the docs don't seem to be clear for me. – Junaid Qadir Shekhanzai Apr 29 '20 at 10:38
  • @JunaidQadir just put `use \Awobaz\Compoships\Compoships;` in your class. The trait will overwrite `hasMany` function, so you can specify two id columns. – Adam Apr 29 '20 at 10:43
  • Sorry, everyone, I created another post and I had to post the link for @Adam and anyone who can help me https://stackoverflow.com/questions/61501540/laravel-relations-with-composite-foreign-keys – Junaid Qadir Shekhanzai Apr 29 '20 at 12:14
2

Everything you need for this can be found here https://laravel.com/docs/5.2/eloquent-relationships

You can easily define which cols sohuld be the referenced key.

Example:

public function bmCoverages() {
    return $this->hasMany('App\BmCoverage', 'firmId', 'id');
}

This would probably belong to your App\Firm or whatever it is called.

In general the hasMany relations looks like this

return $this->hasMany('App\Comment', 'foreign_key', 'local_key');

As you can see you can specify the keys.

Frnak
  • 6,601
  • 5
  • 34
  • 67
  • 1
    Right, but the problem is that I need to be able to relate across 2 keys. So a "BmPerson" needs to be related to many "BmCoverages," but I can't use a unique key because the 3rd party vendor has a "firmId" then "personId" within that firm. So firmId 1 may have personIds 1,2,3; and firmId 2 may also have personIds 1,2,3 – Ben Wilson Jul 11 '16 at 13:49
  • 1
    I appreciate the time you took to answer, but I'm trying to figure out a situation where the 3rd party vendor I'm importing data from doesn't supply a unique key for the items I need to reference, not how to use the relationships at a base level. Do you think I should try to assign my own unique key to items as I try to use larvae's relations? – Ben Wilson Jul 18 '16 at 21:00
  • 1
    the vendor should somehow provide unique data identifiers shouldn't he? I'd need a little more informatoin on what you receive to give more detailed hints. I can only guess otherwise: maybe create primary key from two or even three fields together to ensure uniqueness – Frnak Jul 18 '16 at 21:41
  • that was one thing I thought of actually... just mashing the firmId and personId together to create a unique key... wish there was a more elegant solution but I guess maybe it's either that or just write manual queries in my models/controllers. – Ben Wilson Jul 18 '16 at 22:33
  • 2
    I actually don't consider this as an ugly solution. Having two fields combined as a primary key is a pretty common thing – Frnak Jul 19 '16 at 07:14
  • 1
    `Having two fields combined as a primary key is a pretty common thing` if you create another column that is simply the combination of two other columns, then its not in 3rd normal form. The problem is, that whenever you update one column, you have to update the related column and this makes it hard to maintain. – Adam May 16 '19 at 16:02
1

As the others have said, you need to use the HasMany and HasManyThrough relationship.

Here from your table definitions, you simply need access to:

  • Person->BmCoverage(s)
  • Person->BmSecurity(s) of an individual.

What I think is the major problem here is linking the BmSecurity with BmCoverage as apparently there's no coverage_id per BmSecurity but rather, a composite mapping through firmId and securityId.

In this case, Laravel does not officially support composite keys unfortunately, although you could use a trait like this... but you could also achieve the same with some tricky hasMany.

i.e. on BmCoverage

$this->hasMany('BmSecurity', 'securityId', 'securityId')
     ->andWhere('firmId', '=', $this->firmId);

Same applies for BmSecurity from BmPerson using HasManyThrough.

Hope that helps.

Community
  • 1
  • 1
Chibueze Opata
  • 9,856
  • 7
  • 42
  • 65
0

read laravel hasManyThrough relationship . it will help you to write this query more easily

https://laravel.com/docs/5.1/eloquent-relationships#has-many-through

tapos ghosh
  • 2,114
  • 23
  • 37
0

I know that is a really old question. But i got this need on a custumer's project and i solved it using kind of like where.

$this->hasMany('BmCoverage', 'firmId', 'firmId', 'BmPerson')
 ->whereColumn('BmPerson.personId', '=', 'BmCoverage.personId');

I really expect that this answer helps someone.