[MSSQL] 使用Stored Procedure 產生以日期為群組的流水號ID

  • 497
  • 0

[MSSQL] 使用Stored Procedure 產生以日期為群組的流水號ID

ex: S210101001,S為前綴字,210101為日期(yyMMdd),001為流水號

因為工作上的需求,

需要產生「S210101001」像這種以日期為群組的流水序號當成Table的PK,

一般最簡單的做法是「從目標Table取最大值再進行+1」,

但為了避免每次都要從目標Table內查最大值,

若資料量大可能會有效能問題,

於是選擇用一張Table進行Key的控管

  • Table Schema
欄位名稱說明NullablePrimaryKey
KeyType索引鍵類型 Y
KeyValue最新索引鍵編號Y 
/****** Object:  Table [dbo].[KeyData]    Script Date: 2021/7/13 下午 06:23:41 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[KeyData](
	[KeyType] [nvarchar](20) NOT NULL,
	[KeyValue] [nvarchar](50) NULL,
 CONSTRAINT [PK_KeyData] PRIMARY KEY CLUSTERED 
(
	[KeyType] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'索引鍵類型' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'KeyData', @level2type=N'COLUMN',@level2name=N'KeyType'
GO

EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'最新索引鍵編號' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'KeyData', @level2type=N'COLUMN',@level2name=N'KeyValue'
GO

 

另外為了避免同時取號會取到重覆值的情況發生,

故採用Stored Procedure的方式進行取號,

並且針對取值與賦值的部分加入Transaction

  • Stored Procedure Schema

/****** Object:  StoredProcedure [dbo].[usp_GetNewID]    Script Date: 2021/7/13 下午 05:59:46 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Author:		Charley.Chang
-- Create date: 2021/07/12
-- Description:	Get New ID S+yyMMdd+001 (ex: S210712001)
-- =============================================
ALTER  PROCEDURE [dbo].[usp_GetNewID]
AS
BEGIN
	-- Declare Variables
	DECLARE @KeyType nvarchar(10)
	DECLARE @Prefix nvarchar(1)
	DECLARE @NewID nvarchar(20)
	DECLARE @DT nvarchar(20)
	DECLARE @NewSN nvarchar(4)
	DECLARE @ErrCnt int,@RowCnt int	-- Declare Variables to catch row modify counts 

	-- Set Variable
	SET @KeyType = 'SalesOrder'
	SET @Prefix = 'S'

	BEGIN TRANSACTION
	-- Set Datetime String yyMMdd (ex: 210712)
	SET @DT = SUBSTRING(CONVERT(varchar(10), GetDate(), 112), 3, 6)

	-- Set SN String 00X (ex: 001)
	SET @NewSN = (SELECT TOP 1 FORMAT(CAST(ISNULL(MAX(substring(kd.KeyValue, 8, 3)), '') AS INT) + 1, '00#') AS sno
    FROM dbo.KeyData kd WITH(HOLDLOCK, UPDLOCK)
	WHERE kd.KeyType = @KeyType AND kd.KeyValue LIKE CONCAT(@Prefix, @DT, '%'))

	IF (LEN(@NewSN) = 3)	-- To avoid the sn out of range
		BEGIN
			-- Concat ID S+yyMMdd+001 (ex: S210712001)
			SET @NewID = Concat(@Prefix, @DT, @NewSN)

			UPDATE dbo.KeyData SET dbo.KeyData.KeyValue = @NewID WHERE dbo.KeyData.KeyType = @KeyType
			SELECT @ErrCnt = @@ERROR	-- Update Error Count
			SELECT @RowCnt = @@ROWCOUNT	-- Update Count

			-- Check Success
			IF (@ErrCnt = 0 AND @RowCnt = 1)
				BEGIN	-- transaction success
					COMMIT TRANSACTION
				END
			ELSE	-- transaction failed, set new id is empty
				BEGIN
					ROLLBACK TRANSACTION;
					SET @NewID = ''
				END
		END
	ELSE	-- sn out of range, set new id is empty
		BEGIN
			ROLLBACK TRANSACTION;
			SET @NewID = ''
		END
	SELECT @NewID AS NewID
END
  • 各變數說明:
變數名稱用途說明
@KeyType索引鍵類型,對應Table的KeyType欄位
@Prefix前綴字
@NewID回傳最新ID
@DT日期字串(上方範例為yyMMdd)
@NewSN該群組最新流水號(上方範例為00#)
@ErrCnt用於取得Update時錯誤筆數@@ERROR
@RowCnt用於取得Update更新完成筆數@@ROWCOUNT

 

  • Stored Procedure執行說明:

實際取值與賦值的地方皆包覆在BEGIN TRANSACTIONCOMMIT TRANSACTIONROLLBACK TRANSACTION  之間,

其中 WITH(HOLDLOCK, UPDLOCK) 主要是為了避免「多人同時取值」時,造成取重複值的問題,

SELECT時會同時將該Record鎖定避免存取,

並且為避免ID超出格式有加上IF (LEN(@NewSN) = 3)進行檢核。

 

  • 實際執行結果:

 

 

 

 

 

 

 

 

 

如果各位大大有其他不錯的解法,

也歡迎一起交流~

Write By Charley Chang 


新手發文,若有錯誤還請指教,
歡迎留言或Mail✉給我

創用 CC 授權條款


本著作係採用創用 CC 姓名標示-非商業性-相同方式分享 4.0 國際 授權條款授權.