2017-08-24 128 views
1

在关系数据库(SQL)中,我有一个可以有0..n个相关子实体的父实体。父母实体部分由其相关子实体的集合来唯一标识,这样我就不应该有两个类似的父母拥有同一个孩子集合。实施相关实体的唯一性

所以我可以有父1子1和子项2,和父母2子2和儿童3,但我不能有儿童2儿童另一名家长3.

理想情况下,我想使用数据库约束强制执行此唯一性。我曾考虑过将所有子记录的散列存储到父项中,但是想知道是否有更简单/更标准的方法来完成此操作。

任何想法?

+0

棘手。我们是否允许带有子1,2 *和* 3的“父母3”来扩展您的示例?另外,请记住哈希值告诉你两件事情是完全不同的,但*不要*告诉你两件事情是一样的。 –

+0

另外,还有一个(合理的)儿童总数的上限? –

+0

@Damien_The_Unbeliever是的。我们可以让一个父亲拥有另一个集合的超集,但是没有两个父母的集合应该是平等的。至于哈希,我希望能够使它足够大,使碰撞机会可以忽略不计。我会说我需要哈希来区分不超过100个独特的儿童组合。 –

回答

2

这种约束是棘手,因为SQL没有关系平等的运营商,即evaluting A = B,其中A的没有简单的方法和B是成组的行。标准SQL确实支持嵌套表,但不幸的是SQL Server不支持。

一个可能的答案是类似下面的断言,这将检查表中的任一相同的家庭:

NOT EXISTS (
    SELECT 1 
    FROM family f, family g 
    WHERE f.child = g.child 
    AND f.parent <> g.parent 
    GROUP BY f.parent, g.parent 
    HAVING COUNT(*) = (SELECT COUNT(*) FROM family WHERE parent = f.parent) 
    AND COUNT(*) = (SELECT COUNT(*) FROM family WHERE parent = g.parent) 
    ) 

注意,此查询不试图处理不带孩子的家庭。在集合论上,两个空集合必然是相同的。如果你想允许无子女的家庭,那么你将不得不决定是否应该认定两个无子女的家庭是否相同。

SQL不是一种真正的关系语言,它远远不够关系语言应该具备的功能。教程D是支持关系相等和关系值属性的真正关系语言的一个例子。在教程D中,原则上可以将每个家族表示为relvar中单个属性的值。这个家庭属性也可能是一个关键,因此不允许重复的家庭。

+0

不错,看起来像我们有类似的想法......这是光滑的,似乎它会比我的回答表现更好。谢谢。 –

+0

我太仓促了。我原来的查询没有给出正确的结果,所以我编辑了它。 – sqlvogel

1

感谢那些建议使用触发器的人的帮助。这大致是我所拥有的,似乎正在发挥作用。

CREATE TRIGGER [dbo].[trig_Parent_Child_Uniqueness] 
ON [dbo].[Parent_Child] 
AFTER INSERT, UPDATE 
AS 
BEGIN 
    IF EXISTS (
     SELECT 1 
     FROM Parent p1 
     --Compare each pair of parents 
     JOIN Parent p2 ON p1.ParentId <> p2.ParentId 
     WHERE NOT EXISTS (
      --Find any children that are different 
      SELECT 1 
      FROM (
       SELECT ChildId FROM Parent_Child c1 
       WHERE c1.ParentId = p1.ParentId 
      ) as c1 
      FULL OUTER JOIN (
       SELECT ChildId FROM Parent_Child c2 
       WHERE c2.ParentId = p2.ParentId 
      ) as c2 ON c2.ChildId = c1.ChildId 
      WHERE c1.ChildId IS NULL OR c2.ChildId IS NULL 
     ) 
    ) ROLLBACK; 
END; 

编辑:或者更好的解决方案,从@sqlvogel改编

CREATE TRIGGER [dbo].[trig_Parent_Child_Uniqueness] 
ON [dbo].[Parent_Child] 
AFTER INSERT, UPDATE 
AS 
BEGIN 
    IF EXISTS (
     SELECT 1 
     FROM Parent_Child p1 
     FULL JOIN Parent_Child p2 ON p1.ParentId <> p2.ParentId 
      AND p1.ChildId = p2.ChildId 
     GROUP BY p1.ParentId 
     HAVING COUNT(p1.ParentId) = COUNT(*) 
      AND COUNT(p2.ParentId) = COUNT(*) 
    ) ROLLBACK; 
END; 
0

这是一个有点恶心的,因为它包括触发器和光标:(

它包括在父表中的列,其是基于孩子

设置:

CREATE TAble Parent 
(
    Id INT Primary Key, 
    Name VARCHAR(50), 
    ChildItems VARCHAR(200) NOT NULL UNIQUE 
) 
CREATE TABLE Child 
(
    Id INT Primary Key, 
    Name VARCHAR(50) 
) 

CREATE TABLE ParentChild 
(
    Id INT Identity Primary Key, 
    ParentId INT, 
    ChildId Int 
) 

触发器

-- This gives the unique colmn a default based upon the id of the parent 
CREATE TRIGGER trg_Parent ON Parent 
INSTEAD OF Insert 
AS 
    SET NOCOUNT ON 
    INSERT INTO Parent (Id, Name, ChildItems) 
    SELECT Id, Name, '/' + CAST(Id As Varchar(10)) + '/' 
    FROM Inserted 
GO 

-- This updates the parent with a path based upon child items 
-- If a the exact same child items exist for another parent then this fails 
-- because of the unique index 

CREATE Trigger trg_ParentChild ON ParentChild 
AFTER Insert, Update 
AS 
    DECLARE @ParentId INT = 0 
    DECLARE @ChildItems VARCHAR(8000) = '' 

    DECLARE parentCursor CURSOR FOR 
     SELECT DISTINCT ParentId 
     FROM Inserted 

    OPEN parentCursor 
    FETCH NEXT FROM parentCursor INTO @ParentId 

    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     SELECT @ChildItems = COALESCE(@ChildItems + '/ ', '') + CAST(ChildID As Varchar(10)) 
     FROM ParentChild 
     WHERE ParentId = @ParentId 
     ORDER BY ChildId 

     UPDATE Parent 
      SET ChildItems = @ChildITems 
     WHERE Id = @ParentId 

     FETCH NEXT FROM parentCursor INTO @ParentId 
     SET @ChildItems = '' 
    END 
    CLOSE parentCursor 
    DEALLOCATE parentCursor 

GO 

数据设置

INSERT INTO Parent (Id, Name) 
VALUES (1, 'Parent1'), (2,'Parent2'), (3, 'Parent3') 



INSERT INTO Child (Id, Name) 
VALUES (1,'Child1'), (2,'Child2'), (3,'Child3'), (4,'Child4') 

现在插入一些数据

-- This one succeeds 
INSERT INTO ParentChild (ParentId, ChildId) 
VALUES (1,1),(1,2),(2,2),(2,3) 

-- This one Fails 
INSERT INTO ParentChild (ParentId, ChildId) VALUES (3,1),(3,2)