2016-05-13 37 views
3

我有一個表,其中我嘗試定義像這樣SQL Server中可能存在多行唯一性?

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

其中協變量在同一組屬於一起ID捕獲協變量gouping。因此,協變量組1(ID = 1)由年齡,性別和出生年份組成,而組2僅爲性別。

現在,插入一個新的僅由性別組成的協變量組應該是非法的,因爲該組已經存在,但應該允許插入由年齡和性別組成的新組(但它不是組1的子集)完全匹配)。

而且排名事項所以

ID Rank Covariate 
     2 Age 
     1 Gender 
     3 YearOfBirth 

不應被視爲等於組1

有沒有辦法在SQL服務器來執行呢?

理想情況下,ID列會自動增加合法插入(但這是一個不同的問題)。

+0

我假設行列必須連續並從'1'開始?否則,僅插入性別但等級爲2的規則似乎既被您的規則所禁止也被允許...... –

+0

正確。等級從1開始,對於組中的每個協變量都加1。我應該指出這一點。 – mortysporty

回答

1

我不知道的任何手段強制執行的共變通過標準唯一性約束或檢查約束或任何其他優雅的解決方案來處理這些問題。但是,您可以通過僅允許通過存儲過程訪問表或者定義具有「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. 

一般來說,我會避免使用視圖方法,因爲它會比存儲過程更容易出現邊緣情況,並且可能會出現一些意外的行爲。以下調用示例:

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

由於視圖上的觸發器將每行視爲單獨的數據集/組,因此無法按預期工作。結果驗證檢查將失敗。

+0

你先生是一個學者和紳士。這工作完美!我對最近的回覆表示抱歉(5月16日和17日是挪威的公共假期,昨天我的盤子上還有很多其他東西)。我可以看到你真的認真解決了這個問題。對此,我真的非常感激。還要感謝@EastOfJupiter。我參加了這一次,但我也很感謝你在這方面的努力。 – mortysporty

1

很明顯,沒有辦法產生一個可重複執行的唯一約束,該約束在多行中重複,因爲如果它重複,那麼它不是唯一的。

但是,有許多巧妙的方法來創建一個簡單的檢查,以確保您的協變量值的分組不會被多次插入。

在簡單性方面的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 

然後,協變量的有序發生與上面返回的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(如果沒有組不存在可以被返回作爲用於布爾真,或0表示否)。

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 
+0

謝謝!我選擇了@Edmond Quinton發佈的版本,但我可以告訴你在認真對待這個問題上付出了一些努力,我真的很感激。此外,爲遲到的回覆appologies。 – mortysporty

+0

不用擔心@mortysporty。正如我在開幕式上所說的,有很多方法可以做到這一點。在我看來,這是最簡單,也是最可重用的方法(不需要將邏輯編碼到每個改變表數據的過程中)。在開始時投擲健康檢查功能,你就完成了。如果這些規則突然改變,那麼只有該功能需要改變。我喜歡那種簡單,個人:) – EastOfJupiter