返回

实体关系图:如何表示层级结构?(自引用/连接表)

mysql

实体关系图中如何表示层级结构

数据库设计中,层级结构的表示是一个常见的需求。以超市商品为例,商品分为不同的类别,类别又可细分为子类别。这些类别和子类别构成一个层级关系。如何准确、有效地在实体关系图(ER 图)中体现这种层级结构?我们来探讨几种可行方案。

自引用外键

一个直接且常见的办法是利用自引用外键。在一个类别(Category)表中,增加一个外键 parent_category_id ,这个外键引用本表的主键 category_id

核心思想

每个类别,除了顶级类别,都关联一个父类别。这样就构成了一个树状结构,可以清晰表示类别间的层级关系。顶级的类别,则将 parent_category_id 置空即可。

优势

这种方法相对简洁、实现成本低、易于理解。不需要额外的表或特殊的数据库技术。对于大多数层级结构简单的场景都适用。

代码示例 (SQL)

CREATE TABLE Category (
    category_id INT PRIMARY KEY AUTO_INCREMENT,
    category_name VARCHAR(255) NOT NULL,
    parent_category_id INT,
    FOREIGN KEY (parent_category_id) REFERENCES Category(category_id)
);

CREATE TABLE Product (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(255) NOT NULL,
    category_id INT NOT NULL,
    FOREIGN KEY (category_id) REFERENCES Category(category_id)
);

操作步骤

  1. 创建 Category 表,包含 category_id, category_nameparent_category_id 列。
  2. 设置 parent_category_id 外键,引用自身的 category_id
  3. 创建 Product 表,通过 category_id 关联到 Category 表。
  4. Category 表中插入数据时,子类别记录的 parent_category_id 列应设置为对应的父类别id,根节点 parent_category_id 设置为 null。

安全建议

为了保证数据完整性,应设置 parent_category_id 列的非空约束,以便明确类别必须属于一个父类别,从而更精细的表达数据的完整性。对于 Product 表的 category_id 外键,推荐设置 ON DELETE RESTRICTON DELETE CASCADE,避免出现外键关联问题,例如:当 category 表里的记录被删除后,Product 表里依然有这个被删除的类别 id.

使用连接表

有时为了满足更多的关系属性或者避免过度自引用,可以使用一个连接表(junction table)来表示层级关系。连接表 CategoryHierarchy 存储父类别ID super_category_id 和子类别ID sub_category_id 之间的关联关系。

核心思想

此方法在自引用外键的基础上做了拓展。当除了维护层级结构本身之外,还需要维护额外的属性(例如子类的权重、排列顺序)等属性,可以考虑使用连接表。

优势

相较于单一的自引用外键,此方法提供了更大的灵活性和扩展性,易于调整结构和记录关系属性。

代码示例 (SQL)

CREATE TABLE Category (
    category_id INT PRIMARY KEY AUTO_INCREMENT,
    category_name VARCHAR(255) NOT NULL
);


CREATE TABLE CategoryHierarchy (
  super_category_id INT NOT NULL,
  sub_category_id INT NOT NULL,
  PRIMARY KEY (super_category_id, sub_category_id),
  FOREIGN KEY (super_category_id) REFERENCES Category(category_id),
  FOREIGN KEY (sub_category_id) REFERENCES Category(category_id)
);


CREATE TABLE Product (
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(255) NOT NULL,
    category_id INT NOT NULL,
    FOREIGN KEY (category_id) REFERENCES Category(category_id)
);

操作步骤

  1. 创建 Category 表, 存储类别基本信息(不包含层级关系信息)。
  2. 创建 CategoryHierarchy 表,包含 super_category_idsub_category_id 两列,并且设置为主键。并建立相应的外键,分别指向 Category 表的 category_id
  3. 创建 Product 表,通过 category_id 关联到 Category 表。
  4. CategoryHierarchy 表插入数据,维护层级结构。

安全建议

为避免 CategoryHierarchy 中出现重复记录,可以设置复合主键;为了避免子类别不能加入到自身父类别,建议使用触发器来防止该种情况的发生。

IS-A 关系与层级关系

“IS-A” 关系通常用于表示面向对象编程中的继承关系。直接将 "IS-A" 应用于数据库建模的层级结构可能并非最自然、最简洁的方式,例如帖子中贴出来的关系图,反而会使得设计复杂,不够灵活,不利于扩展维护。 使用上面的方案,更符合数据建模的最佳实践。

如何确定子类别数量

确定每个父类别下子类别数量可通过以下 SQL 查询实现。无论哪种模型(自引用外键、连接表),基本逻辑一致。这里以自引用外键为例:

SELECT c1.category_name AS SuperCategory, COUNT(c2.category_id) AS SubCategoryCount
FROM Category c1
LEFT JOIN Category c2 ON c2.parent_category_id = c1.category_id
WHERE c1.parent_category_id IS NULL
GROUP BY c1.category_id, c1.category_name;

连接表的方式稍作修改即可实现同样的功能。

总之,数据库中层级结构的表达是一个通用问题,通过合理运用外键或者连接表,可以很好地应对此类挑战,并维护数据结构。