重置 SQL 服务器中存储过程的 ID 计数器
Reset the ID counter on a stored procedure in SQL Server
我正在开发一个管理车辆工单的系统。工单ID组成如下:OT-001-16
.
其中 OT-
是字符串,001
是计数器,然后是 -
字符,最后是数字 16
是当前年份。
示例:
如果当前年份是2018年,ID应该是OT-001-18
。
问题是当年份改变时,计数器必须从 001
重新开始。我有一个存储过程来执行此操作,但我认为我正在做更多的工作。
这是我的存储过程代码:
CREATE PROCEDURE ot (@name varchar(100), @area varchar(100), @idate varchar(100), @edate varchar(100))
AS
BEGIN
SET NOCOUNT ON;
DECLARE @aux varchar(100);
DECLARE @aux2 varchar(100);
DECLARE @aux3 int;
DECLARE @aux4 varchar(100);
SELECT @aux = id_workorder FROM idaux;
IF (@aux IS NULL)
SET @aux = CONCAT('OT-000-', RIGHT(YEAR(GETDATE()), 2));
SET
@aux2 = SUBSTRING(
@aux, CHARINDEX('-', @aux) + 1,
LEN(@aux) - CHARINDEX('-', @aux) - CHARINDEX('-', REVERSE(@aux)));
SET @aux3 = CAST(@aux2 AS int) + 1;
SET @aux4 = @aux3;
IF @aux3 < 1000
IF @aux3 >= 10
SET @aux4 = CONCAT('0', @aux4);
ELSE
SET @aux4 = CONCAT('00', @aux4);
ELSE
SET @aux4 = @aux4;
DECLARE @f varchar(100);
DECLARE @y varchar(50);
SELECT TOP 1
@y = id_workorder
FROM workorder
WHERE (RIGHT(id_workorder, 2)) = (RIGHT(YEAR(GETDATE()), 2))
ORDER BY id_workorder DESC;
DECLARE @yy varchar(10);
SET
@yy = RIGHT(@y, 2);
DECLARE @yn varchar(10);
SET
@yn = RIGHT(YEAR(GETDATE()), 2);
BEGIN
IF @yn = @yy
BEGIN
DECLARE @laux varchar(20)
SET @f = 'OT-' + @aux4 + '-' + RIGHT(YEAR(GETDATE()), 2);
INSERT INTO workorder (id_workorder, name, area, initial_date, end_date)
VALUES (@f, @name, @area, @idate, @edate);
SELECT
@laux = id_workorder
FROM idaux
IF (@laux IS NULL)
BEGIN
INSERT idaux (id_workorder) VALUES (@f);
END
ELSE
BEGIN
UPDATE idaux SET id_workorder = @f;
END
END
ELSE
BEGIN
SET @f = CONCAT('OT-001-', (RIGHT(YEAR(GETDATE()), 2)));
INSERT INTO workorder (id_workorder, name, area, initial_date, end_date)
VALUES (@f, @name, @area, @idate, @edate);
SELECT @laux = id_workorder FROM idaux;
IF (@laux IS NULL)
BEGIN
INSERT idaux (id_workorder) VALUES (@f);
END
ELSE
BEGIN
UPDATE idaux SET id_workorder = @f;
END
END
END
END
基本上,我创建了一个辅助 table 来保存最后一个工单 ID,然后从这个名为 idaux
的 table 中获取 ID 并与新的可能 ID 进行比较字符串处理。然后,如果最后保存的 ID 的年份等于当前年份,则计数器增加,但如果不是,则计数器重新启动到 001,新 ID 在辅助 table 中更新,并将工单插入到table workorder
.
我的存储过程可以正常工作,但我需要你的帮助来优化存储过程。有任何问题 post 评论。
有许多基于您的代码的观察结果,您可以更改这些观察结果以优化和保证您的结果。
我不知道你的 Table 结构,但你的 ID 似乎使用了自然键。
- 相反,使用代理键,例如
INT
/BIGINT
不仅可以提高 table 连接的效率(不需要字符串),还可能添加另一层当前设计中的安全性。
- 或者,将列规范化为它们的标志。例如:
OT-001-05
有三个元素:OT
是工单类型,001
是ID,15
是年份。目前,OT确定ID决定年份。
SELECT @aux = id_workorder FROM idaux;
- idaux 没有被描述。它是一个单一的价值?如果是表格,请保证结果,否则将来可能会崩溃。
- 即使你加了
MAX(id_workorder)
,你的结果也不会如你所想。由于这是一个 VARCHAR,最左边未绑定的字符的最大值将为 return.
@aux, CHARINDEX('-', @aux) + 1,
LEN(@aux) - CHARINDEX('-', @aux) - CHARINDEX('-', REVERSE(@aux)));
这很好,但总的来说,通过将所有这三个元素拆分到它们自己的变量中,您可以使代码更清晰、更易于调试。您仍在使用您的方法,但稍微简化了一点(就个人而言,CHARINDEX
可能会很痛苦)。
SET @aux = @Type -- 'OT'
SET @aux2 = @ID -- The result of your previous code
SET @aux3 = @Year -- your YY from GETDATE()
-- then join
SET @Work_Order = CONCAT(@aux, '-', @aux2, '-', @aux3)
更新:
目前,您在 idaux
中的列的 ID 位于列的中间。 这将产生灾难性的结果,因为任何 ID 比较都将发生在列的中间。这意味着您充其量可能会逃脱 PATINDEX
但仍在对 table 执行 table 扫描。没有索引(FULLTEXT
除外)的优化程度要低得多。
我应该补充一点,如果将 ID 元素放入其自己的列中,您可能会发现在该列上使用 BINARY
归类会提高其性能。请注意,我尚未测试在混合列上尝试进行 BINARY 归类
以下是我如何设置存储过程和底层 table 以跟踪您的工作订单:
create database tmpWorkOrders;
go
use tmpWorkOrders;
go
/*
The work order ID (as you wish to see it) and the
work order counter (per year) will be separated into
two separate columns (with a unique constraint).
The work order ID (you wish to see) is automatically
generated for you and stored "persisted":
*/
create table WorkOrders
(
SurrogateKey int identity(1, 1) primary key not null,
WorkOrderYear int not null,
WorkOrderCounter int not null,
WorkOrderID as
N'OT-' + right(N'000' + cast(WorkOrderCounter as nvarchar), 3)
+ N'-' + right(cast(WorkOrderYear as nvarchar), 2)persisted,
WorkOrderDescription nvarchar(200),
constraint UQ_WorkOrderIDs
unique nonclustered (WorkOrderYear, WorkOrderCounter)
);
go
create procedure newWorkOrder
(@WorkOrderYear int = null,
@WorkOderCounter int = null,
@WorkOrderDescription nvarchar(200) = null
)
as
begin
/*
If no year is given the the current year is assumed
*/
if @WorkOrderYear is null
begin
set @WorkOrderYear = year(current_timestamp);
end;
/*
If no work order counter (for the above year) is given
then the next available one will be given
*/
if @WorkOderCounter is null
begin
set @WorkOderCounter
= isnull(
(
select max(WorkOrderCounter)
from WorkOrders
where WorkOrderYear = @WorkOrderYear
) + 1,
0
);
end;
else
/*
If a work order counter has been passed to the
stored procedure then it must be validated first
*/
begin
/*
Does the work order counter (for the given year)
already exist?
*/
if exists
(
select 1
from dbo.WorkOrders as wo
where wo.WorkOrderYear = @WorkOrderYear
and wo.WorkOrderCounter = @WorkOderCounter
)
begin
/*
If the given work order counter already exists
then the next available one should be assigned.
*/
while exists
(
select 1
from dbo.WorkOrders as wo
where wo.WorkOrderYear = @WorkOrderYear
and wo.WorkOrderCounter = @WorkOderCounter
)
begin
set @WorkOderCounter = @WorkOderCounter + 1;
end;
end;
end;
/*
The actual insert of the new work order ID
*/
insert into dbo.WorkOrders
(
WorkOrderYear,
WorkOrderCounter,
WorkOrderDescription
)
values
(@WorkOrderYear,
@WorkOderCounter,
@WorkOrderDescription
);
end;
go
/*
Some test runs with the new table and stored procedure...
*/
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = null,
@WorkOrderDescription = null;
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = 3,
@WorkOrderDescription = null;
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = 0,
@WorkOrderDescription = null;
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = 0,
@WorkOrderDescription = null;
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = 0,
@WorkOrderDescription = null;
/*
...reviewing the result of the above.
*/
select *
from dbo.WorkOrders as wo;
请注意,"next available" 工单计数器曾经被给定 (1) 作为最大值 + 1,并且曾经 (2) 增加,直到它不违反 table 上的唯一键约束了。像这样,您有两种不同的可能性。
我正在开发一个管理车辆工单的系统。工单ID组成如下:OT-001-16
.
其中 OT-
是字符串,001
是计数器,然后是 -
字符,最后是数字 16
是当前年份。
示例:
如果当前年份是2018年,ID应该是OT-001-18
。
问题是当年份改变时,计数器必须从 001
重新开始。我有一个存储过程来执行此操作,但我认为我正在做更多的工作。
这是我的存储过程代码:
CREATE PROCEDURE ot (@name varchar(100), @area varchar(100), @idate varchar(100), @edate varchar(100))
AS
BEGIN
SET NOCOUNT ON;
DECLARE @aux varchar(100);
DECLARE @aux2 varchar(100);
DECLARE @aux3 int;
DECLARE @aux4 varchar(100);
SELECT @aux = id_workorder FROM idaux;
IF (@aux IS NULL)
SET @aux = CONCAT('OT-000-', RIGHT(YEAR(GETDATE()), 2));
SET
@aux2 = SUBSTRING(
@aux, CHARINDEX('-', @aux) + 1,
LEN(@aux) - CHARINDEX('-', @aux) - CHARINDEX('-', REVERSE(@aux)));
SET @aux3 = CAST(@aux2 AS int) + 1;
SET @aux4 = @aux3;
IF @aux3 < 1000
IF @aux3 >= 10
SET @aux4 = CONCAT('0', @aux4);
ELSE
SET @aux4 = CONCAT('00', @aux4);
ELSE
SET @aux4 = @aux4;
DECLARE @f varchar(100);
DECLARE @y varchar(50);
SELECT TOP 1
@y = id_workorder
FROM workorder
WHERE (RIGHT(id_workorder, 2)) = (RIGHT(YEAR(GETDATE()), 2))
ORDER BY id_workorder DESC;
DECLARE @yy varchar(10);
SET
@yy = RIGHT(@y, 2);
DECLARE @yn varchar(10);
SET
@yn = RIGHT(YEAR(GETDATE()), 2);
BEGIN
IF @yn = @yy
BEGIN
DECLARE @laux varchar(20)
SET @f = 'OT-' + @aux4 + '-' + RIGHT(YEAR(GETDATE()), 2);
INSERT INTO workorder (id_workorder, name, area, initial_date, end_date)
VALUES (@f, @name, @area, @idate, @edate);
SELECT
@laux = id_workorder
FROM idaux
IF (@laux IS NULL)
BEGIN
INSERT idaux (id_workorder) VALUES (@f);
END
ELSE
BEGIN
UPDATE idaux SET id_workorder = @f;
END
END
ELSE
BEGIN
SET @f = CONCAT('OT-001-', (RIGHT(YEAR(GETDATE()), 2)));
INSERT INTO workorder (id_workorder, name, area, initial_date, end_date)
VALUES (@f, @name, @area, @idate, @edate);
SELECT @laux = id_workorder FROM idaux;
IF (@laux IS NULL)
BEGIN
INSERT idaux (id_workorder) VALUES (@f);
END
ELSE
BEGIN
UPDATE idaux SET id_workorder = @f;
END
END
END
END
基本上,我创建了一个辅助 table 来保存最后一个工单 ID,然后从这个名为 idaux
的 table 中获取 ID 并与新的可能 ID 进行比较字符串处理。然后,如果最后保存的 ID 的年份等于当前年份,则计数器增加,但如果不是,则计数器重新启动到 001,新 ID 在辅助 table 中更新,并将工单插入到table workorder
.
我的存储过程可以正常工作,但我需要你的帮助来优化存储过程。有任何问题 post 评论。
有许多基于您的代码的观察结果,您可以更改这些观察结果以优化和保证您的结果。
我不知道你的 Table 结构,但你的 ID 似乎使用了自然键。
- 相反,使用代理键,例如
INT
/BIGINT
不仅可以提高 table 连接的效率(不需要字符串),还可能添加另一层当前设计中的安全性。 - 或者,将列规范化为它们的标志。例如:
OT-001-05
有三个元素:OT
是工单类型,001
是ID,15
是年份。目前,OT确定ID决定年份。
SELECT @aux = id_workorder FROM idaux;
- idaux 没有被描述。它是一个单一的价值?如果是表格,请保证结果,否则将来可能会崩溃。
- 即使你加了
MAX(id_workorder)
,你的结果也不会如你所想。由于这是一个 VARCHAR,最左边未绑定的字符的最大值将为 return.
@aux, CHARINDEX('-', @aux) + 1,
LEN(@aux) - CHARINDEX('-', @aux) - CHARINDEX('-', REVERSE(@aux)));
这很好,但总的来说,通过将所有这三个元素拆分到它们自己的变量中,您可以使代码更清晰、更易于调试。您仍在使用您的方法,但稍微简化了一点(就个人而言,
CHARINDEX
可能会很痛苦)。SET @aux = @Type -- 'OT' SET @aux2 = @ID -- The result of your previous code SET @aux3 = @Year -- your YY from GETDATE() -- then join SET @Work_Order = CONCAT(@aux, '-', @aux2, '-', @aux3)
更新:
目前,您在 idaux
中的列的 ID 位于列的中间。 这将产生灾难性的结果,因为任何 ID 比较都将发生在列的中间。这意味着您充其量可能会逃脱 PATINDEX
但仍在对 table 执行 table 扫描。没有索引(FULLTEXT
除外)的优化程度要低得多。
我应该补充一点,如果将 ID 元素放入其自己的列中,您可能会发现在该列上使用 BINARY
归类会提高其性能。请注意,我尚未测试在混合列上尝试进行 BINARY 归类
以下是我如何设置存储过程和底层 table 以跟踪您的工作订单:
create database tmpWorkOrders;
go
use tmpWorkOrders;
go
/*
The work order ID (as you wish to see it) and the
work order counter (per year) will be separated into
two separate columns (with a unique constraint).
The work order ID (you wish to see) is automatically
generated for you and stored "persisted":
*/
create table WorkOrders
(
SurrogateKey int identity(1, 1) primary key not null,
WorkOrderYear int not null,
WorkOrderCounter int not null,
WorkOrderID as
N'OT-' + right(N'000' + cast(WorkOrderCounter as nvarchar), 3)
+ N'-' + right(cast(WorkOrderYear as nvarchar), 2)persisted,
WorkOrderDescription nvarchar(200),
constraint UQ_WorkOrderIDs
unique nonclustered (WorkOrderYear, WorkOrderCounter)
);
go
create procedure newWorkOrder
(@WorkOrderYear int = null,
@WorkOderCounter int = null,
@WorkOrderDescription nvarchar(200) = null
)
as
begin
/*
If no year is given the the current year is assumed
*/
if @WorkOrderYear is null
begin
set @WorkOrderYear = year(current_timestamp);
end;
/*
If no work order counter (for the above year) is given
then the next available one will be given
*/
if @WorkOderCounter is null
begin
set @WorkOderCounter
= isnull(
(
select max(WorkOrderCounter)
from WorkOrders
where WorkOrderYear = @WorkOrderYear
) + 1,
0
);
end;
else
/*
If a work order counter has been passed to the
stored procedure then it must be validated first
*/
begin
/*
Does the work order counter (for the given year)
already exist?
*/
if exists
(
select 1
from dbo.WorkOrders as wo
where wo.WorkOrderYear = @WorkOrderYear
and wo.WorkOrderCounter = @WorkOderCounter
)
begin
/*
If the given work order counter already exists
then the next available one should be assigned.
*/
while exists
(
select 1
from dbo.WorkOrders as wo
where wo.WorkOrderYear = @WorkOrderYear
and wo.WorkOrderCounter = @WorkOderCounter
)
begin
set @WorkOderCounter = @WorkOderCounter + 1;
end;
end;
end;
/*
The actual insert of the new work order ID
*/
insert into dbo.WorkOrders
(
WorkOrderYear,
WorkOrderCounter,
WorkOrderDescription
)
values
(@WorkOrderYear,
@WorkOderCounter,
@WorkOrderDescription
);
end;
go
/*
Some test runs with the new table and stored procedure...
*/
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = null,
@WorkOrderDescription = null;
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = 3,
@WorkOrderDescription = null;
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = 0,
@WorkOrderDescription = null;
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = 0,
@WorkOrderDescription = null;
exec dbo.newWorkOrder @WorkOrderYear = null,
@WorkOderCounter = 0,
@WorkOrderDescription = null;
/*
...reviewing the result of the above.
*/
select *
from dbo.WorkOrders as wo;
请注意,"next available" 工单计数器曾经被给定 (1) 作为最大值 + 1,并且曾经 (2) 增加,直到它不违反 table 上的唯一键约束了。像这样,您有两种不同的可能性。