Storing time pricing information

Posted on

Question :

I have an application for a business customer and one of the new components is a time tracking system. The actual time tracking is done (pretty standard schema), but the structure this company uses to price their time is really odd and I don’t know how to properly store this.

0-10 minutes: €16
10-15 minutes: €23

35-40 minutes: €58
more minutes: €63

In short, it’s a completely irregular structure and while I could make a table with a column per interval, that seems to me like a smelly solution. A second table with a foreign key and interval and price also doesn’t sound ideal. There should also be multiple of these timetables (different per employee / role).

Does anyone have any advice on storing this?

Answer :

Store it in a way that makes sense, which is to say ignore the interval approach and just store the breakpoints like such:

Role        | MaxMinuteAmt      | Cost
Plumber     | 10                | 16
Plumber     | 15                | 23
Plumber     | 40                | 58
Plumber     | 32767             | 63

The primary key for this table is (Role,MaxMinuteAmt). This prevents any data integrity issues with improperly entered intervals and assists in the quick lookup of the appropriate value.

To return the appropriate Cost, you would use the following query (which can be hardened into a stored procedure):

SELECT
  EmpRoleCost.Cost
FROM
  EmployeeRoleCost EmpRoleCost
WHERE
  EmpRoleCost.Role = 'Plumber'
    AND EmpRoleCost.MaxMinuteAmt =
      (
        SELECT
          MIN(MaxMinuteAmt)
        FROM
          EmployeeRoleCost
        WHERE
          Role = EmpRoleCost.Role
            AND MaxMinuteAmt >= <Number of Minutes>
      )

So if we query with a duration of 1 minute, the minimum value greater than or equal to that value is 10 and so on. Anything over 40 minutes up to the maximum SMALLINT value would return the same value (you could go with a larger datatype if needed).

This is assuming that anything up to 32,767 minutes would be charged €63, but my guess is there’s probably some fixed rate once you get above a certain value (say 60 minutes). In that case you’d change the above table to:

Role        | MaxMinuteAmt      | Cost
Plumber     | 10                | 16
Plumber     | 15                | 23
Plumber     | 40                | 58
Plumber     | 60                | 63

And create a second table to store the rate for amounts over, along with the duration of the rate (let’s say 15 minutes for this example):

Role        | MinuteAmt     | Rate
Plumber     | 15            | 15.75

Then we’d change the query to utilize that rate if no flat cost is found:

SELECT
  COALESCE
    (
      EmpRoleCost.Cost
     ,EmpOverage.Rate * CEILING(<Actual number of minutes>/EmpOverage.MinuteAmt)
    ) AS Cost
FROM
  EmployeeRole EmployeeRole
LEFT JOIN
  EmployeeRoleOverageRate EmpOverage
    ON EmpOverage.Role = EmployeeRole.Role
LEFT JOIN
  EmployeeRoleCost EmpRoleCost
    ON EmpRoleCost.Role = EmployeeRole.Role
        AND EmpRoleCost.MaxMinuteAmt =
          (
            SELECT
              MIN(MaxMinuteAmt)
            FROM
              EmployeeRoleCost
            WHERE
              Role = EmployeeRole.Role
                AND MaxMinuteAmt >= <Number of Minutes>
          )
WHERE
  EmployeeRole.Role = 'Plumber'

So in this case if the number of minutes was 65, the cost would be €78.75 (CEILING(65/15) * 15.75).

See this Fiddle.

Leave a Reply

Your email address will not be published. Required fields are marked *