LessThanJake's avatar

Overloading a DB Table, Schema Design Concerns

My project is a multi-tenant, single database system for design Projects. Company tenants usually service external Companies, but I am also trying to allow for Company tenants who do not have client Companies (they only have internal/in-house Projects). I have been attempting to utilize a single Companies table, using a can_parent bool field to determine if they can add client Companies and a company_id field to indicate which company is the parent. I’m trying to be efficient, but I have a bad feeling I’m going to keep running into ownership issues with my current design.

Schema::create('companies', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->unsignedBigInteger('company_id')->nullable();
            $table->boolean('can_parent')->default(1);
            $table->boolean('active')->default(1);
            $table->unsignedBigInteger('created_by')->nullable();
            $table->timestamps();
            $table->softDeletes();

            $table->foreign('company_id')
                ->references('id')
                ->on('companies')
                ->onDelete('cascade');
        });

I have a Company model and a Companies model (I’m adding to the Company booted method to pull in the client companies and allow the parent company to add/edit them. Works okay.

Feels a little hacky, I’m pretty sure there’s a better Laravel way to make these models work that I’ll come upon, but I am successfully adding tenant Companies and their external Companies without error. Now, here’s where I am encountering the issue that is making me rethink my design. I have a Projects model which are owned by the external Companies via company_id, but need to be accessed/controlled by the tenant Company (the parent of the external Company). It doesn’t feel very efficient in the current design, and adding to the booted query currently disables me from editing the projects (since the company join returns the ‘id’ field for both the projects and the companies, the edit query is ambiguous).

I could add an additional field to the Projects table and call it companies_id, storing the tenant parent Company with each record, but that seems inefficient. That said, the current query above is no model of efficiency, either. Most queries will build-on/run-through the Company & Projects tables, so I’m sweating how I am currently going about this. Really would appreciate any advice on how I could improve my design.

0 likes
6 replies
Tray2's avatar

Did I read that right? You are using a foreign key constraint to the same table?

However, why not just add a internal company, or several and just set an internal flag on them? I probably wouldn't even do that, since the company belongs to a user if I read your text right.

Btw, I hope your user name is in reference to the awesome band with the same name.

LessThanJake's avatar

@Tray2 Yep, foreign key on the same table. Did some reading before I did it, seems it's acceptable. There is an Admin Model for internal control/admin of the Companies, tenant Companies are all 3rd parties/clients of the system. The three reasons in my head rationalizing why I'm doing it this way (for now):

  1. I'm capturing identical information for tenant Companies and externals; they are basically/potentially/functionally the same in the system, save for the externals having a parent Company that controls their access and abilities.
  2. Breaking out into separate tables for two types of Company will increase the complexity of the roles/permissions architecture and duplicate code.
  3. I want external companies to be have the option to become tenant Companies without losing all their Projects, People, etc.,.

Yep yep, the band is the reason behind my name. My initial impulse was taking "Me First and the Gimme Gimmes", but I wasn't sure that name would translate appropriately in certain languages :D

1 like
Tray2's avatar

@LessThanJake With that kind of setup, I would probably create a dummy/internal company that I then connect the companies to, then I can move those companies to other companies if I want to.

LessThanJake's avatar

@tray2 Thanks Tray, but I think I'm not explaining the system clearly. Companies will come to my app to use the Project management services the app offers. Those (tenant/parent) companies will join and then create Company profiles for their clients (parented companies), so their clients can login and interact/collaborate on the Projects. Some tenant/parent companies will chose to not allow their parented companies to have login access, but they'll still need the exact same Company data so they can invoice/bill them, add client/contacts, etc.,.

My primary goal is to achieve the above functionality; the ability to convert a parented company into a parent company or move it to another parent company is just functionality I hope to achieve in the future.

Tray2's avatar

@LessThanJake So you have something like

Consultant Companies (Parent) that can have Collaboration Companies (Allow login) or Non-collaboration companies (Disallow login)?

LessThanJake's avatar

@Tray2 Thanks for the interaction, it's helped me think over the situation. I've decided the best course is to add a parent_company_id to the projects table. There will be some redundancy (company_id & parent_company_id are the same) in the case where a tenant Company is never a parent (only has in-house/inter-company Projects, cannot parent additional companies), but that's more of an exception to the general situation. I expect the vast majority of tenant Companies will be parents. I was able to get rid of that join by adding that column, which also fixed the ambiguous ID issue.

1 like

Please or to participate in this conversation.