可选的外键应该是 NULL 还是应该指向一个空字符串

Posted

技术标签:

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

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

    设置为null

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

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

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

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

参考:

    Can a foreign key be NULL... Can foreign key be NULL 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)

【问题讨论】:

【参考方案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 中是一个灰色地带。

以上是关于可选的外键应该是 NULL 还是应该指向一个空字符串的主要内容,如果未能解决你的问题,请参考以下文章

我应该索引一个经常更新的外键吗

如果我的外键已经是唯一约束的一部分,我应该索引它们吗?

hibernate一对多,更新一方时多方的外键ID更新为null

我需要在 Oracle 的外键上创建索引吗?

NHibernate有一个懒惰的负载

如何创建指向同一实体的列表和属性