fgb*_*ist 6 postgresql erd foreign-key database-design orm
我正在处理一组可以移除孩子的关系,但我显然不想失去孙子和父母之间的联系。我确实考虑过将孩子标记为“已故”(在这篇文章中使用相关术语),但后来我最终会被困在我的数据库中的一堆已故孩子,谁想要这样(只是为了保持关系)?
如果删除父项,则删除其所有后代。此外,它的工作方式类似于“正常”关系,其中孙子和子项始终具有相同的顶级父级。层次结构固定为 3 个级别(如上所示)。最后,Parent、Child 和 Grandchild 都是不同的类型(例如,我们不是在谈论 3 个“人类”,他们没有相同的基础)。
然而,让孙子跟踪父母感觉有点奇怪,因为这种关系通常可以从父子关系中推导出来。虽然,我想不出另一种方法。
这个模型有效吗?或者有不同的方法吗?
此答案基于您在澄清每个级别是不同类型之前的问题。由于您已经确定了对不同类型的需求,我同意我最初出现的答案,并且您的自我回答记录了您是如何解决这个问题的。 向孙表添加单列,引用最顶层的表,似乎是最简单的方法。
我将保留以下详细信息,以防它对未来的访问者有所帮助。
我会用交叉引用表来实现这一点。
下面是一个特定于 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 DELETE
在Entities
表上创建一个触发器,在删除已删除的实体之前,通过重新父级任何必要的关系来正确处理删除:
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 1
从Entities
表中删除:
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)
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.Science
with 的后代关系@>
DELETE FROM test
WHERE path = 'Top.Science.Astronomy';
SELECT *
FROM test
WHERE 'Top.Science' @> path;
Run Code Online (Sandbox Code Playgroud)
我只是想分享我实际上最终选择了哪种配置以及原因(因为它与当前的解决方案不同):
只需将外键添加到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 完美配合。如果你父母的祖父母永远无法改变,那么这是一个完美的妥协(关于非规范化)。
我很乐意听取有关此解决方案的任何建议/评论。
归档时间: |
|
查看次数: |
2486 次 |
最近记录: |