我有一张桌子,我尝试定义像这样的协变量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列会自动增加合法插入内容(但这是一个不同的问题)。
答案 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'
由于视图上的触发器会将每一行视为单独的数据集/组,因此无法按预期工作。因此,验证检查将失败。