在设计糟糕的数据库中处理不相关的记录

时间:2017-03-27 03:17:50

标签: data-structures database-design

概述

我继承了一个允许用户订购定制产品的网站。这些定制的保存方式使他们与记录无关。我想修改数据库,以便可以关联这些记录。

实施例

用户可以获得产品#1“库存”,或自定义它,更改多达10个不同的属性。让我们说颜色,面料,宽度,高度等。

订单可以并且经常包含多个产品,每个产品都可以定制。

此外,用户可以保存他们的订单,以便他们以后可以重新订购。

设计数据库时,订单的详细信息整齐地组织到各个列中。客户名称,地址,付款类型等。但产品列表以及更明显的 customisations 在一列中保存为JSON字符串。为方便起见,我们将此列称为“购物车”。

基本上,order表格中有一列cart,而cart列包含一个JSON格式的产品和自定义列表。

不幸的是,JSON对象具有product表的引用ID,但缺少对customisation表的引用。相反,它使用一堆字符串供人阅读。幸运的是,这些字符串存在于customisation表中,但它们是在创建购物车时编写的。

我们面临的问题是CMS可以更改自定义列表。到目前为止,他们还没有改变。但他们需要很快,这将导致问题:

问题

  1. 如果删除了自定义选项(例如,结构选项)并且客户从旧保存的订单中重新订购,我们需要能够解析购物车,检测到并警告他们更改。

  2. 自定义目前是不可变的。将产品添加到购物车后,无法更改。用户需要删除并重新添加才能进行单个更改。可怜的用户体验。

  3. 如果有人更改customisation上的人类可读文字,我们就死定了。 ☠️

  4. 问题

    • 如果你从头开始盯着,你会如何设计?

    • 我们如何将当前的和旧数据转换为此新架构?

    我不知道堆栈是否值得注意,但我们使用的是Postgres和Django-Python。

2 个答案:

答案 0 :(得分:4)

我将使用以下表格实现此目的:

Products {
  productId                   // primary key
  name
  price
}

Customization_Types {
  customizationTypeId         // primary key
  name                        // e.g. COLOR, FABRIC, LENGTH
}

Customizations {
  customizationId             // primary key
  customizationTypeId         // foreign key
  value                       // e.g. BEIGE, VELVET, 8
}

Product_Customizations {
  productCustomizationId      // primary key
  productId                   // foreign key
  customizationId             // foreign key
  priceModifier               // price markup for applying the customization
  isValid                     // false if this record is invalid/obsolete
}

Orders {
  orderId                     // primary key
  customerId                  // foreign key
}

Product_Orders {
  productOrderId              // primary key
  orderId                     // foreign key
  productId                   // foreign key
  quantity
}

Customization_Orders {
  customizationOrderId        // primary key
  productOrderId              // foreign key
  productCustomizationId      // foreign key
}

Products表包含基本产品的数据 - 名称,价格等

Customization_Types表包含不同自定义的类型名称 - COLOR,FABRIC,LENGTH等

Customizations表包含指向customizationTypeId的链接以及合法值 - 我假设用户无法输入任意数值(例如LENGTH或WIDTH),即他们已经给出了一个下拉框而不是文本框,但是如果他们可以输入任意数字数据,那么你需要MIN / MAX字段,这些字段对于命名约束是空的(例如,你可以有Type:COLOR / Value:BEIGE /最小值:NULL / Max:NULL或类型:LENGTH /值:NULL / Min:4 / Max:8)

Product_Customizations表将Customization链接到Product,例如,如果ProductX可以加入BEIGE,那么您将创建一个ProductXComomization记录,将ProductX链接到BEIGE。

订单表只包含orderId以及与订单相关的任何其他内容(例如指向customerId和shippingAddressId的链接)

Product_Orders将产品链接到订单

Customization_Orders将Product_Customization链接到Product_Order

假设客户在BEIGE和LENGTH = 8中订购ProductX,那么您将创建订单记录,带有ProductX链接的Product_Order记录和两个Customization_Order记录 - 一个链接到COLOR = BEIGE和一个与LENGTH = 8相关联。

这样可以轻松修改产品的自定义而无需重新加载整个产品 - 用户可以将颜色修改为COLOR = RED,而无需触及长度自定义(删除旧的Customization_Order:COLOR = BEIGE记录和创建一个新的COLOR = RED记录),或者用户可以删除长度自定义而不触及颜色自定义(删除旧的Customization_Order:LENGTH = 8记录)。

重新加载旧订单/产品时,您可以快速验证相同的productCustomizationId仍然适用于相关产品,否则标记用户。此外,如果自定义仍然适用但是自定义的价格修改器已更改,则可以标记用户。

就转换遗留数据而言,我不熟悉Python,但我确实有通过Java阅读JSON的经验,并且我假设Python为此提供了类似的(如果不是更好的)库。诀窍是将现有数据与预先加载的Product_Customization数据相匹配 - 如果数据无法匹配,则使用isValid = FALSE创建与其对应的新Product_Customization行(假设不再提供相关的自定义) ,当你有机会手动迭代无效的Product_Customization行时,确保这些是真正无法匹配的自定义,而不仅仅是解析错误。

答案 1 :(得分:0)

对Zim-Zam的回答几乎没有改进。

更好的方法是将不是普通值(BEIGE,VELVET,8)存储为自定义参数,而是存储代码可以构建正确的自定义视图的模式。

它可能只是JSON / XML格式的文本。负责构建视图和应用逻辑的实体应该能够使用不同版本的JSON数据。

例如,如果自定义的属性已更改且添加了新内容,则在这种情况下您只需更改代码并将保存已调整的JSON。无需更改现有数据。此外,应该有可能读取旧属性的旧JSON版本并使用它。

如果您从DB中读取旧实体,该怎么办?

  1. “视图”构建器将忽略自定义的所有旧属性,添加新属性并将其值设置为默认值。我个人会这样做。
  2. 向用户显示旧视图,但是当用户点击(例如,“确定”按钮或“完成”)时,其他逻辑将检查是否存在旧属性,并通知用户应手动删除它们或仅自动删除它们。
  3. 更灵活的方法,只需更改代码而无需触及数据库,并允许在必要时显示用户旧的自定义属性。

    更新: 自定义可以有两种属性:一种是管理员可以定义的属性,例如标题或价格,它们不经常更改并且对于所有自定义都是通用的,另一种属性如可以经常更改的大小和颜色,可以具有用户定义的值和对于所有自定义都不常见。

    第一种应该作为单独的列存储在Customization表中。这将允许在管理面板中更改此类属性,并使所有先前存储的数据保持一致。

    第二种属性可以是1)经常更改2)并非所有自定义类型都可以具有此类属性。将它们存储为单独的列是一个坏主意,因为如果存在大量数据,更改列类型或添加新列可能会导致性能下降,有时由于不兼容的属性类型而无法实现。 实际上,如果将它们存储为单独的列,则可能必须更改代码以支持新属性。

    我的想法是,您仍然允许管理员更改此类属性的类型并添加新属性或通过某个界面删除旧属性。这里的关键是你要存储像这样的JSON数据

    {
        "properties": {
                {
                    "propertyName": "height",
                    "propertyType": "int",
                    "min" : 10,
                    "max" : 25,
                },
                {
                    "propertyName": "color",
                    "propertyType": "color",
                },
                {
                    "propertyName": "anotherCustomField",
                    "propertyType": "text",
                },
            }
    }
    

    剩下要做的是为各种属性类型实现视图构建器或渲染器。并添加一个仅包含值的单独表。您从db中获取了自定义记录,您发现了哪些自定义属性,检查哪一个仍然有效并且只渲染了有效的自定义属性。如果管理员更改了自定义属性的类型或者只删除了一个属性,则将该自定义的属性标记为在db中无效,这就是所有工作。没有代码更改,也没有数据库架构更改。