vincent15000's avatar

Database design for commissions

Hello,

I work on an application that calculates commissions.

For each seller, there can be several types of commissions, some of them applied on only some products, others on all products. Furthermore the commission calculation rules can change from one month to another.

Here is my idea.

I know that JSON fields are not the best way and some devs really don't like JSON fields, but in this context, I think that it's not a real problem because values can be really very different.

$values = 25; // fixed
$values = 0.1; // percentage
$values = [
	[
		'value' => 0,
		'percentage' => 0.05;
	],
	[
		'value' => 1000,
		'percentage' => 0.10;
	],
	[
		'value' => 2500,
		'percentage' => 0.15;
	],
]; // tier

So here is the commission rules table structure.

commission_rules
- id
- started_at
- ended_at
- type (fixed, percentage, tier, sliding, ...)
- values (JSON field with values, each type doesn't have the same values, some need only one, others two or three values)
- user_id // seller
- product_id

Is it a good idea to duplicate the same values for 10 or 100 products when it's exactly the same ?

Or would it be better to create a table with the values and add a value_id field to the commission_rules ?

Given that after having saved values for example for all products, the user can customize only one commission rule for one or two products.

Some help to design this ?

Thanks a lot.

V

0 likes
13 replies
LaryAI's avatar
Level 58

Designing a database for commission calculations can be complex, especially when dealing with varying commission types and rules that can change over time. Your proposed structure is a good starting point, but there are a few considerations and potential improvements to make it more efficient and maintainable.

Proposed Database Design

  1. Commission Rules Table: This table will store the basic information about each commission rule.
CREATE TABLE commission_rules (
    id INT AUTO_INCREMENT PRIMARY KEY,
    started_at DATE,
    ended_at DATE,
    type ENUM('fixed', 'percentage', 'tier', 'sliding', ...),
    values JSON,
    user_id INT,
    product_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (product_id) REFERENCES products(id)
);
  1. Commission Values Table: Instead of storing the values directly in the commission_rules table, you can create a separate table for commission values. This will help avoid data duplication and make it easier to manage changes.
CREATE TABLE commission_values (
    id INT AUTO_INCREMENT PRIMARY KEY,
    rule_id INT,
    value DECIMAL(10, 2),
    percentage DECIMAL(5, 2),
    FOREIGN KEY (rule_id) REFERENCES commission_rules(id)
);
  1. Commission Rule Types Table: If you have many types and they have specific attributes, consider creating a table for rule types.
CREATE TABLE commission_rule_types (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50)
);
  1. Linking Products and Rules: If the same rule applies to multiple products, consider a many-to-many relationship.
CREATE TABLE product_commission_rules (
    product_id INT,
    rule_id INT,
    PRIMARY KEY (product_id, rule_id),
    FOREIGN KEY (product_id) REFERENCES products(id),
    FOREIGN KEY (rule_id) REFERENCES commission_rules(id)
);

Considerations

  • Normalization: By separating the commission values into their own table, you reduce redundancy and make it easier to update rules that apply to multiple products or users.

  • Flexibility: This design allows for flexibility in changing commission rules over time without affecting historical data, as each rule is tied to a specific time period.

  • Performance: JSON fields can be useful for storing complex data structures, but they can also impact performance. By normalizing the data, you can leverage SQL's indexing and querying capabilities more effectively.

  • Customization: If a user needs to customize a rule for a specific product, you can create a new entry in the commission_rules table for that product, ensuring that the original rule remains unchanged for other products.

This design should provide a robust framework for managing complex commission calculations while maintaining flexibility and performance.

Tray2's avatar

Would not use json for this.

I would use a commision_types table, to handle those, then I would use a pivot to join the different types of commisionable models with the commission type. The commision_type table would then have a valid_from and valid_to column, that you then check when you calculate the commission.

4 likes
vincent15000's avatar

@Tray2 But how would you structure the commission_types table to store the tiers ? I don't find how to store all needed data (for fixed, percentage, tier, sliding, ...) in a unique table structure.

Tray2's avatar

@vincent15000 You mean like, if a seller for example has sold for $10000 then they would get 3% but if they sold for $15000 they would get 3.5%?

If so you add criteria for each level in nullable fields the same goes for fixed, just a field for it that is nullable.

2 likes
vincent15000's avatar

@Tray2 I don't see how to do that. Perhaps I don't understand what you exactly suggests me.

Tray2's avatar

@vincent15000 The table structure would be something like

  • type (ranged, fixed ,or other)
  • percent
  • fixed_amount
  • range_start
  • range_stop
  • valid_from
  • valid_to
2 likes
vincent15000's avatar

@Tray2 Hmmm yes interesting ... but what if in the future there is a new commission type which needs a new fields, I will have to change the table structure (add a field).

Tray2's avatar

@vincent15000 There is always the option to make it more generic, and use another table for it's properties. Kinda like you can can have different properties for products in a store.

I wrote a little about it in this post https://tray2.se/posts/database-design-part-2 look for the One-to-many With Different Properties

1 like
jlrdw's avatar

@vincent15000 Also you could use some getters and setters for this. In conjunction with using the database.

1 like
vincent15000's avatar

@jlrdw Hmmm ... I don't understand what you mean ... getters and setters ok, but why ? Do you have an example ?

@tray2 @jlrdw All what you suggest me is fine and useful. I just reformulate my first question : what is best between : duplicate all commission rules values (for example if a seller has the same values for all products, duplicate the values for each product) ? or store the value in one table and apply the value to the seller / product pair ? I think that the first solution is more easy to update.

Tray2's avatar

@vincent15000 Single source of truth is what you should aim for as much as possible.

When it comes to products one way to do it would be to create product families and add the comission to them, then if there is no product family it is valid for all products.

1 like
martinbean's avatar

@vincent15000 Take a look at Stripe’s API. They have modelled some pretty complicated scenarios (particularly around usage-based billing and billing meters) where they won’t have resorted to JSON but came up with nomenclature around such problems. You may be able to extract some ideas from how they’ve approached modelling and naming things.

1 like
vincent15000's avatar

@tray2 @jlrdw @martinbean

I have thought about your suggestions and I have this idea.

I have these informations to store.

RULES :

- valid_from
- valid_to
- fixed value
- percentage value
- unique spiff value
- tiers tier
- tiers fixed
- tiers percentage
- tiers unique spiff
- targets target
- targets fixed
- targets percentage
- targets unique spiff
- products fixed
- products percentage
- products spiff
- pyramidal users
- pyramidal percentage
- user

Can become this.

RULES :

- name
- valid_from
- valid_to
- user_id

TYPES : (enum ?)

- fixed => simple commission type
- percentage => simple commission type
- spiff => simple commission type
- unique_spiff => simple commission type
- tiers => complex commission type (array of simple ones)
- targets => simple commission type (array of simple ones)

VALUES :

- id
- valid_from ?
- valid_to ?
- type
- renew (true / false) ?
- value (json)
- product_source_id
- per_product (true / false)
- rule_id
- value_id // IF NEEDED WITH COMPLEX COMMISSION TYPES LIKE TIERS

The only problem is with tiers commission type and targets one too. Because in tiers commission type, I reuse simple types like fixed or percentage commission types.

- valid_from
- valid_to
- type : tiers
- parameters : [ { tier: { value: 1000, [ { type: fixed, parameters: { value: 20 } }, { type: percentage, parameters: { value: 5 } } ] } },  { tier: { value: 2000, [ { type: fixed, parameters: { value: 20 } }, { type: percentage, parameters: { value: 6 } } ] } } ]
- product_source_id : null
- per_product : false

So is it a good idea to add a value_id to the values table in order to refer to a parent values row ?

Please or to participate in this conversation.