【问题标题】:Two mutually exclusive many-to-many relationships两个互斥的多对多关系
【发布时间】:2018-06-09 04:44:07
【问题描述】:

想象一下网上商店。你有货。有些商品有尺寸,有些没有。我有一个orders 表:

id int not null,
...

orders_products表:

order_id int not null,
product_id int null,
size_id int null,
...

products表:

id int not null,
...

sizes表:

id int not null,
product_id int not null,
...

现在product_idsize_id 不为空。换句话说,主键是order_id + (product_id xor size_id)。两者都不是。

用 Django 的话来说是:

class OrderProduct(models.Model):
    product = models.ForeignKey(Product, null=True, on_delete=models.CASCADE)
    size = models.ForeignKey(Size, null=True, on_delete=models.CASCADE)
    order = models.ForeignKey('Order', on_delete=models.CASCADE)
    amount = models.PositiveSmallIntegerField()

class Order(models.Model):
    products = models.ManyToManyField(Product, through=OrderProduct, related_name='orders')
    sizes = models.ManyToManyField(Size, through=OrderProduct, related_name='orders')
    ...

至少这是我现在所拥有的。但我不喜欢在orders_products 表中有两个互斥的外键。或Order 模型中的两个属性。其中之一 (sizes) 可能是多余的。

所以,我可能必须从 Order 模型中删除 sizes 属性。是这样吗?或者我应该让product_idorders_products 表中不为空?并且只有当产品有不同尺寸时才有size_id?还有其他建议吗?

我用djangopythonpostgresql 标记了这个问题。那是因为这些是我现在正在使用的。但我不拘泥于任何特定的语言,而是 SQL。

UPD 我刚刚意识到我已经对 sizes 表进行了非规范化。那里主要有SML 尺寸。

现在我看到四个选项:

  1. 我现在的样子。 Order.productsOrder.sizes 似乎有效。他们得到不相交的产品集。但是数据库中存在不一致的可能性(orders_products.product_idorders_products.size_id 都已设置或未设置)。

  2. 建议maverick:通用外键。

  3. 规范化sizes 表(多对多关系):

    products表:

    id int not null,
    ...
    

    products_sizes表:

    product_id int not null,
    size_id int not null,
    ...
    

    sizes表:

    id int not null,
    ...
    

    然后,以这种方式拥有orders_products 表:

    order_id int not null,
    product_id int null not null,
    size_id int null,
    ...
    

    有点意思。好吧,对于具有尺寸的产品,orders_products.size_id 仍有可能为空。对于 orders_products.size_id 被链接到产品没有的尺寸。

    通用外键不太可能在规范化表的情况下使用。

  4. 提取product_variants表(消费者基本买的):

    products:

    id int not null,
    ...
    

    sizes:

    id int not null,
    ...
    

    product_variants:

    id int not null,
    product id int not null,
    size_id int null
    

    orders_products:

    order_id int not null,
    productvariant_id int not null,
    amount int not null
    

    关于通用外键的说法似乎也适用于此。

哪个更好?

【问题讨论】:

    标签: python sql django postgresql many-to-many


    【解决方案1】:

    考虑在ProductProductSize 上将Generic Foreign Key 用于OrderProduct。它存储对象类型和对象ID,提供两个外键之间的互斥性。

    class OrderProduct(models.Model):
        ...
        limit = models.Q(app_label = 'app', model = 'Product') | models.Q(app_label = 'app', model = 'ProductSize')
        content_type = models.ForeignKey(ContentType, limit_choices_to = limit)
        object_id = models.PositiveIntegerField()
        content_object = generic.GenericForeignKey('content_type', 'object_id')
    

    【讨论】:

    • 你觉得值得吗?有什么优点/缺点?我可能需要自然键(用于固定装置/序列化)。不能将 GenericForeignKey 字段与过滤器一起使用。它们不会出现在ModelForms 中。 on_delete=cascade 用于具有反向关系的 GenericForeignKey 字段。表现更差。可能这些对我来说并不重要,至少现在是这样。数据库中不一致的可能性可能更大。如果我错了,请纠正我。
    • 优势:GenericForeignKey 将删除您的数据库中的冗余。对GenericForeignKey 的建议完全基于互斥。回答您对GenericForeignKey 的担忧:您可以在产品上使用过滤器,例如OrderProduct.objects.filter(Q(products__id=1) | Q(product_sizes__product__id=1))。您可以在 ModelForm 中添加自定义字段以从两个表中加载列表。但是是的,性能更差。这是性能和冗余数据之间的权衡。
    • 它还将处理您对产品与它没有的尺寸相关联或产品尺寸为空的产品尺寸的担忧。因为当产品没有尺寸时您将存储Product 对象,而当产品有尺寸时您将存储ProductSize 对象。对于非相交集,您不需要 2 ManytoManyFields 在订单表中。
    • 但是,如果我规范化表格,通用外键似乎没有用(请参阅我的问题中的选项 3 和 4)。哪个可能更好(有标准化的表,直到你有理由不这样做)并且让我认为通用外键不适合我的情况(当你想链接到多个表时它们似乎很好,但情况并非如此标准化表)。你说什么?
    • 您的第四个选项看起来不错。您最终将ProductSize 更改为ProductVarient,这从名称上更有意义。起初我认为只有ProductSizes 的外键名称似乎不正确,但最终消费者会购买。
    【解决方案2】:

    我决定解决我的最后一个想法。通过创建中间的productvariants 表来规范化sizes 表,该表包含代表客户购买的东西的实体。

    orders表:

    id int not null,
    ...
    

    orders_productvariants表:

    order_id int not null,
    productvariant_id int not null,
    ...
    

    productvariants表:

    id int not null,
    product_id int not null,
    size_id int not null,
    ...
    

    products表:

    id int not null,
    ...
    

    sizes表:

    id int not null,
    ...
    

    另外,我有一个空名称的尺寸,用于没有尺寸的项目。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-04
      • 2014-10-09
      • 2020-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多