SQL Server中是否可以实现多行唯一性?

时间:2016-05-13 09:42:44

标签: sql-server

我有一张桌子,我尝试定义像这样的协变量gouping

ID Rank  Covariate
 1    1  Age
 1    2  Gender
 1    3  YearOfBirth
 2    1  Gender

ID捕获哪些协变量属于同一组。协变组1(ID = 1)由年龄,性别和出生年份组成,而第2组仅为性别。

现在,插入一个由(仅限性别)组成的新协变量组应该是非法的,因为该组已经存在,但是应该允许插入由Age和Gender组成的新组(它是组1的子集但不是完全匹配)。

排名也很重要

ID Rank  Covariate
      2  Age
      1  Gender
      3  YearOfBirth

不应被视为与第1组相同。

有没有办法在sql-server中强制执行此操作?

理想情况下,ID列会自动增加合法插入内容(但这是一个不同的问题)。

2 个答案:

答案 0 :(得分:1)

很明显,没有办法生成一个可执行的唯一约束,该约束在多行中重复,因为如果它重复,则它不是唯一的。

然而,有许多聪明的方法可以创建一个简单的检查,以确保不会多次插入Covariate值的分组。

就简单性而言,下面的SQL将产生两列:一个ID,以及协变量值的有序出现:

CREATE TABLE #tmp_Covariate (ID INT, RANK INT, Covariate VARCHAR(24))

INSERT INTO #tmp_Covariate (ID, RANK, Covariate)
VALUES   (1,1,'Age')
        ,(1,2,'Gender')
        ,(1,3,'YearOfBirth')
        ,(2,1,'Gender')

SELECT   DISTINCT ID
        ,STUFF((SELECT   N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
                  FROM   #tmp_Covariate C2
                 WHERE   C1.ID = C2.ID
                 ORDER
                    BY   C2.ID,C2.RANK
                   FOR   XML PATH ('')),1,2,'') AS GroupCovariate
 FROM   #tmp_Covariate C1

SELECT的结果如下:

ID  GroupCovariate
1   Age, Gender, YearOfBirth
2   Gender

如果将第三组添加到表中,则协变量值为:

ID Rank  Covariate
      2  Age
      1  Gender
      3  YearOfBirth

然后Covariates的有序出现与上面返回的GroupCovariate列不匹配。

如果我解决这个问题,我会创建一个接受表值参数的函数。将需要根据表格检查的输入输入到表中,就像成功提交时一样。

DECLARE @TVP TABLE (Rank INT, Covariate VARCHAR(24))

INSERT INTO @TVP(Rank, Covariate) VALUES (1,'Age'),(2,'Gender'),(3,'YearOfBirth')

SELECT  COUNT(CheckTable.GroupCovariate) AS Exist
FROM     (SELECT STUFF((SELECT   N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
            FROM @TVP C2
           ORDER
              BY C2.RANK
             FOR XML PATH ('')),1,2,'') AS GroupCovariate
          ) AS InputTable
JOIN     (SELECT DISTINCT ID
                ,STUFF((SELECT   N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
           FROM  #tmp_Covariate C2
          WHERE  C1.ID = C2.ID
          ORDER
             BY  C2.ID,C2.RANK
            FOR  XML PATH ('')),1,2,'') AS GroupCovariate
            FROM    #tmp_Covariate C1) AS CheckTable
  ON     CheckTable.GroupCovariate = InputTable.GroupCovariate

因为提供的协变量组已经存在于表中,所以输出将为1(如果没有组不存在则可以作为bool返回true,或者0表示false)。

Exist
1

如果我提供“FavoriteColor”作为我的协变量的一部分:

DECLARE @TVP TABLE (Rank INT, Covariate VARCHAR(24))

INSERT INTO @TVP(Rank, Covariate) VALUES (1,'FavoriteColor'),(2,'Gender'),(3,'YearOfBirth')

SELECT  COUNT(CheckTable.GroupCovariate) AS Exist
FROM     (SELECT STUFF((SELECT   N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
            FROM @TVP C2
           ORDER
              BY C2.RANK
             FOR XML PATH ('')),1,2,'') AS GroupCovariate
          ) AS InputTable
JOIN     (SELECT DISTINCT ID
                ,STUFF((SELECT   N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
           FROM  #tmp_Covariate C2
          WHERE  C1.ID = C2.ID
          ORDER
             BY  C2.ID,C2.RANK
            FOR  XML PATH ('')),1,2,'') AS GroupCovariate
            FROM    #tmp_Covariate C1) AS CheckTable
  ON     CheckTable.GroupCovariate = InputTable.GroupCovariate

我的结果是0:

Exist
0

答案 1 :(得分:1)

我不知道通过标准唯一性约束或检查约束或任何其他优雅解决方案来强制执行Covariant组唯一性标准的任何方法。但是,您可以通过仅允许通过存储过程访问表或者定义“INSTEAD OF INSERT”触发器的视图来强制执行约束。

方法1 - 存储过程

以下示例显示了存储过程方法。首先,我们创建一个表值类型,以便我们可以将我们的协变组作为只读参数传递给我们的存储过程。

    CREATE TYPE CovariateGroupEntry AS TABLE 
    (
         [Rank]         INT NOT NULL
        ,[Covariate]    NVARCHAR(50)
        PRIMARY KEY([Rank], [Covariate])
    )

接下来,我们创建包含协变组的基表:

    CREATE TABLE CovariateGroups 
    (
         [ID]           INT NOT NULL
        ,[Rank]         INT NOT NULL
        ,[Covariate]    NVARCHAR(50)
        PRIMARY KEY([ID], [Rank], [Covariate])
    )

下一步,我们创建一个虚拟表,用于自动生成我们的ID:

    CREATE TABLE CovariateGroupIDs
    (
         [GroupID] INT PRIMARY KEY IDENTITY
        ,[CreatedDateTime] DATETIME NOT NULL
    )

最后一步我们创建了我们的程序:

    CREATE PROCEDURE CovariateGroup_Add
    (
        @covariateGroupEntry dbo.CovariateGroupEntry READONLY
    )
    AS 
    BEGIN

        SET NOCOUNT ON;

        DECLARE @groupID INT;
        DECLARE @groupSize INT;
        DECLARE @groupMatchCount INT;
        DECLARE @minRank INT;
        DECLARE @maxRankDelta INT;
        DECLARE @minRankDelta INT;

        -- Get the size of the new group which user will attempt to add.
        SELECT  @groupSize = COUNT([Rank]) 
        FROM    @covariateGroupEntry

        -- Validate that the new group rank starts at 1 and increments by 1 step value only.
        SELECT   @minRank = ISNULL(MIN([Rank]), 0)
                ,@maxRankDelta = ISNULL(MAX(Delta), 0)
                ,@minRankDelta = ISNULL(MIN(Delta), 0)
        FROM    (
                    SELECT  [Rank]
                            ,[Rank] - (LAG([Rank], 1, 0) OVER (ORDER BY [Rank])) AS Delta
                    FROM    @covariateGroupEntry
                ) RankValidation

        IF ( (@minRank > 1) OR (@maxRankDelta > 1) OR (@minRankDelta < 1) )
        BEGIN
            -- Raise an error if our input data sets rank column does not start at 1 or does not increment by 1 as expected.
            RAISERROR (N'Attempting to add covariant group with invalid rank order.', -- Message text.
                       15, -- Severity,
                       1 -- State
                       ); -- Second argument.
        END
        ELSE
        BEGIN

            -- Generate a new group ID
            INSERT INTO [dbo].[CovariateGroupIDs]
            (
                [CreatedDateTime]
            )
            SELECT GETDATE() AS [CreatedDateTime]

            SET @groupID = SCOPE_IDENTITY();


            WITH CTE_GroupsCompareSize 
            AS
            (
                -- Compare the size of the new group with all of the existing groups. If the size is different we can 
                -- safely assume that the group is either a sub set or super set of the compared group. These groups 
                -- can be excluded from further consideration.
                SELECT           [CovariateGroups].[ID] 
                                ,[CovariateGroups].[Rank]   
                                ,[CovariateGroups].[Covariate]
                                ,COUNT([CovariateGroups].[Rank]) OVER (PARTITION BY [CovariateGroups].[ID]) GroupSize
                                ,@groupSize AS NewGroupSize
                FROM             [CovariateGroups]
            )
            ,CTE_GroupsCompareRank
            AS
            (
                -- For groups of the same size left outer join the new group on the original groups on both rank and covariant entry.  
                -- If the MIN() OVER window function return a value of 0 then there is at least on entry in the compared groups that does 
                -- not match and is therefore deemed different.
                SELECT           [OrginalGroup].[ID]
                                ,[OrginalGroup].[Rank]
                                ,[OrginalGroup].[Covariate]
                                ,MIN(
                                    CASE
                                        WHEN [NewGroup].[Covariate] IS NULL THEN 0
                                        ELSE 1
                                    END
                                 ) OVER (PARTITION BY [OrginalGroup].[ID]) AS EntireGroupRankMatch
                FROM             CTE_GroupsCompareSize [OrginalGroup]
                LEFT OUTER JOIN  @covariateGroupEntry [NewGroup] ON ([OrginalGroup].[Rank] = [NewGroup].[Rank] AND [OrginalGroup].[Covariate] = [NewGroup].[Covariate])
                WHERE   GroupSize = NewGroupSize
            )
            SELECT  @groupMatchCount = COUNT(EntireGroupRankMatch)
            FROM    CTE_GroupsCompareRank
            WHERE   EntireGroupRankMatch = 1

            IF ISNULL(@groupMatchCount, 0) = 0 
            BEGIN

                INSERT INTO [CovariateGroups]
                (
                     [ID]       
                    ,[Rank] 
                    ,[Covariate]
                )
                SELECT   @groupID AS [ID]
                        ,[Rank] 
                        ,[Covariate]
                FROM    @covariateGroupEntry
            END
            ELSE
            BEGIN

                -- Raise an error if our uniqueness constraints are not met.
                RAISERROR (N'Uniqueness contain violation, the covariant set is not unique with table "CovariateGroups".', -- Message text.
                           15, -- Severity,
                           1 -- State
                           ); -- Second argument.

            END
        END

    END

方法2 - 使用触发器查看

第二种方法涉及使用视图并在视图上创建而不是插入触发器。

首先我们创建视图如下:

    CREATE VIEW CovariateGroupsView
    AS
    SELECT    [ID]      
            ,[Rank]     
            ,[Covariate]
    FROM     CovariateGroups

然后我们创建触发器:

    ALTER TRIGGER CovariateGroupsViewInsteadOfInsert on CovariateGroupsView
    INSTEAD OF INSERT
    AS
    BEGIN

        DECLARE @groupID INT;
        DECLARE @groupSize INT;
        DECLARE @groupMatchCount INT;
        DECLARE @minRank INT;
        DECLARE @maxRankDelta INT;
        DECLARE @minRankDelta INT;

        -- Get the size of the new group which user will attempt to add.
        SELECT  @groupSize = COUNT([Rank]) 
        FROM    inserted

        -- Validate that the new group rank starts at 1 and increments by 1 step value only.
        SELECT   @minRank = ISNULL(MIN([Rank]), 0)
                ,@maxRankDelta = ISNULL(MAX(Delta), 0)
                ,@minRankDelta = ISNULL(MIN(Delta), 0)
        FROM    (
                    SELECT  [Rank]
                            ,[Rank] - (LAG([Rank], 1, 0) OVER (ORDER BY [Rank])) AS Delta
                    FROM    inserted
                ) RankValidation

        IF ( (@minRank > 1) OR (@maxRankDelta > 1) OR (@minRankDelta < 1) )
        BEGIN
            RAISERROR (N'Attempting to add covariant group with invalid rank order.', -- Message text.
                       15, -- Severity,
                       1 -- State
                       ); -- Second argument.
        END
        ELSE
        BEGIN

            -- Generate a new group ID
            INSERT INTO [dbo].[CovariateGroupIDs]
            (
                [CreatedDateTime]
            )
            SELECT GETDATE() AS [CreatedDateTime]

            SET @groupID = SCOPE_IDENTITY();


            WITH CTE_GroupsCompareSize 
            AS
            (
                -- Compare the size of the new group with all of the existing groups. If the size is different we can 
                -- safely assume that the group is either a sub set or super set of the compared group. These groups 
                -- can be excluded from further consideration.
                SELECT           [CovariateGroups].[ID] 
                                ,[CovariateGroups].[Rank]   
                                ,[CovariateGroups].[Covariate]
                                ,COUNT([CovariateGroups].[Rank]) OVER (PARTITION BY [CovariateGroups].[ID]) GroupSize
                                ,@groupSize AS NewGroupSize
                FROM             [CovariateGroups]
            )
            ,CTE_GroupsCompareRank
            AS
            (
                -- For groups of the same size left outer join the new group on the original groups on both rank and covariant entry.  
                -- If the MIN() OVER window function return a value of 0 then there is at least on entry in the compared groups that does 
                -- not match and is therefore deemed different.
                SELECT           [OrginalGroup].[ID]
                                ,[OrginalGroup].[Rank]
                                ,[OrginalGroup].[Covariate]
                                ,MIN(
                                    CASE
                                        WHEN [NewGroup].[Covariate] IS NULL THEN 0
                                        ELSE 1
                                    END
                                 ) OVER (PARTITION BY [OrginalGroup].[ID]) AS EntireGroupRankMatch
                FROM             CTE_GroupsCompareSize [OrginalGroup]
                LEFT OUTER JOIN  inserted [NewGroup] ON ([OrginalGroup].[Rank] = [NewGroup].[Rank] AND [OrginalGroup].[Covariate] = [NewGroup].[Covariate])
                WHERE   GroupSize = NewGroupSize
            )
            SELECT  @groupMatchCount = COUNT(EntireGroupRankMatch)
            FROM    CTE_GroupsCompareRank
            WHERE   EntireGroupRankMatch = 1

            IF ISNULL(@groupMatchCount, 0) = 0 
            BEGIN

                INSERT INTO [CovariateGroups]
                (
                     [ID]       
                    ,[Rank] 
                    ,[Covariate]
                )
                SELECT   @groupID AS [ID]
                        ,[Rank] 
                        ,[Covariate]
                FROM    inserted
            END
            ELSE
            BEGIN

                RAISERROR (N'Uniqueness contain violation, the covariant set is not unique with table "CovariateGroups".', -- Message text.
                           15, -- Severity,
                           1 -- State
                           ); -- Second argument.

            END
        END

    END;

以下示例显示了应如何执行存储过程:

    DECLARE @covariateGroupEntry AS dbo.CovariateGroupEntry

    -- INSERT GROUP 1 -------------------
    INSERT INTO   @covariateGroupEntry
    (
        [Rank]
        ,[Covariate]
    ) 
    SELECT 1  ,'Age' UNION ALL
    SELECT 2  ,'Gender' UNION ALL
    SELECT 3  ,'YearOfBirth' 

    EXEC CovariateGroup_Add @covariateGroupEntry

以下示例显示了如何使用视图插入组:

    DECLARE @covariateGroupEntry AS dbo.CovariateGroupEntry

    -- INSERT GROUP 1 -------------------
    INSERT INTO   @covariateGroupEntry
    (
         [Rank]
        ,[Covariate]
    ) 
    SELECT 1  ,'Age' UNION ALL
    SELECT 2  ,'Gender' UNION ALL
    SELECT 3  ,'YearOfBirth' 

    INSERT INTO [dbo].[CovariateGroupsView]
    (
         [Rank]
        ,[Covariate]
    )
    SELECT   [Rank]
            ,[Covariate]
    FROM    @covariateGroupEntry

    DELETE @covariateGroupEntry -- Delete our memory table if we intend to use it again.

一般情况下,我会避免使用view方法,因为它会比存储过程更容易受到边缘情况的影响,并且可能会有一些意外的行为。示例以下调用:

    INSERT INTO [dbo].[CovariateGroupsView]
    (
         [Rank]
        ,[Covariate]
    )
    SELECT 1  ,'Age' UNION ALL
    SELECT 2  ,'Gender' UNION ALL
    SELECT 3  ,'YearOfBirth' 

由于视图上的触发器会将每一行视为单独的数据集/组,因此无法按预期工作。因此,验证检查将失败。