【发布时间】:2020-01-14 06:24:30
【问题描述】:
我希望就哪种数据库模式最适合我的情况达成共识,以便在表中存储小部件的“类型”信息。 Widget 只能有一种类型,但该类型可以是 Preset-Type 或 Custom-Type。我显然会创建预设类型,而用户会创建自定义类型。
我将在服务器上使用 MySQL 和 INNODB。我还将使用 SQLite 在应用程序上存储相同的信息。但是我们在这里只讨论服务器。我是一名应用程序员,而不是数据库管理员,但我想在第一时间就为这个项目找到正确的数据库并在合理范围内进行规范化。
在搜索是否应该对外键使用空值时,我从比我拥有更多数据库经验的人那里得到了以下答案。
- “当然,Null 可以在外键和其他地方使用。”
- “外键中的 NULL 完全可以接受。”
- “NULL 必不可少的一个领域是外键。”
- “几乎不应该使用 Null,尤其是在外键中。”
- “绝不能在任何地方使用 Null。”
- “具有大量 NULL 值的列通常表明需要(进一步)规范化。”
我需要知道在模型 #2 的特定情况下使用 Null 是否是不好的做法,以及哪种模型更可取以及为什么。或者可能建议一个更好的模型。感谢您的任何意见。
型号 #1
对于预设和自定义类型都有一个“类型”表。我通过使用预设类型预先填充“类型”表并为以后可以添加的未来预设类型保留大约 1500 个保留空间来做到这一点。
优点:简单,没有额外的表,没有连接,可能是最快的选择,并且从长远来看可能更少的数据库空间(4 字节 type_id)。并且小部件表 type_id FK 永远不会为 NULL。
缺点:将预设和自定义类型混合在一起可能不是很好的规范化做法,因为预设不需要“account_id”等字段。如果我想要超过 1500 个预设(极不可能),我需要弄清楚别的东西出来。此模型还使用类型表中的标记/占位符值作为预设和保留的预设点。
CREATE TABLE accounts (
account_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
# Other Columns...,
PRIMARY KEY (account_id)
);
CREATE TABLE widgets (
widget_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
account_id INT UNSIGNED NOT NULL,
type_id INT UNSIGNED NOT NULL,
PRIMARY KEY (widget_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE,
FOREIGN KEY (type_id) REFERENCES types(type_id)
);
CREATE TABLE types (
type_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
account_id INT UNSIGNED NOT NULL,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (type_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id)
);
型号 #2
预设和自定义类型的单独小部件类型表。 'widgets' 表具有预设类型和自定义类型的可为空的 FK 字段。 Check 约束确保其中一个为空,另一个不为空。
优点:数据库中只有 1 个额外的表。除了可能为空的 FK 之外,没有任何标记/占位符值。无需预留预置值空间,对未来预置类型添加无限制。
缺点:对于preset_type_id 或custom_type_id,widgets 表中的每条记录使用一个FK null。
CREATE TABLE accounts (
account_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
# Other Columns...,
PRIMARY KEY (account_id)
);
CREATE TABLE widgets (
widget_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
account_id INT UNSIGNED NOT NULL,
preset_type_id INT UNSIGNED DEFAULT NULL,
custom_type_id INT UNSIGNED DEFAULT NULL,
PRIMARY KEY (widget_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE,
FOREIGN KEY (preset_type_id) REFERENCES preset_types(preset_type_id),
FOREIGN KEY (custom_type_id) REFERENCES custom_types(custom_type_id),
CHECK ((preset_type_id IS NOT NULL AND custom_type_id IS NULL) OR (preset_type_id IS NULL AND custom_type_id IS NOT NULL) )
);
CREATE TABLE preset_types (
preset_type_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (preset_type_id)
);
CREATE TABLE custom_types (
custom_type_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
account_id INT UNSIGNED NOT NULL,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (custom_type_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id)
);
模型#3
使用中间表 widget_preset_types 和 widget_custom_types。如果小部件具有预设类型,它将在 widget_preset_types 表中引用,或者如果小部件具有自定义类型,它将在 widget_custom_types 表中引用。
优点:可能是最标准化的模型。永远不要使用 Null 或 FK Null。没有使用哨兵/占位符值。
缺点:在数据库中添加 3 个额外的表只是为了确定小部件类型。除了具有自定义/预设类型的数据库中的小部件之外,我还有其他东西,这意味着我可以使用此模型向我的数据库中添加至少 12 个额外的表。是否过度标准化?我将不得不使用某种类型的连接来同时从 3 个表中获取所有小部件信息和类型信息。我将不得不检查 custom_type_id 或 preset_type_id 是否在连接中返回,可能使用的代码比我在 Model#2 中检查空值时使用的代码多。可能比模型 1 和 2 慢。更多的表意味着更多的索引意味着更多的内存。
CREATE TABLE accounts (
account_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
# Other Columns...,
PRIMARY KEY (account_id)
);
CREATE TABLE widgets (
widget_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
account_id INT UNSIGNED NOT NULL
PRIMARY KEY (widget_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
);
CREATE TABLE preset_types (
preset_type_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (preset_type_id)
);
CREATE TABLE custom_types (
custom_type_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
account_id INT UNSIGNED NOT NULL,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (custom_type_id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
);
CREATE TABLE widget_preset_types (
widget_id INT UNSIGNED NOT NULL UNIQUE,
preset_type_id INT UNSIGNED NOT NULL,
PRIMARY KEY (widget_id),
FOREIGN KEY (widget_id) REFERENCES widgets(widget_id) ON DELETE CASCADE,
FOREIGN KEY (preset_type_id) REFERENCES preset_types(preset_type_id)
);
CREATE TABLE widget_custom_types (
widget_id INT UNSIGNED NOT NULL UNIQUE,
custom_type_id INT UNSIGNED NOT NULL,
PRIMARY KEY (widget_id),
FOREIGN KEY (widget_id) REFERENCES widgets(widget_id) ON DELETE CASCADE,
FOREIGN KEY (custom_type_id) REFERENCES custom_types(custom_type_id)
);
【问题讨论】:
-
您的第一个问题是什么?似乎它很可能是 DB/SQL 子类型/继承的常见问题。 (尽管感谢您的想法/设计。)在考虑发布之前,请始终使用谷歌搜索您的错误消息或您的问题/问题/目标的许多清晰、简洁和精确的措辞,有和没有您的特定字符串/名称和站点:stackoverflow.com 和标签,并阅读许多答案。如果您发布问题,请使用一个短语作为标题。请参阅How to Ask 和投票箭头鼠标悬停文本。除非我们努力(重新重新)写清楚,否则我们无法推理、交流或搜索。 PS 阅读编辑帮助代码。
-
工程中没有“更好”/“最好”之类的东西,除非你定义它。同样不幸的是,所有合理的实际定义都需要大量的经验,以及与对细节的混乱敏感度相互作用的大量因素。进行简单的设计。当您通过测量证明您可以想到的设计和所有替代方案都存在问题时(无论当时意味着什么),然后提出一个非常具体的问题。这也应该定义“更好”/“最好”。 Strategy for “Which is better” questions
-
如果我必须将其归结为一个问题,那就是“我需要知道在 Model #2 的特定情况下使用 Null 是否是一种不好的做法”
-
请通过编辑而非 cmets 进行澄清。单选按钮 FK 是一种子类型化反模式,您会发现它说研究 DB/SQL 子类型化/继承,请参阅链接。 PS 在任何特定情况下“使用 Nulls 是否是不好的做法”本质上是(知情的)意见问题,正如您从发现的论点中看到的那样。 (但是当一个人只有一把锤子时,一切都需要锤击。) PS这里没有标准化。如果您打算(错误)在此处使用该术语,请定义您的意思并解释它在您使用时的应用方式。如果您不能在一个学期内做到这一点,请不要使用它。
标签: mysql database inheritance database-design foreign-keys