【问题标题】:Should optional Foreign Key be NULL or should it point to an empty string可选的外键应该是 NULL 还是应该指向一个空字符串
【发布时间】:2015-03-16 17:34:05
【问题描述】:

当外部表中缺少可选Foreign Key 的相关值时,我可以:

  1. 设置为null

  2. 指向外部表中的空字符串''

在我看来,如果您遵循 Django 设计实践,您最终会得到选项 2(参见下面的代码)。这两种方法有什么明显的优点或缺点吗?

Django 在设计/惯例上偏爱2。文档说null'' 是“无数据”的两个可能值。因此,如果您省略可选字段,表单将正确验证并提供您的Foreign Key 可以指向的空字符串。 但是从逻辑上讲,缺失值似乎应该暗示null 或缺失Foreign Key(而不是指向空值的有效Foreign Key)。

此外,如果我只是列出所有专辑或进行计数,存储空白将是一个问题。每次我都要记住避免空字符串。相反,null 外键在专辑表中永远不会有条目。

参考:

  1. Can a foreign key be NULL...
  2. Can foreign key be NULL
  3. null vs blank 上的 Django 文档

带有代码的可选细节:

#models.py
class Album(Model):
    name = CharField(max_length=50, unique=True)

class Song(Model):
    name = CharField(max_length=50) 
    album = ForeignKey(Album, null=True, blank=True)    

#forms.py
class SongForm(Form):
    name = CharField(max_length=50)
    album = CharField(max_length=50, required=False)

如果您有一首没有专辑名称的歌曲,表单将返回{'name':'foo', 'album':''}。这会在 Album 表中创建一个名称为空的条目。我可以在视图中规避它(参见下面的代码)。但这似乎是一种 hack,因为数据验证应该在表单中完成。

if album:
    Song.objects.create(name=form.cleaned_data['name'], album=form.cleaned_data['album'])
else:
    Song.objects.create(name=form.cleaned_data['name'], album_id=None)

【问题讨论】:

    标签: django database django-models


    【解决方案1】:

    经过深思熟虑,与接近 1 相比,接近 2(缺少外部关系意味着 FK 到空字符串的方法)具有一个优势。

    使用2 方法可以更轻松地在(song.name, song.album) 上创建unique_together 索引。用一个例子可以更好地解释这一点。当Album 使用空字符串时,两个相似的Song 值(下面的d)将被唯一约束捕获。但是,null 在 DB 中被视为不同的值,它们不会在案例 1 中被捕获(必须依靠条件索引才能完成这项工作)。

    我不确定这是设计使然还是 Django 约定的意外优势。

    Missing album => FK is null                    Missing Album => FK points to blank name
    Song  |   Album                                Song  |   Album
    ----------------                               ----------------
    'a'   |    'x'                                 'a'   |    'x' 
    'b'   |    'y'                                 'b'   |    'y'
    'c'   |    'y'                                 'c'   |    'y'
    'd'   |    null                                'd'   |    ''
    'd'   |    null  <- Dup. not caught by DB      'd'   |    ''   <- Duplicate caught by DB
    

    【讨论】:

      【解决方案2】:

      Django 的约定是使用'' 来表示基于文本的字段中缺少数据。然而,当谈到ForeignKeys 时,Django 和其他地方的约定是用NULL 表示缺少数据(即选项1)。

      您的选项 2 混淆了没有名字的专辑没有专辑的歌曲之间的区别。 Django 的约定与表示允许的字段中缺少数据的不同方式有关。但在你的情况下,没有名字的专辑无效有效,所以选项 2 需要发明一个无效的专辑只是为了给其他模型一些指向。

      您写道:“将其指向外部表中的空字符串''”。但请注意,外键不指向外部表中的字段,它们指向整行。假设您在 Album 模型中有其他字段(例如 BooleanField is_internationalDateField release_date)。现在,您必须在不代表实际专辑的神奇行中为这些字段虚构任意值。

      所以我建议坚持传统的选项 1。

      在表单中处理此问题是内置且直接的。例如,在ModelForms 中,ForeignKeyModelChoiceField 表示。如果ForeignKey 具有blank=Truenull=True,则下拉列表中的选项之一将为空白,并且选择该选项保存表单将使相应的数据库字段NULL

      在您的情况下,您似乎让用户直接在Song 表单上输入专辑名称。这很好,但当然你必须有特殊的逻辑来解释这个值并创建适当的模型。您上面的示例代码不是黑客攻击,也不是数据验证。验证是否允许空值,这由blank=True(在模型中)或required=False(在表单中)控制。

      【讨论】:

      • 您能否在上面的代码中引用一个文档/示例或显示,Django 将如何通过null 表示FK 并在表单中进行验证。关于2,我原则上同意你的观点,但如果我仅根据 Django 文档进行争论,我会说代表“无数据”Django convention is to use the empty string, not NULL。按照这个逻辑,“no name”不是无效的专辑名称,而是表示缺少数据。最后,通过“指向一个空字符串”,我知道FK 指向一行而不是一个字段。另请参阅下面的答案。
      • 您引用的约定与基于文本的字段有关(“避免在基于字符串的字段上使用 null,例如 CharFieldTextField...”)。 NULL 表示字段中缺少数据是通常规则的例外。这包括ForeignKeys。我编辑了答案以解决您对表单的评论。如果您在工作时遇到特定问题,我建议您提出一个新问题。
      • 我知道约定是针对文本字段的,但这是 Django 在任何关于 null 值的讨论中最接近的。我无法在我的应用程序中使用 ModelFields。我觉得关于可选FK 的约定在Django 中是一个灰色地带。
      猜你喜欢
      • 2013-07-10
      • 1970-01-01
      • 2014-12-09
      • 2018-04-16
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      相关资源
      最近更新 更多