Database schema - Configurable fields? - mysql

I sell leads and charge my clients like so:
(Only one type of payment from the followings can be charged from a client)
Pay Per Lead:
$__ for the first __ leads per month
$__ for the next __ leads per month
$__ for the next __ leads per month
and so on...
Pay per Appointment:
$__ for the first __ leads per month
$__ for the next __ leads per month
$ __ for the next __ leads per month
and so on...
Pay per Percentage of Sale:
__% of the sale price (per sale)
My Question:
What are the best possible database design solutions in such cases?
What i have tried:
+---------+
| clients |
+---------+
| id |
| name |
+---------+
+---------------+
| deals |
+---------------+
| client_id |
| max_quantity |
| cost |
| unit_type |
+---------------+
So records for client with the id 1 might look like:
+-----------+--------------+---------------+-------------+
| client_id | max_quantity | cost_per_unit | unit_type |
+-----------+--------------+---------------+-------------+
| 1 | 10 | 10 | lead |
| 1 | 30 | 5 | lead |
| 1 | 100 | 2 | lead |
| 1 | 10 | 35 | appointment |
| 1 | 30 | 20 | appointment |
| 1 | 100 | 10 | appointment |
| 1 | 1000 | 5 | appointment |
| 1 | 0 | 50 | sale |
+-----------+--------------+---------------+-------------+
Now the above table means that:
$10 will be charged per lead upto 10 leads
$5 will be charged per lead upto 30 leads
$2 will be charged per lead upto 100 leads
$35 will be charged per appointment upto 10 leads
$20 will be charged per appointment upto 30 leads
$10 will be charged per appointment upto 100 leads
$5 will be charged per appointment upto 1000 leads
$50 will be charged per sale
Also i want to add x number of such rules (per lead, per appointment, per sale)
I personally don't think that my approach is one of the best solutions. Looking forward to hear for you cleaver folks! Thank you.
P.S. I know that unit_type can be further normalized but this is not the issue :)
Update
Maybe i can store serialized data?

Your proposed schema is a good start and has some merits. IMO the less elegant parts are the denormalized repetition of unit_type values and non-functional max_quantity value for sale.
Would suggest splitting deals into three tables rather than one. Would personally go with singular rather than plural table names** and begin with the same prefix so they are listed close to each other: Something like commission_lead, commission_appointment and commission_sale.
** [Lots of debate on this here]
Would also suggest including both lower and upper bands in each row. This does use more data than is strictly needed but think it is worth doing as it should make the table data more readable and simplify the calculation queries.
So the proposed new schema is:
+---------+
| client |
+---------+
| id |
| name |
+---------+
+-----------------+
| commission_lead |
+-----------------+
| client_id |
| min_quantity |
| max_quantity |
| cost_per_unit |
+-----------------+
+------------------------+
| commission_appointment |
+------------------------+
| client_id |
| min_quantity |
| max_quantity |
| cost_per_unit |
+------------------------+
+-----------------+
| commission_sale |
+-----------------+
| client_id |
| cost_per_unit |
+-----------------+
And the records for client_id = 1 are:
commission_lead
+-----------+--------------+--------------+---------------+
| client_id | min_quantity | max_quantity | cost_per_unit |
+-----------+--------------+--------------+---------------+
| 1 | 0 | 10 | 10 |
| 1 | 11 | 30 | 5 |
| 1 | 31 | 100 | 2 |
+-----------+--------------+--------------+---------------+
commission_appointment
+-----------+--------------+--------------+---------------+
| client_id | min_quantity | max_quantity | cost_per_unit |
+-----------+--------------+--------------+---------------+
| 1 | 0 | 10 | 35 |
| 1 | 11 | 30 | 20 |
| 1 | 31 | 100 | 10 |
| 1 | 101 | 1000 | 5 |
+-----------+--------------+--------------+---------------+
commission_sale
+-----------+---------------+
| client_id | cost_per_unit |
+-----------+---------------+
| 1 | 50 |
+-----------+---------------+

I make an assumption that the change is very rare (update/insert), most of the time you use select to calculate the cost, so I propose this design, the select to calculate cost is very simple
+-----------+--------------+---------------+---------------+--------------+------------+
| client_id | max_quantity | min_quantity | cost_per_unit | default_cost | unit_type |
+-----------+--------------+---------------+---------------+--------------+------------+
| 1 | 10 | 0 | 10 | 0 | lead|
| 1 | 40 | 10 | 5 | 100 | lead|
| 1 | 140 | 40 | 2 | 250 | lead|
| 1 | 10 | 0 | 35 | 0 | appointment|
| 1 | 40 | 10 | 20 | 350 | appointment|
| 1 | 140 | 40 | 10 | 950 | appointment|
| 1 | 1140 | 140 | 5 | 1950 | appointment|
| 1 | 0 | 0 | 50 | 0 | sale|
+-----------+--------------+---------------+---------------+--------------+------------+
select query looks like
select
default_cost + ($quantity - min_quantity) * cost_per_unit
from
table
where
unit_type = $unit_type
and (max_quantity >= $quantity or max_quantity = 0)
and $quantity >= min_quantity

IF you consider the cost calculations business logic that is likely to change in the future AND you dont need to filter/sort the table based on the calculation constants, I recommend having one column for rule_id, that pretty much works like your unit_type, and one varchar column called properties where all the specific values needed for that rule is stored with a separator.
You then retrieve the rules that apply for your client to your business logic and do your calculations there. If you need a new rule that suddenly takes 5 parameters, you don't need to change the database schema. Simply write code for a new rule_id in your business logic and you are good to go.
Of course, if you prefer to move calculation logic into stored procedures and/or need to filter or order by rule properties, I think you should go with separate columns for each rule parameter...

Related

Precalculate numbers of records for each possible combination

I have a mySQL database table containing cellphones information like this:
ID Brand Model Price Type Size
==== ===== ===== ===== ====== ====
1 Apple A71 3128 A 40
2 Samsung B7C 3128 B 20
3 Apple ZX5 3128 A 30
4 Huawei Q32 2574 B 40
5 Apple A21 2574 A 25
6 Apple A71 3369 A 30
7 Samsung A71 7413 C 40
Now I want to create another table, that would contain counts for every possible combination of the parameters.
Params Count
============================================== =======
ALL 1000000
Brand(Apple) 20000
Brand(Apple,Samsung) 40000
Brand(Apple),Model(A71) 7100
Brand(Apple),Type(A) 6000
Brand(Apple),Model(A71,B7C),Type(A,B) 7
Model(A71) 12514
Model(A71,B7C) 26584
Model(A71),Type(A) 6521
Model(A71),Type(A,B) 8958
Model(A71),Type(A,B),Size(40) 85
And so on for every possible combination. I was thinking about creating a stored procedure (that i would execute periodically), that would perform queries with every existing condition like that, but I am a little stuck on how exactly should it look like. Or is there a better way how to do this?
Edit: the reason why I want to store information like this is to be able to show number of results in filter in client application, like in the picture.
I would like to create index on the Params column to be able to get the Count number for given hash instantly, improving performance.
I also tried querying and caching the values dynamically, but I want to try this approach as well, so I can compare which one is more effective.
This is how I am calculating the counts now:
SELECT COUNT(*) FROM products;
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple');
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple', 'Samsung');
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple') AND Model IN ('A71');
etc.
You can use a ROLLUP for this.
SELECT
model, type, size, COUNT(*)
FROM mytab
GROUP BY 1, 2, 3
WITH ROLLUP
With your sample data, we get the following:
| model | type | size | COUNT(*) |
| ----- | ---- | ---- | -------- |
| A21 | A | 25 | 1 |
| A21 | A | | 1 |
| A21 | | | 1 |
| A71 | A | 30 | 1 |
| A71 | A | 40 | 1 |
| A71 | A | | 2 |
| A71 | C | 40 | 1 |
| A71 | C | | 1 |
| A71 | | | 3 |
| B7C | B | 20 | 1 |
| B7C | B | | 1 |
| B7C | | | 1 |
| Q32 | B | 40 | 1 |
| Q32 | B | | 1 |
| Q32 | | | 1 |
| ZX5 | A | 30 | 1 |
| ZX5 | A | | 1 |
| ZX5 | | | 1 |
| | | | 7 |
The subtotals are present in the rows with null values in different columns, and the total is the last row where all group by columns are null.

query between 3 Mysql tables per time range

I am working on a small database that records rentals of different properties for different periods of time to different clients. I leave a summary example of the structures of my tables below.
+--------------+
| customer |
|--------------|
| id |
| name |
+--------------+
+--------------+
| property |
|--------------|
| id |
| number |
| address |
+--------------+
+--------------+
| rental |
|--------------|
| id |
| date_init |
| date_end |
| month_payment|
| customer_id |
| property_id |
+--------------+
What I am trying to find out now through a consultation is the following: in my rental table I keep the client, property and amount that I agree to cancel each month for the rental, so there are clients who rent different properties, during the year. How can I know how much money my clients have generated during a certain period of time, for example if I have the following records:
customer
+--------------+
| id | name |
|--------------|
| 1 | jhon doe|
| 2 | alex gs |
| 3 | martha |
+--------------+
property
+------------------------------------+
| id | number | address |
--------------------------------------
| 1 | 5643 | chicago |
| 2 | 1023 | toronto |
| 3 | 3445 | atlanta |
+------------------------------------+
rental
+-------------------------------------------------------------------+
| id | customer_id | property_id | date_init | date_end | amount |
---------------------------------------------------------------------
| 1 | 1 | 1 | 2019-01-05 | 2019-06-05 |3000 |
| 2 | 2 | 2 | 2019-04-10 | 2019-10-10 |1800 |
| 3 | 1 | 3 | 2019-02-14 | 2019-11-14 |1000 |
+-------------------------------------------------------------------+
then given as a parameter a period of time for example: 2019-01-01 to 2019-12-30 get only the records that match and have the following result:
+---------------------+
| customer | total |
|----------------------
| jhon doe | 24,000 |
| alex gs | 10,800 |
+---------------------+
In this case, the John Doe client has rented 2 properties the first for 5 months for an amount of 3000 total of 15000 and the other property for 9 months to 1000 total of 9000, so is it possible to make a query with this type of data? I don't have a query as an example yet, since I don't know how to deal with this problem. I am working on it, as soon as I have something I will update my question, thank you!
edited--
You need to use SUM() and a JOIN then specify the fields you want returned.
SELECT c.name AS customer, SUM(r.amount) AS total
FROM rentals r
INNER JOIN customer c ON c.id = r.customer_id
WHERE r.date_end >= $START AND r.date_end <= $END
GROUP BY r.customer_id
http://www.sqlservertutorial.net/sql-server-aggregate-functions/sql-server-sum/

How can I structure my database to order by a function dependent on time efficiently in MySQL?

I have a table with ID's and KPI's like this
+----+---------------------+------------+--------+
| id | created_at | page_views | shares |
+----+---------------------+------------+--------+
| 1 | 2015-02-25 07:24:50 | 10 | 3 |
| 2 | 2015-04-22 13:48:46 | 40 | 1 |
| 3 | 2014-09-17 15:26:51 | 28 | 5 |
| 4 | 2014-08-09 16:27:48 | 149 | 2 |
| 5 | 2015-01-21 15:56:30 | 3 | 1 |
+----+---------------------+------------+--------+
I want to make a ranking algorithm along the lines of
SELECT id FROM pages ORDER BY (pages.page_views + pages.shares * 40) / (NOW() - created_at);
But I feel like this isn't scalable with increasing number of KPIs and entrees.
Is there a way to structure my table to efficiently rank using KPIs with decreasing score as time goes on?
I would add a pageranking column and update it regularly (daily?) using a batch job.
This will provide good search performance at the cost of using slightly out-of-date page rankings.

How do I write in a single query two different sum queries with different conditions?

I have a MySQL datatable items as below:
+----+-------+----------+----------+
| id | value | discount | type |
+----+-------+----------+----------+
| 1 | 20 | 1 | hardware |
| 2 | 40 | 0 | hardware |
| 3 | 60 | 1 | software |
| 4 | 30 | 1 | software |
+----+-------+----------+----------+
When discount is 1, this means that effectively the value is ZERO.
I want to get back the following results
+----------+----+
| software | 0 |
| hardware | 40 |
+----------+----+
I know how to do this in multiple queries.
SELECT type, SUM(value) from items where discount != 1 group by type
which gives me just
+----------+----+
| hardware | 40 |
+----------+----+
and then
SELECT type, 0 from items where discount = 1 group by type
which gives me
+----------+----+
| software | 0 |
| hardware | 0 |
+----------+----+
then i need to join these two tables to get the final result.
My question is :
is there a way I can accomplish the same result with just 1 query?
Thank you.
I think this is what you want
SELECT type, SUM(if(discount =1, 0,value)) from items group by type

Mysql how to find cumulative total by group

Can anyone help me to sort this out pleaase. i have a episode table and for an episode there will be following appointments . Episode table will be like
+-------------+------------+------------+------------+----------------+------+
| Episode_id | Patientid | St_date | End_date | Status | ... |
+-------------+------------+------------+------------+----------------+------+
| 61112345 | 100001 | 12-01-2010 | | Active | |
| 61112346 | xxxxxx | 20-01-2010 | 10-10-2011 | Withdrawn | |
| ......... | xxxxxxxx | 30-01-2010 | 10-05-2011 | Lost to follow | |
| ......... | xxxxxxxx | 01-02-2011 | Active | Active | |
+-------------+------------+------------+------------+----------------+------+
Status field holds the status of each episode.A episode has 6 appointments , 3 months per appointment. so totally an episode has 18 months . some patient may complete all 6 appointment , some may withdraw in the middle, or some will be lost to follow up. i need to create a dashboard .
Appointment table will have fields for
Appointment_id
PatientId
...
Stats // Completed or pending, which is used for reporting
For example if a patient complete 2 appointment and if he is marked as Withdrawn on episdode which means that he has withdrawn from 3rd visit and active for 2 visits, if we lost to follow him on 5th app, then he will be active for 4app and then he will be added to lost to follow up on 5th visit. if he completes all then he will added to active for all 6 visits. and the report should be like
Report from 01-01-2010 to 31-12-2010
+--------+--------+-------------+----------------+---------+
| | Active | Withdrawn | Lost to follow | Revised |
+------- +--------+-------------+----------------+---------+
| visit1 | 1500 | 30 | 5 | 5 |
| Visit2 | 1800 | 20 | 4 | 3 |
| Visit3 | 1900 | 45 | 3 | 2 |
| Visit4 | 1800 | 34 | 0 | 1 |
| Visit5 | 1900 | 30 | 0 | 1 |
| Visit6 | 1200 | 20 | 0 | 5 |
+--------+--------+-------------+----------------+---------+
Currently we are fetching the query and using loop only we are generating reports like this, but it is taking time to process, is there any way i can achieve using query itself.
It isn't really clear what you want to group by, but I can give you a general answer. After your where clause you can add "group by fieldname order by fieldname" where fieldname is the element you want to count or sum. You can then count(fieldname) or sum(fieldname) to either add or count.
This may be helpful: http://www.artfulsoftware.com/infotree/qrytip.php?id=105