如何正确设计数据库的这一部分(循环引用?)

How to properly design this part of a database (circular reference?)

情况:

A company has many projects
A project has many tags

A project belongs to only 1 company
A tag can belong to multiple projects

A company must have access to its own tags

示例 1:

在第一张图片中,公司的所有标签都可以通过 projects/project_tag 获得。但是,如果删除所有项目,则公司的标签将无法再访问,因为 project_tag 和项目之间的 link 消失了。 标签应该总是以某种方式 link 发送给公司,即使没有项目。

示例 2(标签也 linked 到公司):

在第二张图片中,它应该可以工作,但是现在是 'circular reference' ??? 对于这样的问题,最好的解决方案应该是什么? 那么外键呢?

问题最后是: 如何针对这种情况正确设置 database/datamodel?


第二个例子中可能出错的例子:

companies:
id=1, name=MyCompany
id=2, name=OtherCompany

tags:
id=1, company_id=1, name=MyTag
id=2, company_id=2, name=OtherTag

projects:
id=1, company_id=1, name=MyProject

project_tag:
project_id=1, tag_id=1
project_id=1, tag_id=2 --> THIS ROW IS NOT VALID!

The last project_tag row is not valid because:
project 1 is linked to company_id 1
tag_id 2 is linked to company_id 2


更新:感谢大家提供的信息!

根据接受的答案,PostgreSQL 的 CREATE 查询将变为:

CREATE TABLE companies (
   id SERIAL PRIMARY KEY NOT NULL,
   name TEXT NOT NULL
);
CREATE TABLE projects (
   id SERIAL PRIMARY KEY NOT NULL,
   company_id INT NOT NULL,
   name TEXT NOT NULL,
   UNIQUE (id, company_id),
   FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE TABLE tags (
   id SERIAL PRIMARY KEY NOT NULL,
   company_id INT NOT NULL,
   name TEXT NOT NULL,
   UNIQUE (id, company_id),
   FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE project_tag (
   id SERIAL PRIMARY KEY NOT NULL,
   company_id INT NOT NULL,
   project_id INT NOT NULL,
   tag_id INT NOT NULL,
   UNIQUE (company_id, project_id, tag_id),
   FOREIGN KEY (company_id, project_id) REFERENCES projects (company_id, id) ON DELETE CASCADE ON UPDATE CASCADE,
   FOREIGN KEY (company_id, tag_id) REFERENCES tags (company_id, id) ON DELETE CASCADE ON UPDATE CASCADE
);

Tested:
- Rows inserted in project_tag are checked on the same company_id (else: denied)
- Not possible to insert duplicate rows into project_tag
- If a project is removed, the linked project_tag rows are also removed
- If a tag is removed, the linked project_tag rows are also removed
- If a company is being removed while still having projects, the removal is rejected (see projects table: ON DELETE RESTRICT)
- If a company (without projects) is removed, all linked tags are removed also

首先,你的第二个模型完全正确,没有任何循环引用。

您应该将 CompanyCompany_ID 作为 F.K 传输到 TagsProject 并制作它不为空

然后,你应该将TAG_IDProject_ID作为F.K传输到Project_Tag中,并一起做唯一性。并且不需要将ProjectTagCompany_ID(我们在上一段中传输的)传输到Project_Tag

现在,最后一个问题,您的最终要求:

THIS ROW IS NOT VALID!

无法通过 ER 捕获它。您应该编写一些函数、触发器或存储过程来捕获和控制它。

编辑:
基于@reaanb 的评论和他的出色回答 :您可以通过这种方式控制此约束,但有一点冗余:

CREATE TABLE Project(
    project_id INT NOT NULL,
    company_id INT NOT NULL,
    PRIMARY KEY (project_id),
    FOREIGN KEY (company_id) REFERENCES Company (id),
    UNIQUE KEY (project_id, company_id)
);

CREATE TABLE Tag(
    tag_id INT NOT NULL,
    company_id INT NOT NULL,
    PRIMARY KEY (tag_id),
    FOREIGN KEY (company_id) REFERENCES Company (id),
    UNIQUE KEY (tag_id, company_id)
);

CREATE TABLE Project_Tags(
    id INT NOT NULL,
    company_id INT NOT NULL,
    project_id INT NOT NULL,
    tag_id INT NOT NULL,

    PRIMARY KEY (id),
    UNIQUE KEY (tag_id, project_id)

    FOREIGN KEY (project_id, company_id) REFERENCES Project (project_id, company_id),
    FOREIGN KEY (tag_id, company_id) REFERENCES Tag (tag_id, company_id),
);