如何获取序列中的下一个数字
How to get the next number in a sequence
我有一个 table 这样的:
+----+-----------+------+-------+--+
| id | Part | Seq | Model | |
+----+-----------+------+-------+--+
| 1 | Head | 0 | 3 | |
| 2 | Neck | 1 | 3 | |
| 3 | Shoulders | 2 | 29 | |
| 4 | Shoulders | 2 | 3 | |
| 5 | Stomach | 5 | 3 | |
+----+-----------+------+-------+--+
对于 Model 3,我如何在 Stomach
之后的下一个序列中插入另一条记录。所以这里是新的 table 假设的样子:
+----+-----------+------+-------+--+
| id | Part | Seq | Model | |
+----+-----------+------+-------+--+
| 1 | Head | 0 | 3 | |
| 2 | Neck | 1 | 3 | |
| 3 | Shoulders | 2 | 29 | |
| 4 | Shoulders | 2 | 3 | |
| 5 | Stomach | 5 | 3 | |
| 6 | Groin | 6 | 3 | |
+----+-----------+------+-------+--+
有没有一种方法可以制作一个插入查询,该查询将仅在模型 3 的最高序列之后给出下一个数字。另外,寻找并发安全的东西。
处理此类插入的正确方法是使用 identity
列,或者,如果您愿意,也可以使用列的序列和默认值。
但是,NULL
列的值似乎不正确。
查询问题如:
Insert into yourtable(id, Part, Seq, Model)
Select 6, 'Groin', max(Seq) + 1, 3
From yourtable;
是两个这样的查询,同时 运行 可以产生相同的值。建议将 seq
声明为唯一标识列,让数据库完成所有工作。
由于您希望序列基于特定模型,因此只需在执行 select 时将其添加到 where 子句中即可。这将确保 Max(SEQ) 仅适用于该模型系列。此外,由于 SEQ 可以为 null,将其包装在 ISNULL 中,因此如果它为 null,它将为 0,因此 0 + 1 会将 next 设置为 1。
基本方法是:
Insert into yourtable(id, Part, Seq, Model)
Select 6, 'Groin', ISNULL(max(Seq),0) + 1, 3
From yourtable
where MODEL = 3;
让我们先列出挑战:
- 我们不能使用普通约束,因为存在空值,而且如果我们查看现有数据,我们还需要解决重复项和空缺问题。 没关系,我们会解决的;-> 在第 3 步中
我们需要并发操作的安全性(因此某种形式或混合的事务、隔离级别和可能的 "kinda SQL mutex"。)直觉认为这里是一个存储过程,原因有几个:
2.1 它更容易防止 sql 注入
2.2 我们可以更轻松地控制隔离级别(table 锁定)并从此类要求带来的一些问题中恢复
2.3 我们可以使用应用级数据库锁来控制并发
- 我们必须在每次插入时存储或查找下一个值。并发这个词已经告诉我们会有争用和可能的高吞吐量(否则请坚持使用单线程)。所以我们一定已经在想:在一个已经很复杂的世界里,不要从你想写的同一个 table 中读取。
那么在那个简短的前传中,让我们尝试一个解决方案:
首先,我们正在创建您的原始 table,然后还有一个 table 来保存我们设置为上次使用的序列 + 1:[=13 的序列 (BodyPartsCounter) =]
CREATE TABLE BodyParts
([id] int identity, [Part] varchar(9), [Seq] varchar(4), [Model] int)
;
INSERT INTO BodyParts
([Part], [Seq], [Model])
VALUES
('Head', NULL, 3),
('Neck', '1', 3),
('Shoulders', '2', 29),
('Shoulders', '2', 3),
('Stomach', '5', 3)
;
CREATE TABLE BodyPartsCounter
([id] int
, [counter] int)
;
INSERT INTO BodyPartsCounter
([id], [counter])
SELECT 1, MAX(id) + 1 AS id FROM BodyParts
;
然后我们需要创建可以发挥神奇作用的存储过程。简而言之,它充当互斥体,基本上保证您的并发性(如果您不对其他地方的相同 table 进行插入或更新)。然后获取下一个序列,更新它并插入新行。这一切都发生后,它将提交事务并为下一个等待调用线程释放存储过程。
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Charlla
-- Create date: 2016-02-15
-- Description: Inserts a new row in a concurrently safe way
-- =============================================
CREATE PROCEDURE InsertNewBodyPart
@bodypart varchar(50),
@Model int = 3
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRANSACTION;
-- Get an application lock in your threaded calls
-- Note: this is blocking for the duration of the transaction
DECLARE @lockResult int;
EXEC @lockResult = sp_getapplock @Resource = 'BodyPartMutex',
@LockMode = 'Exclusive';
IF @lockResult = -3 --deadlock victim
BEGIN
ROLLBACK TRANSACTION;
END
ELSE
BEGIN
DECLARE @newId int;
--Get the next sequence and update - part of the transaction, so if the insert fails this will roll back
SELECT @newId = [counter] FROM BodyPartsCounter WHERE [id] = 1;
UPDATE BodyPartsCounter SET [counter] = @newId + 1 WHERE id = 1;
-- INSERT THE NEW ROW
INSERT INTO dbo.BodyParts(
Part
, Seq
, Model
)
VALUES(
@bodypart
, @newId
, @Model
)
-- END INSERT THE NEW ROW
EXEC @lockResult = sp_releaseapplock @Resource = 'BodyPartMutex';
COMMIT TRANSACTION;
END;
END
GO
现在 运行 测试:
EXEC @return_value = [dbo].[InsertNewBodyPart]
@bodypart = N'Stomach',
@Model = 4
SELECT 'Return Value' = @return_value
SELECT * FROM BodyParts;
SELECT * FROM BodyPartsCounter
这一切都有效 - 但要小心 - 任何类型的多线程应用程序都需要考虑很多因素。
希望对您有所帮助!
如果您不维护计数器 table,有两种选择。在交易中,首先 select MAX(seq_id)
具有以下 table 提示之一:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
有点矫枉过正。它会阻止常规 select 语句,即使交易很小,也可以将其视为 heavy。
A ROWLOCK, XLOCK, HOLDLOCK
table 提示可能是一个更好的主意(但是:请阅读带有计数器 table 的替代方案)。优点是它不会阻塞常规 select 语句,即当 select 语句没有出现在 SERIALIZABLE
事务中时,或者当 select 语句没有出现时提供相同的 table 提示。使用 ROWLOCK, XLOCK, HOLDLOCK
仍会阻止插入语句。
当然,您需要确保程序的其他部分 select 和 MAX(seq_id)
没有这些 table 提示(或在 SERIALIZABLE
事务之外)并且然后使用此值插入行。
请注意,根据以这种方式锁定的行数,SQL 服务器可能会将锁升级为 table 锁。阅读有关锁定升级的更多信息 here。
使用 WITH(ROWLOCK, XLOCK, HOLDLOCK)
的插入过程如下所示:
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE model=@target_model);
IF @max_seq IS NULL SET @max_seq=0;
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
另一种可能是更好的想法是使用 计数器 table,并在计数器 table 上提供这些 table 提示.此 table 如下所示:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
然后您将按如下方式更改插入过程:
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE model=@target_model);
IF @new_seq IS NULL
BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
ELSE
BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET seq=@new_seq WHERE model=@target_model; END
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
优点是使用的行锁较少(即dbo.counter_seq
中每个模型一个),锁升级无法锁定整个dbo.table_seq
table从而阻塞select 语句。
您可以测试所有这些并亲自查看效果,方法是在 select 中 counter_seq
的序列之后放置一个 WAITFOR DELAY '00:01:00'
,然后摆弄 table( s) 在第二个 SSMS 选项卡中。
PS1:使用ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
不是一个好方法。如果行是 deleted/added,或者 ID 已更改,则序列将更改(考虑永远不应更改的发票 ID)。同样在性能方面,在检索单行时必须确定所有先前行的行号是一个坏主意。
PS2:当 SQL 服务器已经通过隔离级别或细粒度 table 提示提供锁定时,我永远不会使用外部资源来提供锁定。
我一开始不会尝试将 Seq
值存储在 table 中。
正如您在评论中所说,您的 ID
是 IDENTITY
,它由服务器以一种非常有效且并发安全的方式自动增加。使用它来确定插入行的顺序以及应该生成 Seq
值的顺序。
然后根据查询需要使用ROW_NUMBER
生成被Model
分区的Seq
的值(Model
的每个值序列从1重新开始)。
SELECT
ID
,Part
,Model
,ROW_NUMBER() OVER(PARTITION BY Model ORDER BY ID) AS Seq
FROM YourTable
insert into tableA (id,part,seq,model)
values
(6,'Groin',(select MAX(seq)+1 from tableA where model=3),3)
create function dbo.fncalnxt(@model int)
returns int
begin
declare @seq int
select @seq= case when @model=3 then max(id) --else
end from tblBodyParts
return @seq+1
end
--query idea To insert values, ideal if using SP to insert
insert into tblBodyParts values('groin',dbo.fncalnxt(@model),@model)
我想你可以试试这个。
新手出手,如有不妥请指正。我建议使用函数根据模型获取 seq 列中的值;
你必须检查 else 情况,尽管 return 你想要的另一个值,当模型!=3 时,它现在 return 为空。
我认为处理这种序列生成场景的最佳选择是计数器 table 作为 TT suggested. I just wanted to show you here a slightly simplified version of TT 实现。
表格:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq INT);
CREATE TABLE dbo.table_seq(part varchar(128), seq int, model int);
更简单的版本(没有 SELECT
语句来检索当前 seq
):
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Otra MAS';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @seq int = 1
UPDATE dbo.counter_seq WITH(ROWLOCK,HOLDLOCK) SET @seq = seq = seq + 1 WHERE model=@target_model;
IF @@ROWCOUNT = 0 INSERT INTO dbo.counter_seq VALUES (@target_model, 1);
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@seq,@target_model);
COMMIT
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
假设您有以下 table:
CREATE TABLE tab (
id int IDENTITY(1,1) PRIMARY KEY,
Part VARCHAR(32) not null,
Seq int not null,
Model int not null
);
INSERT INTO
tab(Part,Seq,Model)
VALUES
('Head', 0, 3),
('Neck', 1, 3),
('Shoulders', 2, 29),
('Shoulders', 2, 3),
('Stomach', 5, 3);
下面的查询将允许您导入多条记录,而不会破坏 model_seq
INSERT INTO
tab (model, part, model_seq)
SELECT
n.model,
n.part,
-- ensure new records will get receive the proper model_seq
IFNULL(max_seq + model_seq, model_seq) AS model_seq
FROM
(
SELECT
-- row number for each model new record
ROW_NUMBER() OVER(PARTITION BY model ORDER BY part) AS model_seq,
n.model,
n.part,
MAX(t.seq) AS max_seq
FROM
-- Table-values constructor allows you to prepare the
-- temporary data (with multi rows),
-- where you could join the existing one
-- to retrieve the max(model_seq) if any
(VALUES
('Stomach',3),
('Legs',3),
('Legs',29),
('Arms',1)
) AS n(part, model)
LEFT JOIN
tab
ON
tab.model = n.model
GROUP BY
n.model n.part
) AS t
我们需要 row_number() 来确保如果我们导入多个值,顺序将被保留。有关 ROW_NUMBER() OVER() (Transact-SQL)
的更多信息
Table-value 构造函数用于创建具有新值的 table 并加入 MAX model_seq 模型。
您可以在此处找到有关 table 值构造函数的更多信息:Table Value Constructor (Transact-SQL)
我有一个 table 这样的:
+----+-----------+------+-------+--+
| id | Part | Seq | Model | |
+----+-----------+------+-------+--+
| 1 | Head | 0 | 3 | |
| 2 | Neck | 1 | 3 | |
| 3 | Shoulders | 2 | 29 | |
| 4 | Shoulders | 2 | 3 | |
| 5 | Stomach | 5 | 3 | |
+----+-----------+------+-------+--+
对于 Model 3,我如何在 Stomach
之后的下一个序列中插入另一条记录。所以这里是新的 table 假设的样子:
+----+-----------+------+-------+--+
| id | Part | Seq | Model | |
+----+-----------+------+-------+--+
| 1 | Head | 0 | 3 | |
| 2 | Neck | 1 | 3 | |
| 3 | Shoulders | 2 | 29 | |
| 4 | Shoulders | 2 | 3 | |
| 5 | Stomach | 5 | 3 | |
| 6 | Groin | 6 | 3 | |
+----+-----------+------+-------+--+
有没有一种方法可以制作一个插入查询,该查询将仅在模型 3 的最高序列之后给出下一个数字。另外,寻找并发安全的东西。
处理此类插入的正确方法是使用 identity
列,或者,如果您愿意,也可以使用列的序列和默认值。
但是,NULL
列的值似乎不正确。
查询问题如:
Insert into yourtable(id, Part, Seq, Model)
Select 6, 'Groin', max(Seq) + 1, 3
From yourtable;
是两个这样的查询,同时 运行 可以产生相同的值。建议将 seq
声明为唯一标识列,让数据库完成所有工作。
由于您希望序列基于特定模型,因此只需在执行 select 时将其添加到 where 子句中即可。这将确保 Max(SEQ) 仅适用于该模型系列。此外,由于 SEQ 可以为 null,将其包装在 ISNULL 中,因此如果它为 null,它将为 0,因此 0 + 1 会将 next 设置为 1。 基本方法是:
Insert into yourtable(id, Part, Seq, Model)
Select 6, 'Groin', ISNULL(max(Seq),0) + 1, 3
From yourtable
where MODEL = 3;
让我们先列出挑战:
- 我们不能使用普通约束,因为存在空值,而且如果我们查看现有数据,我们还需要解决重复项和空缺问题。 没关系,我们会解决的;-> 在第 3 步中
我们需要并发操作的安全性(因此某种形式或混合的事务、隔离级别和可能的 "kinda SQL mutex"。)直觉认为这里是一个存储过程,原因有几个:
2.1 它更容易防止 sql 注入
2.2 我们可以更轻松地控制隔离级别(table 锁定)并从此类要求带来的一些问题中恢复
2.3 我们可以使用应用级数据库锁来控制并发
- 我们必须在每次插入时存储或查找下一个值。并发这个词已经告诉我们会有争用和可能的高吞吐量(否则请坚持使用单线程)。所以我们一定已经在想:在一个已经很复杂的世界里,不要从你想写的同一个 table 中读取。
那么在那个简短的前传中,让我们尝试一个解决方案:
首先,我们正在创建您的原始 table,然后还有一个 table 来保存我们设置为上次使用的序列 + 1:[=13 的序列 (BodyPartsCounter) =]
CREATE TABLE BodyParts
([id] int identity, [Part] varchar(9), [Seq] varchar(4), [Model] int)
;
INSERT INTO BodyParts
([Part], [Seq], [Model])
VALUES
('Head', NULL, 3),
('Neck', '1', 3),
('Shoulders', '2', 29),
('Shoulders', '2', 3),
('Stomach', '5', 3)
;
CREATE TABLE BodyPartsCounter
([id] int
, [counter] int)
;
INSERT INTO BodyPartsCounter
([id], [counter])
SELECT 1, MAX(id) + 1 AS id FROM BodyParts
;
然后我们需要创建可以发挥神奇作用的存储过程。简而言之,它充当互斥体,基本上保证您的并发性(如果您不对其他地方的相同 table 进行插入或更新)。然后获取下一个序列,更新它并插入新行。这一切都发生后,它将提交事务并为下一个等待调用线程释放存储过程。
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Charlla
-- Create date: 2016-02-15
-- Description: Inserts a new row in a concurrently safe way
-- =============================================
CREATE PROCEDURE InsertNewBodyPart
@bodypart varchar(50),
@Model int = 3
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
BEGIN TRANSACTION;
-- Get an application lock in your threaded calls
-- Note: this is blocking for the duration of the transaction
DECLARE @lockResult int;
EXEC @lockResult = sp_getapplock @Resource = 'BodyPartMutex',
@LockMode = 'Exclusive';
IF @lockResult = -3 --deadlock victim
BEGIN
ROLLBACK TRANSACTION;
END
ELSE
BEGIN
DECLARE @newId int;
--Get the next sequence and update - part of the transaction, so if the insert fails this will roll back
SELECT @newId = [counter] FROM BodyPartsCounter WHERE [id] = 1;
UPDATE BodyPartsCounter SET [counter] = @newId + 1 WHERE id = 1;
-- INSERT THE NEW ROW
INSERT INTO dbo.BodyParts(
Part
, Seq
, Model
)
VALUES(
@bodypart
, @newId
, @Model
)
-- END INSERT THE NEW ROW
EXEC @lockResult = sp_releaseapplock @Resource = 'BodyPartMutex';
COMMIT TRANSACTION;
END;
END
GO
现在 运行 测试:
EXEC @return_value = [dbo].[InsertNewBodyPart]
@bodypart = N'Stomach',
@Model = 4
SELECT 'Return Value' = @return_value
SELECT * FROM BodyParts;
SELECT * FROM BodyPartsCounter
这一切都有效 - 但要小心 - 任何类型的多线程应用程序都需要考虑很多因素。
希望对您有所帮助!
如果您不维护计数器 table,有两种选择。在交易中,首先 select MAX(seq_id)
具有以下 table 提示之一:
WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
有点矫枉过正。它会阻止常规 select 语句,即使交易很小,也可以将其视为 heavy。
A ROWLOCK, XLOCK, HOLDLOCK
table 提示可能是一个更好的主意(但是:请阅读带有计数器 table 的替代方案)。优点是它不会阻塞常规 select 语句,即当 select 语句没有出现在 SERIALIZABLE
事务中时,或者当 select 语句没有出现时提供相同的 table 提示。使用 ROWLOCK, XLOCK, HOLDLOCK
仍会阻止插入语句。
当然,您需要确保程序的其他部分 select 和 MAX(seq_id)
没有这些 table 提示(或在 SERIALIZABLE
事务之外)并且然后使用此值插入行。
请注意,根据以这种方式锁定的行数,SQL 服务器可能会将锁升级为 table 锁。阅读有关锁定升级的更多信息 here。
使用 WITH(ROWLOCK, XLOCK, HOLDLOCK)
的插入过程如下所示:
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE model=@target_model);
IF @max_seq IS NULL SET @max_seq=0;
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
另一种可能是更好的想法是使用 计数器 table,并在计数器 table 上提供这些 table 提示.此 table 如下所示:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
然后您将按如下方式更改插入过程:
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE model=@target_model);
IF @new_seq IS NULL
BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
ELSE
BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET seq=@new_seq WHERE model=@target_model; END
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
优点是使用的行锁较少(即dbo.counter_seq
中每个模型一个),锁升级无法锁定整个dbo.table_seq
table从而阻塞select 语句。
您可以测试所有这些并亲自查看效果,方法是在 select 中 counter_seq
的序列之后放置一个 WAITFOR DELAY '00:01:00'
,然后摆弄 table( s) 在第二个 SSMS 选项卡中。
PS1:使用ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
不是一个好方法。如果行是 deleted/added,或者 ID 已更改,则序列将更改(考虑永远不应更改的发票 ID)。同样在性能方面,在检索单行时必须确定所有先前行的行号是一个坏主意。
PS2:当 SQL 服务器已经通过隔离级别或细粒度 table 提示提供锁定时,我永远不会使用外部资源来提供锁定。
我一开始不会尝试将 Seq
值存储在 table 中。
正如您在评论中所说,您的 ID
是 IDENTITY
,它由服务器以一种非常有效且并发安全的方式自动增加。使用它来确定插入行的顺序以及应该生成 Seq
值的顺序。
然后根据查询需要使用ROW_NUMBER
生成被Model
分区的Seq
的值(Model
的每个值序列从1重新开始)。
SELECT
ID
,Part
,Model
,ROW_NUMBER() OVER(PARTITION BY Model ORDER BY ID) AS Seq
FROM YourTable
insert into tableA (id,part,seq,model)
values
(6,'Groin',(select MAX(seq)+1 from tableA where model=3),3)
create function dbo.fncalnxt(@model int)
returns int
begin
declare @seq int
select @seq= case when @model=3 then max(id) --else
end from tblBodyParts
return @seq+1
end
--query idea To insert values, ideal if using SP to insert
insert into tblBodyParts values('groin',dbo.fncalnxt(@model),@model)
我想你可以试试这个。 新手出手,如有不妥请指正。我建议使用函数根据模型获取 seq 列中的值; 你必须检查 else 情况,尽管 return 你想要的另一个值,当模型!=3 时,它现在 return 为空。
我认为处理这种序列生成场景的最佳选择是计数器 table 作为 TT suggested. I just wanted to show you here a slightly simplified version of TT 实现。
表格:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq INT);
CREATE TABLE dbo.table_seq(part varchar(128), seq int, model int);
更简单的版本(没有 SELECT
语句来检索当前 seq
):
DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Otra MAS';
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @seq int = 1
UPDATE dbo.counter_seq WITH(ROWLOCK,HOLDLOCK) SET @seq = seq = seq + 1 WHERE model=@target_model;
IF @@ROWCOUNT = 0 INSERT INTO dbo.counter_seq VALUES (@target_model, 1);
INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@seq,@target_model);
COMMIT
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
假设您有以下 table:
CREATE TABLE tab (
id int IDENTITY(1,1) PRIMARY KEY,
Part VARCHAR(32) not null,
Seq int not null,
Model int not null
);
INSERT INTO
tab(Part,Seq,Model)
VALUES
('Head', 0, 3),
('Neck', 1, 3),
('Shoulders', 2, 29),
('Shoulders', 2, 3),
('Stomach', 5, 3);
下面的查询将允许您导入多条记录,而不会破坏 model_seq
INSERT INTO
tab (model, part, model_seq)
SELECT
n.model,
n.part,
-- ensure new records will get receive the proper model_seq
IFNULL(max_seq + model_seq, model_seq) AS model_seq
FROM
(
SELECT
-- row number for each model new record
ROW_NUMBER() OVER(PARTITION BY model ORDER BY part) AS model_seq,
n.model,
n.part,
MAX(t.seq) AS max_seq
FROM
-- Table-values constructor allows you to prepare the
-- temporary data (with multi rows),
-- where you could join the existing one
-- to retrieve the max(model_seq) if any
(VALUES
('Stomach',3),
('Legs',3),
('Legs',29),
('Arms',1)
) AS n(part, model)
LEFT JOIN
tab
ON
tab.model = n.model
GROUP BY
n.model n.part
) AS t
我们需要 row_number() 来确保如果我们导入多个值,顺序将被保留。有关 ROW_NUMBER() OVER() (Transact-SQL)
的更多信息Table-value 构造函数用于创建具有新值的 table 并加入 MAX model_seq 模型。 您可以在此处找到有关 table 值构造函数的更多信息:Table Value Constructor (Transact-SQL)