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 更改为UPDATEJOIN。但是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 时使用另一列中的自动增量值?

SQL server select语句用于选择另一列的重复条目的ID

在另一列SQL的连接信息中查找一列信息

要在另一台服务器上解密的 sql server 加密列