使用复合主键在非规范化仓库 table 中强制执行 1:1 和 1:Many 基数
Enforcing 1:1 and 1:Many cardinality in denormalized warehouse table with composite Primary Key
我有一个名为“Accounts
”的 table,其中 composite primary key
由 2 列组成:Account_key
和 Account_Start_date
都具有数据类型 int
和另一个名为 Accountnumber(bigint).
的非键列
Account_key 应该有一个或多个 Accountnumber(bigint)
而不是相反的意思 1 or many Accountnumber can only have 1 Account_key .
如果您尝试插入相同的 Account_key 和相同的 Account_Start_date ,那么 primary key constraint
当然会停止这样做,因为它们是主键。
但是,如果您插入现有的 Account_key 和不同的不存在的 Account_Start_date,那么您可以随心所欲地插入一个随机帐号,而不会受到任何限制,突然间您有了Account_key 和 Accountnumber 之间具有多对多关系的行,我们不想要 .
我尝试了很多限制条件,但都没有成功。我只是不知道我在这里做错了什么所以请继续帮助我,谢谢!
(注意:我不认为更改复合主键是一个选项,因为那样我们将失去缓慢变化的维度日期功能)
还有一个table(案例)1 'Account_Key'只能与1 'AccountNumber'相关,意思是1..1的关系,除了有它们之间应该是 1..1 关系。
唯一索引至少对我没有用,只要考虑我是否想更改 Accounts
table 或放置触发器甚至索引,这样 'Account_Key' 和之间的关系将是 1..1 'AccountNumber', ?
如果我没理解错的话,你想要:
- 任何给定的 AccountNumber 只能与一个 AccountKey 关联
- 任何给定的 AccountKey 都可以与多个 AccountNumbers 相关联
如果这是正确的,您可以使用调用 UDF 的 CHECK CONSTRAINT
来实现。
编辑:
CHECK CONSTRAINT 的伪逻辑可以是:
IF EXISTS anotherRow
WHERE theOtherAccountNumber = thisAccountNumber
AND theOtherAccountKey <> thisAccountKey
THEN False (do not allow this row to be inserted)
ELSE True (allow the insertion)
我会将此逻辑放在 returns true 或 false 的 UDF 中,以使 CHECK 约束更简单。
使用列 Account_Key 和 Account_Number 创建一个额外的 table AccountKeyNumbers(名称当然是您的选择)。
将Account_Number设为主键。
请注意,您不能将 Account_Number 添加两次,因此也不能 link 将其添加到此 table 中的两个不同的 Account_Key。
现在,您在 Account_Number 加上 Account_Key 上添加了一个额外的唯一约束。在此 table 中,您放入所有帐号及其相应的密钥。
最后,您在帐户 table 的列 Account_Key 加上 Account_Number 上定义外键,引用 AccountKeyNumbers table 中的唯一约束。
您现在已确保只能将有效的 key/number 组合插入到帐户中,并且两个帐户密钥的编号不能相同。我们需要额外的唯一约束,它不会影响 AccountKeyNumbers table 的数据完整性,只是为了能够创建一个必须指向主约束或唯一约束的外键。
如果这是一个 OLTP table,解决方案是将数据正确规范化为两个 table,但这是一个 DW table,因此拥有它是有意义的一应俱全 table.
在这种情况下,您应该添加一个 FOR
/ AFTER
触发器 ON INSERT, UPDATE
来针对 inserted
伪 table 执行查询。查询可以是一个简单的 COUNT(DISTINCT Account_Key)
,连接回主 table(仅过滤 AccountNumber
值为 added/updated),在 GROUP BY
AccountNumber
然后 HAVING COUNT(DISTINCT Account_Key) > 1
。将该查询包装在 IF EXISTS
中,如果返回一行,则执行 ROLLBACK
取消 DML 操作,执行 RAISERROR
发送有关取消操作原因的错误消息,然后 RETURN
.
CREATE TRIGGER dbo.TR_TableName_PreventDuplicateAccountNumbers
ON dbo.TableName
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON;
IF (EXISTS(
SELECT COUNT(DISTINCT tab.Account_Key)
FROM dbo.TableName tab
INNER JOIN INSERTED ins
ON ins.AccountNumber = tab.AccountNumber
GROUP BY tab.AccountNumber
HAVING COUNT(DISTINCT tab.Account_Key) > 1
))
BEGIN
ROLLBACK;
RAISERROR(N'AccountNumber cannot be associated with more than 1 Account_Key', 16, 1);
RETURN;
END;
对于 "other" table,其中 Account_Key
和 AccountNumber
之间的关系是 1:1,您可以尝试执行以下操作:
DECLARE @Found BIT = 0;
;WITH cte AS
(
SELECT DISTINCT tab.Account_Key, tab.AccountNumber
FROM dbo.TableName tab
INNER JOIN INSERTED ins
ON ins.Account_Key = tab.Account_Key
OR ins.AccountNumber = tab.AccountNumber
), counts AS
(
SELECT c.[Account_Key],
c.[AccountNumber],
ROW_NUMBER() OVER (PARTITION BY c.[Account_Key
ORDER BY c.[Account_Key, c.[AccountNumber]) AS [KeyCount],
ROW_NUMBER() OVER (PARTITION BY c.[AccountNumber]
ORDER BY c.[AccountNumber], c.[Account_Key) AS [NumberCount]
FROM cte c
)
SELECT @Found = 1
FROM counts
WHERE [KeyCount] > 1
OR [NumberCount] > 1;
IF (@Found = 1)
BEGIN
ROLLBACK;
RAISERROR(N'AccountNumber cannot be associated with more than 1 Account_Key', 16, 1);
RETURN;
END;
我有一个名为“Accounts
”的 table,其中 composite primary key
由 2 列组成:Account_key
和 Account_Start_date
都具有数据类型 int
和另一个名为 Accountnumber(bigint).
Account_key 应该有一个或多个 Accountnumber(bigint)
而不是相反的意思 1 or many Accountnumber can only have 1 Account_key .
如果您尝试插入相同的 Account_key 和相同的 Account_Start_date ,那么 primary key constraint
当然会停止这样做,因为它们是主键。
但是,如果您插入现有的 Account_key 和不同的不存在的 Account_Start_date,那么您可以随心所欲地插入一个随机帐号,而不会受到任何限制,突然间您有了Account_key 和 Accountnumber 之间具有多对多关系的行,我们不想要 .
我尝试了很多限制条件,但都没有成功。我只是不知道我在这里做错了什么所以请继续帮助我,谢谢! (注意:我不认为更改复合主键是一个选项,因为那样我们将失去缓慢变化的维度日期功能)
还有一个table(案例)1 'Account_Key'只能与1 'AccountNumber'相关,意思是1..1的关系,除了有它们之间应该是 1..1 关系。
唯一索引至少对我没有用,只要考虑我是否想更改 Accounts
table 或放置触发器甚至索引,这样 'Account_Key' 和之间的关系将是 1..1 'AccountNumber', ?
如果我没理解错的话,你想要:
- 任何给定的 AccountNumber 只能与一个 AccountKey 关联
- 任何给定的 AccountKey 都可以与多个 AccountNumbers 相关联
如果这是正确的,您可以使用调用 UDF 的 CHECK CONSTRAINT
来实现。
编辑:
CHECK CONSTRAINT 的伪逻辑可以是:
IF EXISTS anotherRow
WHERE theOtherAccountNumber = thisAccountNumber
AND theOtherAccountKey <> thisAccountKey
THEN False (do not allow this row to be inserted)
ELSE True (allow the insertion)
我会将此逻辑放在 returns true 或 false 的 UDF 中,以使 CHECK 约束更简单。
使用列 Account_Key 和 Account_Number 创建一个额外的 table AccountKeyNumbers(名称当然是您的选择)。
将Account_Number设为主键。
请注意,您不能将 Account_Number 添加两次,因此也不能 link 将其添加到此 table 中的两个不同的 Account_Key。
现在,您在 Account_Number 加上 Account_Key 上添加了一个额外的唯一约束。在此 table 中,您放入所有帐号及其相应的密钥。
最后,您在帐户 table 的列 Account_Key 加上 Account_Number 上定义外键,引用 AccountKeyNumbers table 中的唯一约束。
您现在已确保只能将有效的 key/number 组合插入到帐户中,并且两个帐户密钥的编号不能相同。我们需要额外的唯一约束,它不会影响 AccountKeyNumbers table 的数据完整性,只是为了能够创建一个必须指向主约束或唯一约束的外键。
如果这是一个 OLTP table,解决方案是将数据正确规范化为两个 table,但这是一个 DW table,因此拥有它是有意义的一应俱全 table.
在这种情况下,您应该添加一个 FOR
/ AFTER
触发器 ON INSERT, UPDATE
来针对 inserted
伪 table 执行查询。查询可以是一个简单的 COUNT(DISTINCT Account_Key)
,连接回主 table(仅过滤 AccountNumber
值为 added/updated),在 GROUP BY
AccountNumber
然后 HAVING COUNT(DISTINCT Account_Key) > 1
。将该查询包装在 IF EXISTS
中,如果返回一行,则执行 ROLLBACK
取消 DML 操作,执行 RAISERROR
发送有关取消操作原因的错误消息,然后 RETURN
.
CREATE TRIGGER dbo.TR_TableName_PreventDuplicateAccountNumbers
ON dbo.TableName
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON;
IF (EXISTS(
SELECT COUNT(DISTINCT tab.Account_Key)
FROM dbo.TableName tab
INNER JOIN INSERTED ins
ON ins.AccountNumber = tab.AccountNumber
GROUP BY tab.AccountNumber
HAVING COUNT(DISTINCT tab.Account_Key) > 1
))
BEGIN
ROLLBACK;
RAISERROR(N'AccountNumber cannot be associated with more than 1 Account_Key', 16, 1);
RETURN;
END;
对于 "other" table,其中 Account_Key
和 AccountNumber
之间的关系是 1:1,您可以尝试执行以下操作:
DECLARE @Found BIT = 0;
;WITH cte AS
(
SELECT DISTINCT tab.Account_Key, tab.AccountNumber
FROM dbo.TableName tab
INNER JOIN INSERTED ins
ON ins.Account_Key = tab.Account_Key
OR ins.AccountNumber = tab.AccountNumber
), counts AS
(
SELECT c.[Account_Key],
c.[AccountNumber],
ROW_NUMBER() OVER (PARTITION BY c.[Account_Key
ORDER BY c.[Account_Key, c.[AccountNumber]) AS [KeyCount],
ROW_NUMBER() OVER (PARTITION BY c.[AccountNumber]
ORDER BY c.[AccountNumber], c.[Account_Key) AS [NumberCount]
FROM cte c
)
SELECT @Found = 1
FROM counts
WHERE [KeyCount] > 1
OR [NumberCount] > 1;
IF (@Found = 1)
BEGIN
ROLLBACK;
RAISERROR(N'AccountNumber cannot be associated with more than 1 Account_Key', 16, 1);
RETURN;
END;