SQL Server 唯一的自动增量列在另一列的上下文中
Posted
技术标签:
【中文标题】SQL Server 唯一的自动增量列在另一列的上下文中【英文标题】:SQL Server unique auto-increment column in the context of another column 【发布时间】:2011-01-13 09:14:51 【问题描述】:假设表格有两列:
ParentEntityId int foreign key
Number int
ParentEntityId
是另一个表的外键。
Number
是一个本地 身份,即它在单个ParentEntityId
中是唯一的。
通过这两列的唯一键很容易实现唯一性。
如何使Number
在插入时ParentEntityId
的上下文中自动递增?
附录 1
为了澄清问题,这里有一个摘要。
ParentEntity
有多个ChildEntity
,每个ChiildEntity
在其ParentEntity
的上下文中应该有一个唯一的增量Number
。
附录 2
将ParentEntity
视为客户。
将ChildEntity
视为订单。
因此,每个客户的订单都应该编号为 1、2、3 等等。
【问题讨论】:
请用 sql 术语澄清“在它的父实体的上下文中”...唯一的上下文是记录所在的表的上下文。关系是通过 FK 约束实现的,您已经有了一个,如果事实上 1:M 是你所追求的。 “上下文相关”号码的用途是什么? @Sky:请看一下附录 2。希望它能阐明意图。 【参考方案1】:好吧,这种类型的列没有原生支持,但您可以使用触发器来实现它:
CREATE TRIGGER tr_MyTable_Number
ON MyTable
INSTEAD OF INSERT
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN;
WITH MaxNumbers_CTE AS
(
SELECT ParentEntityID, MAX(Number) AS Number
FROM MyTable
WHERE ParentEntityID IN (SELECT ParentEntityID FROM inserted)
)
INSERT MyTable (ParentEntityID, Number)
SELECT
i.ParentEntityID,
ROW_NUMBER() OVER
(
PARTITION BY i.ParentEntityID
ORDER BY (SELECT 1)
) + ISNULL(m.Number, 0) AS Number
FROM inserted i
LEFT JOIN MaxNumbers_CTE m
ON m.ParentEntityID = i.ParentEntityID
COMMIT
未经测试,但我很确定它会起作用。如果您有主键,您也可以将其实现为 AFTER
触发器(我不喜欢使用 INSTEAD OF
触发器,当您需要在 6 个月后修改它们时它们更难理解)。
只是为了解释这里发生了什么:
SERIALIZABLE
是最严格的隔离模式;它保证一次只有一个数据库事务可以执行这些语句,我们需要这些语句来保证这个“序列”的完整性。请注意,这会不可逆地促进整个事务,因此您不希望在长时间运行的事务中使用它。
CTE 获取已用于每个父 ID 的最大数字;
ROW_NUMBER
从数字 1 开始为每个父 ID (PARTITION BY
) 生成一个唯一序列;如果有一个来获取新序列,我们将其添加到之前的最大值。
我可能还应该提到,如果您一次只需要插入一个新的子实体,您最好通过存储过程而不是使用触发器来汇集这些操作 - 您肯定会获得更好的性能出它。这就是目前在 SQL '08 中使用 hierarchyid
列的方式。
【讨论】:
@Aaronaught:是的,我将拥有一个身份主键。在AFTER
触发器的情况下如何简化此代码?老实说,我对你的INSTEAD OF
触发代码不是很了解。
@Anton:我不会说将其设置为AFTER
触发器会简化——它只是将INSERT
更改为UPDATE
和JOIN
。但是INSTEAD OF
触发器有各种烦人的限制,即每个表只能有一个,而且重新插入inserted
数据对我来说总是很奇怪。但这就是它的工作原理。希望我添加的注释可以帮助您更好地理解它。
谢谢你,@Aaronaught。我将按照您的建议使用触发器。接受。【参考方案2】:
需要添加OUTPUT
子句来触发 Linq to SQL 的兼容性。
例如:
INSERT MyTable (ParentEntityID, Number)
OUTPUT inserted.*
SELECT
i.ParentEntityID,
ROW_NUMBER() OVER
(
PARTITION BY i.ParentEntityID
ORDER BY (SELECT 1)
) + ISNULL(m.Number, 0) AS Number
FROM inserted i
LEFT JOIN MaxNumbers_CTE m
ON m.ParentEntityID = i.ParentEntityID
【讨论】:
【参考方案3】:这解决了我理解的问题::-)
DECLARE @foreignKey int
SET @foreignKey = 1 -- or however you get this
INSERT Tbl (ParentEntityId, Number)
VALUES (@foreignKey, ISNULL((SELECT MAX(Number) FROM Tbl WHERE ParentEntityId = @foreignKey), 0) + 1)
【讨论】:
这种方法能保证线程安全吗?我的意思是不会出现两条具有相同Number
值的记录吗?可能,它应该在触发器中完成......
@roufamatic:Anton 询问是否可以自动增加数字。根据我对问题的理解,调用 select MAX(...)
看起来不像是自动生成数字
@Anton -- 我们可以用一个 applock 围绕它来保证线程安全。 @Sung Meister——我同意不同意“自动”的定义。 :-) 这会在调用时自动更新 Number 字段。以上是关于SQL Server 唯一的自动增量列在另一列的上下文中的主要内容,如果未能解决你的问题,请参考以下文章
SQL Server:对列的每个组值求和(或差),直到在另一列上满足条件
SQL select row where(这一列在另一列中有许多不同的值)
在 MySQL 中,如何在 INSERT 时使用另一列中的自动增量值?