强制执行相关实体的唯一性

时间:2017-08-24 13:59:02

标签: sql-server database-design relational-database

在关系数据库(SQL)中,我有一个父实体,它可以有0..n相关的子实体。父实体部分地由其相关子实体的集合唯一地标识,使得我不应该具有两个具有相同子集的类似父母。

所以我可以让父母1与孩子1和孩子2,父母2与孩子2和孩子3,但我不能有另一个父母与孩子2和孩子3。

理想情况下,我想使用数据库约束强制执行此唯一性。我考虑过与父母一起存储所有子记录的哈希,但是想知道是否有更容易/更标准的方法来实现这一点。

有什么想法吗?

3 个答案:

答案 0 :(得分:3)

这种约束很棘手,因为SQL没有关系相等运算符,即没有简单的方法来评估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中单个属性的值。该家庭属性也可以是一个密钥,因此不允许重复的家庭。

答案 1 :(得分: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;

答案 2 :(得分: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)