可选的外键应该是 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_international
或 DateField
release_date
)。现在,您必须在不代表实际专辑的神奇行中为这些字段虚构任意值。
所以我建议坚持传统的选项 1。
在表单中处理此问题是内置且直接的。例如,在ModelForms
中,ForeignKey
由ModelChoiceField
表示。如果ForeignKey
具有blank=True
和null=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,例如 CharField
和 TextField
...”)。 NULL
表示字段中缺少数据是通常规则的例外。这包括ForeignKeys
。我编辑了答案以解决您对表单的评论。如果您在工作时遇到特定问题,我建议您提出一个新问题。
我知道约定是针对文本字段的,但这是 Django 在任何关于 null
值的讨论中最接近的。我无法在我的应用程序中使用 ModelFields。我觉得关于可选FK
的约定在Django 中是一个灰色地带。以上是关于可选的外键应该是 NULL 还是应该指向一个空字符串的主要内容,如果未能解决你的问题,请参考以下文章