如何改进这种无模式的数据库模式?

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    |                |
+-------------------------+---------------------------------+------+-----+---------+----------------+

还有pricestable的schema(是多态的table,希望哪天能搞出一个subcategoriestable):

+----------------+---------------------+------+-----+---------+----------------+
| 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$对于重型纸箱。