如果可以删除孩子,如何建模父 -> 子 -> 孙

fgb*_*ist 6 postgresql erd foreign-key database-design orm

我正在处理一组可以移除孩子的关系,但我显然不想失去孙子和父母之间的联系。我确实考虑过将孩子标记为“已故”(在这篇文章中使用相关术语),但后来我最终会被困在我的数据库中的一堆已故孩子,谁想要这样(只是为了保持关系)?

关系的 ERD

如果删除父项,则删除其所有后代。此外,它的工作方式类似于“正常”关系,其中孙子和子项始终具有相同的顶级父级。层次结构固定为 3 个级别(如上所示)。最后,Parent、Child 和 Grandchild 都是不同的类型(例如,我们不是在谈论 3 个“人类”,他们没有相同的基础)。

然而,让孙子跟踪父母感觉有点奇怪,因为这种关系通常可以从父子关系中推导出来。虽然,我想不出另一种方法。

这个模型有效吗?或者有不同的方法吗?

Han*_*non 8

此答案基于您在澄清每个级别是不同类型之前的问题。由于您已经确定了对不同类型的需求,我同意我最初出现的答案,并且您的自我回答记录了您是如何解决这个问题的。 向孙表添加单列,引用最顶层的表,似乎是最简单的方法。

我将保留以下详细信息,以防它对未来的访问者有所帮助。


我会用交叉引用表来实现这一点。

下面是一个特定于 SQL Server 的示例;此表包含有关实体的列,包括名称等:

CREATE TABLE dbo.Entities
(
    EntityID int NOT NULL
        CONSTRAINT PK_Entities
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , EntityName varchar(30) NOT NULL
);
Run Code Online (Sandbox Code Playgroud)

下表描述了它们的关系:

CREATE TABLE dbo.EntityRelationships
(
      EntityIDParent int NOT NULL
        CONSTRAINT FK_EntityRelationships_Parent
        FOREIGN KEY
        REFERENCES dbo.Entities (EntityID)
    , EntityIDChild int NOT NULL
        CONSTRAINT FK_EntityRelationships_Child
        FOREIGN KEY
        REFERENCES dbo.Entities (EntityID)
    , CONSTRAINT PK_EntityRelationships
        PRIMARY KEY CLUSTERED (EntityIDParent, EntityIDChild)
    , CONSTRAINT CK_EntitytRelationships
        CHECK ((EntityIDParent <> EntityIDChild))
);
Run Code Online (Sandbox Code Playgroud)

每个关系都必须是唯一的,即任何给定的父级只能与任何给定的子级相关一次。

接下来,我们INSTEAD OF DELETEEntities表上创建一个触发器,在删除已删除的实体之前,通过重新父级任何必要的关系来正确处理删除:

CREATE TRIGGER EntityRelationshipDelete
ON dbo.Entities
INSTEAD OF DELETE
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO dbo.EntityRelationships (EntityIDParent, EntityIDChild)
    SELECT erp.EntityIDParent
        , erc.EntityIDChild
    FROM deleted d
        INNER JOIN dbo.EntityRelationships erp ON d.EntityID = erp.EntityIDChild
        INNER JOIN dbo.EntityRelationships erc ON d.EntityID = erc.EntityIDParent
    EXCEPT --don't create duplicate entries
    SELECT er.EntityIDParent, er.EntityIDChild
    FROM dbo.EntityRelationships er;

    DELETE
    FROM dbo.EntityRelationships 
    FROM dbo.EntityRelationships er
        INNER JOIN deleted d ON er.EntityIDChild = d.EntityID OR er.EntityIDParent = d.EntityID;

    DELETE 
    FROM dbo.Entities
    FROM dbo.Entities e
        INNER JOIN deleted d ON e.EntityID = d.EntityID;
END;
GO
Run Code Online (Sandbox Code Playgroud)

在这里,我们将测试该设置:

INSERT INTO dbo.Entities (EntityName)
VALUES ('Grandparent')
    , ('Parent')
    , ('Child');

INSERT INTO dbo.EntityRelationships (EntityIDParent, EntityIDChild)
VALUES (1, 2)
    , (2, 3);

SELECT Parents.EntityName
    , Children.EntityName
FROM dbo.EntityRelationships er
    INNER JOIN dbo.Entities Parents ON er.EntityIDParent = Parents.EntityID
    INNER JOIN dbo.Entities Children ON er.EntityIDChild = Children.EntityID;
Run Code Online (Sandbox Code Playgroud)

上面选择的结果:

?????????????????????????????????
? 实体名称 ?实体名称 ?
?????????????????????????????????
? 祖父母?家长?
? 家长?孩子 ?
?????????????????????????????????

在这里,我们将删除“Parent”实体,并重新查询关系:

DELETE 
FROM dbo.Entities
WHERE dbo.Entities.EntityName = 'Parent';

SELECT Parents.EntityName
    , Children.EntityName
FROM dbo.EntityRelationships er
    INNER JOIN dbo.Entities Parents ON er.EntityIDParent = Parents.EntityID
    INNER JOIN dbo.Entities Children ON er.EntityIDChild = Children.EntityID;
Run Code Online (Sandbox Code Playgroud)

结果:

?????????????????????????????????
? 实体名称 ?实体名称 ?
?????????????????????????????????
? 祖父母?孩子 ?
?????????????????????????????????

请注意,运行DELETE FROM dbo.Entities(不带WHERE子句)将删除两个表中的所有行。

展示一个稍微复杂的例子;假设您有 2 个祖父母、2 个父母和一个孩子:

INSERT INTO dbo.Entities (EntityName)
VALUES ('Grandparent 1')
    , ('Grandparent 2')
    , ('Parent 1')
    , ('Parent 2')
    , ('Child');

INSERT INTO dbo.EntityRelationships (EntityIDParent, EntityIDChild)
VALUES (1, 3)
    , (2, 3)
    , (1, 4)
    , (3, 5)
    , (4, 5);

SELECT Parents.EntityName
    , Children.EntityName
FROM dbo.EntityRelationships er
    INNER JOIN dbo.Entities Parents ON er.EntityIDParent = Parents.EntityID
    INNER JOIN dbo.Entities Children ON er.EntityIDChild = Children.EntityID;
Run Code Online (Sandbox Code Playgroud)
?????????????????????????????????
? 实体名称 ?实体名称 ?
?????????????????????????????????
? 祖父母 1 ? 家长 1 ?
? 祖父母 1 ? 家长 2 ?
? 祖父母 2 ? 家长 1 ?
? 家长 1 ? 孩子 ?
? 家长 2 ? 孩子 ?
?????????????????????????????????

如果我们Parent 1Entities表中删除:

DELETE 
FROM dbo.Entities
WHERE dbo.Entities.EntityName = 'Parent 1';
Run Code Online (Sandbox Code Playgroud)

我们看到这一点:

?????????????????????????????????
? 实体名称 ?实体名称 ?
?????????????????????????????????
? 祖父母 1 ? 家长 2 ?
? 祖父母 1 ? 孩子 ?
? 祖父母 2 ? 孩子 ?
? 家长 2 ? 孩子 ?
?????????????????????????????????

这会清理我们的测试数据:

IF OBJECT_ID(N'dbo.EntityRelationships', N'U') IS NOT NULL
DROP TABLE dbo.EntityRelationships;

IF OBJECT_ID(N'dbo.Entities', N'U') IS NOT NULL
DROP TABLE dbo.Entities;
GO
Run Code Online (Sandbox Code Playgroud)


Eva*_*oll 7

PostgreSQL 和 ltree

如果您使用的是 PostgreSQL,您可以查看ltree哪个执行此操作并保持正常和可索引。

CREATE EXTENSION ltree; -- required if you don't have it.

CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
Run Code Online (Sandbox Code Playgroud)

现在您可以删除Top.Science.Astronomy,并且仍然可以轻松查询所有属于Top.Sciencewith 的后代关系@>

DELETE FROM test
WHERE path = 'Top.Science.Astronomy';

SELECT *
FROM test
WHERE 'Top.Science' @> path;
Run Code Online (Sandbox Code Playgroud)


fgb*_*ist 3

我只是想分享我实际上最终选择了哪种配置以及原因(因为它与当前的解决方案不同):

只需将外键添加到Grandchild跟踪其Parent.

PostgreSQL 代码示例:

CREATE TABLE Parent (
  id INT PRIMARY KEY
);

CREATE TABLE Child (
  id INT PRIMARY KEY,
  parent_id INT REFERENCES Parent(id) NOT NULL ON DELETE CASCADE
);

CREATE TABLE Grandchild (
  id INT PRIMARY KEY,
  child_id INT REFERENCES Child(id),
  parent_id INT REFERENCES Parent(id) NOT NULL ON DELETE CASCADE
);
Run Code Online (Sandbox Code Playgroud)

最后,可以使用触发器来确保孙子具有Parent与其相同的Child.

我知道这可能会使数据库设计变得非规范化,但它看起来比这里的其他答案更容易实现,而且它也可以与开箱即用的 ORM 完美配合。如果你父母的祖父母永远无法改变,那么这是一个完美的妥协(关于非规范化)。

我很乐意听取有关此解决方案的任何建议/评论。