如何在一个或另一个外键之间进行约束或选择?

How can I constrain or choose between one or other foreign key?

问题是概念性的(数据库关系),所以语言不是这里的重点,但我正在使用 Python 和 Django。

我有3个models/tables:

例如

class Company(models.Model):
    name = models.CharField(max_lenght=100) #example

class Customer(models.Model):
    name = models.CharField(max_lenght=100) #example

class Adress(models.Model):
    country = models.CharField(max_lenght=100) #example
    state = models.CharField(max_lenght=100) #example
    
    # here I want the address owner
    # I could put something like this:
    company = models.ForeignKey(Company, on_delete=models.PROTECT)
    customer = models.ForeignKey(Customer, on_delete=models.PROTECT)

我希望地址属于客户或公司,但不能同时属于两者。
我知道我可以简单地创建 2 个地址 classes,例如 CustomerAddress、CompanyAddress,每个地址都有正确的外键:

class Company(models.Model):
    name = models.CharField(max_lenght=100) #example

class Customer(models.Model):
    name = models.CharField(max_lenght=100) #example

class CompanyAdress(models.Model):
    country = models.CharField(max_lenght=100) #example
    state = models.CharField(max_lenght=100) #example
    company = models.ForeignKey(Company, on_delete=models.PROTECT)

class CustomerAdress(models.Model):
    country = models.CharField(max_lenght=100) #example
    state = models.CharField(max_lenght=100) #example
    customer = models.ForeignKey(Company, on_delete=models.PROTECT)

但我不想这样做有两个原因:

  1. 重复的代码以及在 Django 管理面板中我将有两个单独的地址列表的事实,这没有多大意义,因为所有地址在结构上都是相同的。我可以修复重复的代码,创建一个基础 class,从它继承等等,但我仍然会在管理面板中有 2 个列表。
  2. 我以后可能会遇到同样的概念性问题,但更复杂,例如300 class 一些东西和 1 class 应该只有 300 个中的一个有外键。

我该怎么办?

您可以尝试将外键设置为null=True,并根据需要通过视图控制数据库中的输入:

class Address(models.Model):
    country = models.CharField(max_lenght=100) 
    state = models.CharField(max_lenght=100)
    company = models.ForeignKey(Company, on_delete=models.PROTECT, null=True)
    customer = models.ForeignKey(Customer, on_delete=models.PROTECT, null=True)

有关 UML 图,请查看 Martin Fowler 的 Analysis Patterns。以下是DB模型。

-- Address ADR exists.
--
address {ADR}
     PK {ADR}

Party是个人或组织的通称; discriminatorTYP用来区分两者。

-- Party PTY, of party-type TYP, resides at address ADR.
--
party {PTY, TYP, ADR}
   PK {PTY}
   SK {PTY, TYP}

CHECK TYP in {'P', 'O'}

FK {ADR} REFERENCES address {ADR}
-- Person, a party PTY of party-type TYP = 'P', exists.
--
person {PTY, TYP}
    PK {PTY}

CHECK TYP = 'P'

FK {PTY, TYP} REFERENCES party {PTY, TYP}
-- Organization, a party PTY of party-type TYP = 'O', exists.
--
organization {PTY, TYP}
          PK {PTY}

CHECK TYP = 'O'

FK {PTY, TYP} REFERENCES party {PTY, TYP}

注:

All attributes (columns) NOT NULL


PK = Primary Key
AK = Alternate Key   (Unique)
SK = Proper Superkey (Unique)
FK = Foreign Key

关于 亚型 的一句话。为子类型实现约束的正确方法是使用断言(CREATE ASSERTION),但它在主要数据库中仍然不可用。我正在使用 FKs 代替,并且与所有其他替代方法一样,它并不完美。人们争论很多,关于 SO 和 SE-DBA,哪个更好。我鼓励您也检查其他方法。