在我看来,您设计的核心问题是代理键的使用。不必总是为表创建数字单列键。即使你这样做了,这也不能保证你不会有重复。事实上,这迫使系统在您的表上保留更多索引,这是一项额外的工作。
一些概念:
- 我对表名使用单数,因为表中的每个元组代表关系的一个对象;
- 我使用小写字母作为表名和列名(标识符),使用大写字母作为关键字。我不喜欢数据库中的 CamelCase;
- 我
<table_name>_id
用于 PK 列。由于表名单一,这也是可能的;
- 我对我创建的所有约束和索引使用前缀 + 表名 + 详细信息。
您不必遵循它们,但是为您的设计坚持一致的命名模式会非常好。
我将从property_type
字典开始:
CREATE TABLE property_type (
property_type varchar(20) NOT NULL,
CONSTRAINT p_property_type PRIMARY KEY (property_type)
);
它是一个单列表,仅用于为属性类型提供可能值的域。我用过varchar(20)
,文字栏还不错。这样做的一个好处——你不必通过数字键加入这个表来获得什么property_type_id=123
意思。
特性:
CREATE TABLE property (
property_id integer NOT NULL,
property_name varchar(50) NOT NULL,
property_type varchar(20) NOT NULL,
CONSTRAINT p_property PRIMARY KEY (property_id),
CONSTRAINT u_property_name UNIQUE (property_name),
CONSTRAINT f_property_type FOREIGN KEY (property_type)
REFERENCES property_type ON UPDATE CASCADE
);
我决定在这里使用数字 PK,因为我想您可能想在某些时候重命名属性。如果您更改property_type
,更新将级联。
尽管这里已经有一个 PK,但UNIQUE
对名称的约束是必须的,否则您可能最终会遇到具有不同 ID 的相同名称属性的情况。
DT 表:
CREATE TABLE dt (
dt_id integer NOT NULL,
dt_name varchar(50) NOT NULL,
CONSTRAINT p_dt PRIMARY KEY (dt_id),
CONSTRAINT u_dt_name UNIQUE (dt_name)
);
同样,只有 PK 是不够的,还会产生UNIQUE
约束。虽然我会非常彻底地摆脱dt_id
并且只会保留
dt_name
并且会使其成为PK。
DT 的属性:
CREATE TABLE dt_property (
dt_id integer NOT NULL,
property_id integer NOT NULL,
initial_value varchar(50) NOT NULL,
CONSTRAINT p_dt_property PRIMARY KEY (dt_id, property_id),
CONSTRAINT f_dt_id FOREIGN KEY (dt_id) REFERENCES dt,
CONSTRAINT f_property_id FOREIGN KEY (property_id) REFERENCES property
);
这是与您的设计的第一个重大区别——使用了复合键。
是的,这意味着只要您想引用此表中的条目,就必须执行 2 列。但这并不是什么大问题,真的——你设计了一次表格,你也写了一次查询,但是如果你的软件做得正确并且易于维护,它可能会使用相当长的一段时间。从长远来看,最好花更多时间编写查询并轻松维护系统。
标签:
CREATE TABLE tag (
tag_id integer NOT NULL,
tag_name varchar(50) NOT NULL,
CONSTRAINT p_tag PRIMARY KEY (tag_id),
CONSTRAINT u_tag_name UNIQUE (tag_name)
);
这只是另一本字典。同样,就像dt
表格一样,我真的很想避免使用tag_id
column 并保留 just tag_name
,使其也是一个 PK。
新表tag_dt
介绍:
CREATE TABLE tag_dt (
tag_id integer NOT NULL,
dt_id integer NOT NULL,
CONSTRAINT p_tag_dt PRIMARY KEY (tag_id, dt_id),
CONSTRAINT f_tag_id FOREIGN KEY (tag_id) REFERENCES tag,
CONSTRAINT f_dt_id FOREIGN KEY (dt_id) REFERENCES dt
);
此表是创建dt
+tag
关系所必需的。没有它,你就会有数据重复——你可以在你的架构上看到它,你有 2 行带有Tag_name='Tag1'
.
最后,标签属性:
CREATE TABLE tag_property (
tag_id integer NOT NULL,
dt_id integer NOT NULL,
property_id integer NOT NULL,
a_value varchar(50) NOT NULL,
CONSTRAINT p_tag_property PRIMARY KEY (tag_id, dt_id),
CONSTRAINT u_tag_property UNIQUE (tag_id, property_id),
CONSTRAINT f_tag_property_tag FOREIGN KEY (tag_id, dt_id) REFERENCES tag_dt,
CONSTRAINT f_tag_property_property FOREIGN KEY (dt_id, property_id)
REFERENCES dt_property
);
该表是完整的复合键,它符合您的所有要求。主键是tag_id, dt_id
并且它也是tag_dt
表的外键,因此您希望能够引入以前未定义的内容。其次,tag_id, property_id
是唯一的,意味着标签的属性不能重复。最后是dt_id, property_id
引用dt_property
表,这意味着只有允许的属性dt
才会被注册。
最后的笔记
在大多数 DBMS 中,所有主键和唯一键都是通过索引实现的。此外,即使省略了键的第一列,一些 DBMS 也可以使用复合索引(多列索引)。至少PostgreSQL可以做到,这是我用得最多的。
请检查您的dt
和tag
表格,我强烈建议摆脱其中的代理键,就像property_type
完成一样。
我没有创建任何额外的索引,通常我在实现数据模型并对其进行一些实际查询之后进行此练习。
另外,不要使用value
orname
作为列名。这些是保留字,您可能会在您选择的 DBMS 的未来版本中产生意想不到的效果。