如何改进这种无模式的数据库模式?
How to improve this patternless database schema?
在进入模式和 tables 之前,我想先分享一下我想要实现的目标。我正在开发一种快递应用程序,其中我有一些 categories
并且每个类别都有一个预定义的 price
.
但是,确定价格有点难看(缺乏对称性和模式;至少,我似乎找不到)。我给你举个例子:
考虑以下类别:文档、重型文档、笔记本电脑、纸箱、重型纸箱。
1) 文件: 适用于0.5公斤以下的较轻文件。价格是20$,固定。
[价格存储在价格table:20.00]
e.g. For an item of 300g, the price will be 20$.
2) 超重文件: 适用于超过 0.5 公斤的文件。与文档类别不同,它没有固定价格!相反,它有一个单价:每公斤 10 美元,这将适用于每公斤的 except/after 0.5 公斤。
[价格存储在价格table:10.00]
e.g. For an item of 2kg, the price will be 35$ (1.5g = 15$ + 0.5 = 20$)
3) 笔记本电脑: 简单明了,100 美元。没什么特别的,没有任何限制。
[价格存储在价格table:100.00]
e.g. For an item of 2kg, the price will be 35$ (1.5g = 15$ + 0.5 = 20$)
4) Carton: 有趣的来了。到目前为止,只有一个依赖项:weight
。但是这个有一个额外的依赖:dimension
。这有点类似于Document类。对于小于 3 立方英尺 (CF) 的纸箱,价格为每立方英尺 80 美元。 Document 和 Carton 类别的区别在于,Document 有固定价格,而 Carton 有单价。但是等等,还有更多。还有一个额外的限制:尺寸重量比。在这种情况下,它是 7kg per CF
。如果物品的重量超过该比例,则每多出一公斤将收取 5 美元的费用。这太令人困惑了,我知道。一个例子可能会有所帮助:
[价格存储在价格table:80.00]
e.g. For a carton of 80kg and 2CF; the price will be 490$. Here is how:
先算出常规费用:80$*2CF = 160$
现在让我们看看它是否超过 Ratio:因为,1 CF = 7kg,因此,2CF = 14kg。但是物品的重量是80kg,所以超过了比例(14kg)
因为超过了这个比例,所有多出来的公斤数(80-14 = 66公斤),每公斤的价格是5$:66*5 = 330$。加上正常费用后:330$+160$ = 490$.
5) 重型纸箱: 此款适用于尺寸大于 3CF 的纸箱。与Carton的区别在于单价。重型纸箱每 CF 60 美元。
[价格存储在价格table:60.00]
e.g. For a carton of 80kg and 5CF; the price will be 525$. Here is how:
首先计算常规费用:60$*5CF = 300$
现在让我们计算它是否超过 Ratio:因为,1 CF = 7kg,因此,5CF = 35kg。但是物品的重量是80kg,所以超过了比例(35kg)
因为超过了这个比例,所有多出来的公斤数(80-35 = 45公斤),每公斤的价格是5$:45*5 = 225$。添加后按常规收费:300$+225$ = 325$.
如果您已经读到这里,我想我已经让您相信业务结构真的很复杂。现在让我们来看看我的 categories
架构:
+-------------------------+---------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------------+---------------------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(191) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| dim_dependency | tinyint(1) | NO | | NULL | |
| weight_dependency | tinyint(1) | NO | | NULL | |
| distance_dependency | tinyint(1) | NO | | NULL | |
| dim_weight_ratio | varchar(191) | YES | | NULL | |
| constraint_value | decimal(8,2) | YES | | NULL | |
| constraint_on | enum('weight','dim') | YES | | NULL | |
| size | enum('short','regular','large') | YES | | regular | |
| over_ratio_price_per_kg | decimal(8,2) | YES | | NULL | |
| deleted_at | timestamp | YES | | NULL | |
+-------------------------+---------------------------------+------+-----+---------+----------------+
还有prices
table的schema(是多态的table,希望哪天能搞出一个subcategories
table):
+----------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| amount | decimal(8,2) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| priceable_type | varchar(191) | NO | MUL | NULL | |
| priceable_id | bigint(20) unsigned | NO | | NULL | |
| deleted_at | timestamp | YES | | NULL | |
+----------------+---------------------+------+-----+---------+----------------+
我如何改进这种结构以尽可能保持动态和连贯性?
因此,如果我要发送 10kg/2.99cf
包裹,您将向我收取 240$
(3*80$
) 的纸箱费用。如果我把那个包裹放在一个稍微大一点的盒子里,现在想把它作为一个 10kg/3.01cf
包裹发送,你会向我收取 180$
(3*60$
) 的重型纸箱费用。如果您四舍五入到下一个完整的 cf
,我们假设我想发送一个 80kg/3CF
包裹;你向我收费 535$
(3*80+59*5
)。如果我把同一个包裹放在一个更大的盒子里 80kg/4CF
,你只收我 500$
(4*60+52*5
).
这确实是一个好兆头"that the business structure is really complicated"(即使这些可能只是示例值,它显示了使事情过于复杂的可能性)。
无论如何,我可能会将您的条件编码成 table 这样的:
category |max_kg|max_cf|is_laptop|price|p_p_kg|p_p_cf|off_kg|off_cf|off_rat
---------+------+------+---------+-----+------+------+------+------+--------
Document | 0.5 | null | 0 | 20 | 0 | 0 | 0 | 0 | 0
Heavy Doc| 2 | null | 0 | 20 | 10 | 0 | 0.5 | 0 | 0
Laptop | null | null | 1 | 100 | 0 | 0 | 0 | 0 | 0
Carton | null | 3 | 0 | 0 | 5 | 80 | 0 | 0 | 7
Heavy C. | null | null | 0 | 180 | 5 | 60 | 0 | 3 | 7
文档可能也有一些大小限制(例如,我可以将我的 0.0kg/100cf
充氦气球作为文档发送吗?),但您没有指定它们;以这种方式列出条件应该使您在遇到不明确条件时一目了然。
off_*
指定偏移量,例如已包含在 price
中的金额; p_p_kg
是剩余重量的 每公斤价格 (减去偏移量),类似于 p_p_cf
。因此 80kg/4CF
的重纸箱将计算为
price -- 180
+ p_p_kg * greatest(kg - off_rat * cf - off_kg, 0) -- 5 * (80-7*4-0)
+ p_p_cf * greatest(cf - off_cf, 0) -- 60 * (4 - 3)
所以,正如预期的那样,180 + 5 * 52 + 60 = 500
。
用户不会来你店里说"I want to send this as a Heavy Carton"。他会说:"What does it cost me to send something that weights 80 kg, has 3 cf and is no laptop." 如果重型纸箱更便宜,他可能会希望您不要将其作为纸箱发送。
所以你接受这个输入(以及任何其他相关输入,如距离)并使用
之类的东西检查满足条件的所有行
select (your price formula depending on input) as cost
...
where (max_kg is null or max_kg >= 80)
and (max_cf is null or max_cf >= 3)
and (is_laptop is null or is_laptop = 0)
order by cost
您可能应该在一个地方定义它,这样更容易添加其他条件(例如差异)和其他未在您的 table 中定义的规范(例如四舍五入到完整 cf
或步骤0.1).
您可能还需要 table 附加服务,例如快递或隔夜送达、超过 500 美元的包裹保险、固定时间送达或类似服务。
您提到了子类别和 "polymorphic price table",但不清楚您要用它做什么。如果你有一些具体的例子不能用这样的矩阵 table 来表达,请添加它们。但您也应该意识到,简单为王,对您和客户都是如此。如果我认为如果你的竞争对手为 200$
收取我的 10kg/2.99cf
纸箱费用,你可能已经失去我了,即使你实际上只是向我收取 180$
对于重型纸箱。
在进入模式和 tables 之前,我想先分享一下我想要实现的目标。我正在开发一种快递应用程序,其中我有一些 categories
并且每个类别都有一个预定义的 price
.
但是,确定价格有点难看(缺乏对称性和模式;至少,我似乎找不到)。我给你举个例子:
考虑以下类别:文档、重型文档、笔记本电脑、纸箱、重型纸箱。
1) 文件: 适用于0.5公斤以下的较轻文件。价格是20$,固定。
[价格存储在价格table:20.00]
e.g. For an item of 300g, the price will be 20$.
2) 超重文件: 适用于超过 0.5 公斤的文件。与文档类别不同,它没有固定价格!相反,它有一个单价:每公斤 10 美元,这将适用于每公斤的 except/after 0.5 公斤。
[价格存储在价格table:10.00]
e.g. For an item of 2kg, the price will be 35$ (1.5g = 15$ + 0.5 = 20$)
3) 笔记本电脑: 简单明了,100 美元。没什么特别的,没有任何限制。
[价格存储在价格table:100.00]
e.g. For an item of 2kg, the price will be 35$ (1.5g = 15$ + 0.5 = 20$)
4) Carton: 有趣的来了。到目前为止,只有一个依赖项:weight
。但是这个有一个额外的依赖:dimension
。这有点类似于Document类。对于小于 3 立方英尺 (CF) 的纸箱,价格为每立方英尺 80 美元。 Document 和 Carton 类别的区别在于,Document 有固定价格,而 Carton 有单价。但是等等,还有更多。还有一个额外的限制:尺寸重量比。在这种情况下,它是 7kg per CF
。如果物品的重量超过该比例,则每多出一公斤将收取 5 美元的费用。这太令人困惑了,我知道。一个例子可能会有所帮助:
[价格存储在价格table:80.00]
e.g. For a carton of 80kg and 2CF; the price will be 490$. Here is how:
先算出常规费用:80$*2CF = 160$ 现在让我们看看它是否超过 Ratio:因为,1 CF = 7kg,因此,2CF = 14kg。但是物品的重量是80kg,所以超过了比例(14kg)
因为超过了这个比例,所有多出来的公斤数(80-14 = 66公斤),每公斤的价格是5$:66*5 = 330$。加上正常费用后:330$+160$ = 490$.
5) 重型纸箱: 此款适用于尺寸大于 3CF 的纸箱。与Carton的区别在于单价。重型纸箱每 CF 60 美元。
[价格存储在价格table:60.00]
e.g. For a carton of 80kg and 5CF; the price will be 525$. Here is how:
首先计算常规费用:60$*5CF = 300$ 现在让我们计算它是否超过 Ratio:因为,1 CF = 7kg,因此,5CF = 35kg。但是物品的重量是80kg,所以超过了比例(35kg)
因为超过了这个比例,所有多出来的公斤数(80-35 = 45公斤),每公斤的价格是5$:45*5 = 225$。添加后按常规收费:300$+225$ = 325$.
如果您已经读到这里,我想我已经让您相信业务结构真的很复杂。现在让我们来看看我的 categories
架构:
+-------------------------+---------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------------+---------------------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(191) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| dim_dependency | tinyint(1) | NO | | NULL | |
| weight_dependency | tinyint(1) | NO | | NULL | |
| distance_dependency | tinyint(1) | NO | | NULL | |
| dim_weight_ratio | varchar(191) | YES | | NULL | |
| constraint_value | decimal(8,2) | YES | | NULL | |
| constraint_on | enum('weight','dim') | YES | | NULL | |
| size | enum('short','regular','large') | YES | | regular | |
| over_ratio_price_per_kg | decimal(8,2) | YES | | NULL | |
| deleted_at | timestamp | YES | | NULL | |
+-------------------------+---------------------------------+------+-----+---------+----------------+
还有prices
table的schema(是多态的table,希望哪天能搞出一个subcategories
table):
+----------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| amount | decimal(8,2) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| priceable_type | varchar(191) | NO | MUL | NULL | |
| priceable_id | bigint(20) unsigned | NO | | NULL | |
| deleted_at | timestamp | YES | | NULL | |
+----------------+---------------------+------+-----+---------+----------------+
我如何改进这种结构以尽可能保持动态和连贯性?
因此,如果我要发送 10kg/2.99cf
包裹,您将向我收取 240$
(3*80$
) 的纸箱费用。如果我把那个包裹放在一个稍微大一点的盒子里,现在想把它作为一个 10kg/3.01cf
包裹发送,你会向我收取 180$
(3*60$
) 的重型纸箱费用。如果您四舍五入到下一个完整的 cf
,我们假设我想发送一个 80kg/3CF
包裹;你向我收费 535$
(3*80+59*5
)。如果我把同一个包裹放在一个更大的盒子里 80kg/4CF
,你只收我 500$
(4*60+52*5
).
这确实是一个好兆头"that the business structure is really complicated"(即使这些可能只是示例值,它显示了使事情过于复杂的可能性)。
无论如何,我可能会将您的条件编码成 table 这样的:
category |max_kg|max_cf|is_laptop|price|p_p_kg|p_p_cf|off_kg|off_cf|off_rat
---------+------+------+---------+-----+------+------+------+------+--------
Document | 0.5 | null | 0 | 20 | 0 | 0 | 0 | 0 | 0
Heavy Doc| 2 | null | 0 | 20 | 10 | 0 | 0.5 | 0 | 0
Laptop | null | null | 1 | 100 | 0 | 0 | 0 | 0 | 0
Carton | null | 3 | 0 | 0 | 5 | 80 | 0 | 0 | 7
Heavy C. | null | null | 0 | 180 | 5 | 60 | 0 | 3 | 7
文档可能也有一些大小限制(例如,我可以将我的 0.0kg/100cf
充氦气球作为文档发送吗?),但您没有指定它们;以这种方式列出条件应该使您在遇到不明确条件时一目了然。
off_*
指定偏移量,例如已包含在 price
中的金额; p_p_kg
是剩余重量的 每公斤价格 (减去偏移量),类似于 p_p_cf
。因此 80kg/4CF
的重纸箱将计算为
price -- 180
+ p_p_kg * greatest(kg - off_rat * cf - off_kg, 0) -- 5 * (80-7*4-0)
+ p_p_cf * greatest(cf - off_cf, 0) -- 60 * (4 - 3)
所以,正如预期的那样,180 + 5 * 52 + 60 = 500
。
用户不会来你店里说"I want to send this as a Heavy Carton"。他会说:"What does it cost me to send something that weights 80 kg, has 3 cf and is no laptop." 如果重型纸箱更便宜,他可能会希望您不要将其作为纸箱发送。
所以你接受这个输入(以及任何其他相关输入,如距离)并使用
之类的东西检查满足条件的所有行select (your price formula depending on input) as cost
...
where (max_kg is null or max_kg >= 80)
and (max_cf is null or max_cf >= 3)
and (is_laptop is null or is_laptop = 0)
order by cost
您可能应该在一个地方定义它,这样更容易添加其他条件(例如差异)和其他未在您的 table 中定义的规范(例如四舍五入到完整 cf
或步骤0.1).
您可能还需要 table 附加服务,例如快递或隔夜送达、超过 500 美元的包裹保险、固定时间送达或类似服务。
您提到了子类别和 "polymorphic price table",但不清楚您要用它做什么。如果你有一些具体的例子不能用这样的矩阵 table 来表达,请添加它们。但您也应该意识到,简单为王,对您和客户都是如此。如果我认为如果你的竞争对手为 200$
收取我的 10kg/2.99cf
纸箱费用,你可能已经失去我了,即使你实际上只是向我收取 180$
对于重型纸箱。