SQL Server获取下一个编码字符串的实现方案分割和进位

时间:2023-03-09 08:12:40
SQL Server获取下一个编码字符串的实现方案分割和进位
    我在前一种解决方案SQL Server获取下一个编码字符实现和后一种解决方案SQL Server获取下一个编码字符实现继续重构与增强两篇博文中均提供了一种解决编码的方案,考虑良久对比以上两种方案的,后一种方案虽然解决了其中方案的缺点,但是依然存在的编码字符串长度的限制(最多满足8位长度),本博文提供的方案将编码字符串长度增加到19位,也可以足够项目中实现这些编码。
    具体的编码规则可以参看以上两种解决方案博文中的描述,也可以进入SQL Server 大V潇湘隐者获取下一个编码字符串问题这篇博文。
    这次实现的思路主要是分割和进位。
    1、分割,是指将编码字符串分割为两部分:字母字符串和数字字符串。
    2、有数字字符串存在的情况则进位,是指将数字1和数字字符串连接成新数字字符串,将新数字字符串转化为bigint整数。
          如果该结果中的第一个数字为2,则表示该编码字符串要进位,这种的进位要分两种情况:字母字符串最后一个字母字符是否为"Z",如果为”Z“,那数字字符串第一位数字变化为”A",其余的数字字符串全部变为"0";如果不为”Z",那么字母字符串最后一个字母字符递增,数字字符串对应的整数值递增。
           如果该结果中的第一个数字为1,则表示数字字符串部分对应的整数值递增。
           在组装字母字符串和数字字符串就可以得到下一个的编码字符串了。
    3、没有数字字符串的情况,则将最后字母字符串最后一位字母字符递增组成新的编码字符串。
补充修改和优化 
  有关以下两个实现方案中针对从编码字符串中获取首个字母字符位置的方法使用了普通的循序方式,基于编码字符规则的定义,可以通过函数PATINDEX来实现,使用该函数的T-SQL代码如下:
     -- 找到到首个数字字符的位置(其所在编码字符串中的位置)
-- 方式1:通过循环获得
--WHILE @tintFistNumPos <= @tintLength
--BEGIN
-- SET @tintCharASCIIValue = ASCII(SUBSTRING(@chvCodeChars, @tintFistNumPos, ));
-- IF @tintCharASCIIValue BETWEEN ASCII('') AND ASCII('')
-- BEGIN
-- BREAK;
-- END -- SET @tintFistNumPos = @tintFistNumPos + ;
--END ---- 分割编码字符串到字母字符串和数字字符串
--SET @chvLetterChars = SUBSTRING(@chvCodeChars, , @tintFistNumPos - );
---- 只有找到数字字时才分割获得数字字符串
--IF @tintFistNumPos <= @tintLength
--BEGIN
-- SET @chvNumChars = SUBSTRING(@chvCodeChars, @tintFistNumPos, @tintLength - @tintFistNumPos + );
--END -- 方法2:通过PATINDEX函数
SET @tintFistNumPos = PATINDEX('%[0-9]%', @chvCodeChars);
IF @tintFistNumPos BETWEEN AND @tintLength -- 编码字符串规则,首字符必须是字母,只有第2个字符才可为数字。
BEGIN
-- 分割编码字符串得到字母字符串
SET @chvLetterChars = SUBSTRING(@chvCodeChars, , @tintFistNumPos - );
-- 分割编码字符串得到数字字符串
SET @chvNumChars = SUBSTRING(@chvCodeChars, @tintFistNumPos, @tintLength - @tintFistNumPos + );
END
ELSE IF @tintFistNumPos = -- 表示该编码字符串全部为字母字符
BEGIN
SET @chvLetterChars = @chvCodeChars;
END

实现方案代码

该方案的T-SQL代码如下:
 IF OBJECT_ID(N'dbo.ufn_GetNextCodeChars', 'FN') IS NOT NULL
BEGIN
DROP FUNCTION dbo.ufn_GetNextCodeChars;
END
GO --==================================
-- 功能: 获取下一个编码字符串
-- 说明: 具体实现阐述
-- 作者: XXX
-- 创建: yyyy-MM-dd
-- 修改: yyyy-MM-dd XXX 修改内容描述
--==================================
CREATE FUNCTION dbo.ufn_GetNextCodeChars
(
@chvCodeChars VARCHAR() -- 编码字符串,首字符必须以字母A-Z任意一个开始。
) RETURNS VARCHAR()
--$Encode$--
AS
BEGIN;
SET @chvCodeChars = ISNULL(@chvCodeChars, '');
SET @chvCodeChars = UPPER(@chvCodeChars); -- 下一个编码字符串变量
DECLARE @chvNextCodeChars AS VARCHAR();
SET @chvNextCodeChars = ''; -- 编码字符使用的字符字符串变量
DECLARE @chCharStr AS CHAR();
SET @chCharStr = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; DECLARE
@tintLength AS TINYINT,
@tintFistNumPos AS TINYINT;
SELECT
@tintLength = LEN(@chvCodeChars), -- 编码字符串长度变量
@tintFistNumPos = ; -- 首个数字字符所在位置的变量,默认第二个字符是数字字符 DECLARE
@chvLetterChars AS VARCHAR(), -- 字母字符串
@chvNumChars AS VARCHAR(); -- 数字字符串
SELECT
@chvLetterChars = '',
@chvNumChars = ''; -- 字符ASCII值变量
DECLARE @tintCharASCIIValue AS TINYINT;
SET @tintCharASCIIValue = ; -- 编码字符串长度的逻辑检查
IF @tintLength NOT BETWEEN AND
BEGIN
RETURN @chvNextCodeChars;
END -- 首字符是否字母字符的逻辑检查
SET @tintCharASCIIValue = ASCII(SUBSTRING(@chvCodeChars, , ));
IF @tintCharASCIIValue NOT BETWEEN ASCII('A') AND ASCII('Z')
BEGIN
RETURN @chvNextCodeChars;
END -- 所有字符全部为'Z'的逻辑检查
IF @chvCodeChars = REPLICATE('Z', @tintLength)
BEGIN
RETURN @chvNextCodeChars;
END -- 找到到首个数字字符的位置(其所在编码字符串中的位置)
-- 方式1:通过循环获得
--WHILE @tintFistNumPos <= @tintLength
--BEGIN
-- SET @tintCharASCIIValue = ASCII(SUBSTRING(@chvCodeChars, @tintFistNumPos, ));
-- IF @tintCharASCIIValue BETWEEN ASCII('') AND ASCII('')
-- BEGIN
-- BREAK;
-- END -- SET @tintFistNumPos = @tintFistNumPos + ;
--END ---- 分割编码字符串到字母字符串和数字字符串
--SET @chvLetterChars = SUBSTRING(@chvCodeChars, , @tintFistNumPos - );
---- 只有找到数字字时才分割获得数字字符串
--IF @tintFistNumPos <= @tintLength
--BEGIN
-- SET @chvNumChars = SUBSTRING(@chvCodeChars, @tintFistNumPos, @tintLength - @tintFistNumPos + );
--END -- 方法2:通过PATINDEX函数
SET @tintFistNumPos = PATINDEX('%[0-9]%', @chvCodeChars);
IF @tintFistNumPos BETWEEN AND @tintLength -- 编码字符串规则,首字符必须是字母,只有第2个字符才可为数字。
BEGIN
-- 分割编码字符串得到字母字符串
SET @chvLetterChars = SUBSTRING(@chvCodeChars, , @tintFistNumPos - );
-- 分割编码字符串得到数字字符串
SET @chvNumChars = SUBSTRING(@chvCodeChars, @tintFistNumPos, @tintLength - @tintFistNumPos + );
END
ELSE IF @tintFistNumPos = -- 表示该编码字符串全部为字母字符
BEGIN
SET @chvLetterChars = @chvCodeChars;
END -- 字母字符串长度和字母字符串最后一个字母
DECLARE
@tintLetterLength AS TINYINT,
@chLastLetter AS CHAR();
SELECT
@tintLetterLength = LEN(@chvLetterChars),
@chLastLetter = SUBSTRING(@chvLetterChars, @tintLetterLength, ); IF LEN(@chvNumChars) = /*最后一位不为Z或是数字字符时的逻辑处理*/
BEGIN
SET @chvLetterChars = SUBSTRING(@chvLetterChars, , @tintLetterLength - )
+ SUBSTRING(@chCharStr, CHARINDEX(@chLastLetter, @chCharStr, ) + , );
END
ELSE /*数字字符超过1位(最多18位)时逻辑处理*/
BEGIN
-- 声明一个特殊的整数变量,开始为“”后边紧跟数字字符串,在转为整数进行加法运算,如果该结果首字符从1变成了2,则表示前面相邻的字母字符需要递进增加;否则只是数字字符串进行递进增加。
DECLARE @bintNumPlusOne AS BIGINT;
SET @bintNumPlusOne = CAST('' + + @chvNumChars AS BIGINT) + ; IF SUBSTRING(CAST(@bintNumPlusOne AS VARCHAR()), , ) = '' /*数字字符串全部为9*/
BEGIN
IF @chLastLetter = 'Z' /*如果数字字符串相邻前面字母为'Z',则第一个数字变为'A',其余的数字字符串全部变为0*/
BEGIN
SET @chvNumChars = 'A' + REPLICATE('', LEN(@chvNumChars) - );
END
ELSE /*如果数字字符串相邻前面字母不为'Z',则这个字母递进增加,数字字符串全部变为0*/
BEGIN
SET @chvLetterChars = SUBSTRING(@chvLetterChars, , @tintLetterLength - )
+ SUBSTRING(@chCharStr, CHARINDEX(@chLastLetter, @chCharStr, ) + , ); SET @chvNumChars = REPLICATE('', LEN(CAST(@bintNumPlusOne AS VARCHAR())) - );
END
END
ELSE /*数字字符串第一个数字字符不为9,其余的数字字符可全部为9*/
BEGIN
SET @chvNumChars = STUFF(CAST(@bintNumPlusOne AS VARCHAR()), , , '');
END
END -- 将字母字符串和数字字符串一起组装成下一个编码字符串
SET @chvNextCodeChars = @chvLetterChars + @chvNumChars; RETURN @chvNextCodeChars;
END
GO
实现方案效果
测试实现方案的T-SQL代码如下:
 DECLARE @chvCodeChars AS VARCHAR();

 SET @chvCodeChars = 'ZZZZZZZZZZZZZZZZZ99'
SELECT @chvCodeChars AS [当前编码字符串], dbo.ufn_GetNextCodeChars(@chvCodeChars) AS [相邻前面字母为Z且字母进位]; SET @chvCodeChars = 'AAAA99';
SELECT @chvCodeChars AS [当前编码字符串], dbo.ufn_GetNextCodeChars(@chvCodeChars) AS [相邻前面字母不为Z且字母进位]; SET @chvCodeChars = 'ZZZZZZZZZZZZZZZZA99';
SELECT @chvCodeChars AS [当前编码字符串], dbo.ufn_GetNextCodeChars(@chvCodeChars) AS [相邻前面字母不为Z且字母进位]; SET @chvCodeChars = 'ZZZZZZZZZZZZZZZZB00';
SELECT @chvCodeChars AS [当前编码字符串], dbo.ufn_GetNextCodeChars(@chvCodeChars) AS [数字进位]; SET @chvCodeChars = 'ZZZZZZZZZZZZZZZZZA';
SELECT @chvCodeChars AS [当前编码字符串], dbo.ufn_GetNextCodeChars(@chvCodeChars) AS [全为字母且字母进位];
GO
执行后的查询结果如下:
SQL Server获取下一个编码字符串的实现方案分割和进位
补充的解决方案
 
根据博友KingJaja提供的解决方案,该方案针对边界的判断很简洁,需要调整支持19位长度编码字符以及以“9"结尾且长度小于范围值 长度时的小bug,针对以上的增强和修改后的的T-SQL脚本代码如下:
 IF OBJECT_ID(N'[dbo].[ufn_GenerateNexCodeChars]', 'FN') IS NOT NULL
BEGIN
DROP FUNCTION [dbo].[ufn_GenerateNexCodeChars];
END
GO CREATE FUNCTION [dbo].[ufn_GenerateNexCodeChars]
(
@chvCodeChars VARCHAR()
) RETURNS VARCHAR()
AS
BEGIN
SET @chvCodeChars = ISNULL(@chvCodeChars, '');
SET @chvCodeChars = UPPER(@chvCodeChars); DECLARE @chvNextCodeChars AS VARCHAR();
SET @chvNextCodeChars = ''; DECLARE
@tintLength AS TINYINT, -- 编码字符串长度
@tintFistNumPos AS TINYINT, -- 编码字符串中第一个数字字符的位置,默认为第2个位置
@chvLetterChars AS VARCHAR(), -- 字母字符串
@chvNumChars AS VARCHAR(), -- 数字字符串
@tintCharASCIIValue AS TINYINT; -- 字符ASCII整数值
SELECT
@tintLength = LEN(@chvCodeChars),
@tintFistNumPos = ,
@chvLetterChars = '',
@chvNumChars = '',
@tintCharASCIIValue = ; -- 编码字符串长度的逻辑检查
IF @tintLength NOT BETWEEN AND
BEGIN
RETURN @chvNextCodeChars;
END -- 所有字符全部为'Z'的逻辑检查
IF @chvCodeChars = REPLICATE('Z', @tintLength)
BEGIN
RETURN @chvNextCodeChars;
END -- 首字符是否字母字符的逻辑检查
SET @tintCharASCIIValue = ASCII(SUBSTRING(@chvCodeChars, , ));
IF @tintCharASCIIValue NOT BETWEEN ASCII('A') AND ASCII('Z')
BEGIN
RETURN @chvNextCodeChars;
END -- 找到到首个数字字符的位置(其所在编码字符串中的位置)
-- 方式1:通过循环获得
--WHILE @tintFistNumPos <= @tintLength
--BEGIN
-- SET @tintCharASCIIValue = ASCII(SUBSTRING(@chvCodeChars, @tintFistNumPos, ));
-- IF @tintCharASCIIValue BETWEEN ASCII('') AND ASCII('')
-- BEGIN
-- BREAK;
-- END -- SET @tintFistNumPos = @tintFistNumPos + ;
--END ---- 分割编码字符串到字母字符串和数字字符串
--SET @chvLetterChars = SUBSTRING(@chvCodeChars, , @tintFistNumPos - );
---- 只有找到数字字符时才分割获得数字字符串
--IF @tintFistNumPos <= @tintLength
--BEGIN
-- SET @chvNumChars = SUBSTRING(@chvCodeChars, @tintFistNumPos, @tintLength - @tintFistNumPos + );
--END -- 方法2:通过PATINDEX函数
SET @tintFistNumPos = PATINDEX('%[0-9]%', @chvCodeChars);
IF @tintFistNumPos BETWEEN AND @tintLength -- 编码字符串规则,首字符必须是字母,只有第2个字符才可为数字。
BEGIN
-- 分割编码字符串得到字母字符串
SET @chvLetterChars = SUBSTRING(@chvCodeChars, , @tintFistNumPos - );
-- 分割编码字符串得到数字字符串
SET @chvNumChars = SUBSTRING(@chvCodeChars, @tintFistNumPos, @tintLength - @tintFistNumPos + );
END
ELSE IF @tintFistNumPos = -- 表示该编码字符串全部为字母字符
BEGIN
SET @chvLetterChars = @chvCodeChars;
END DECLARE
@tintLetterCharsLength AS TINYINT,
@tintNumCharsLength AS TINYINT,
@chLastLetterOfLetterChars AS CHAR();
SELECT
@tintLetterCharsLength = LEN(@chvLetterChars),
@tintNumCharsLength = LEN(@chvNumChars),
@chLastLetterOfLetterChars = SUBSTRING(@chvLetterChars, @tintLetterCharsLength, ); IF @chvNumChars = REPLICATE('', @tintNumCharsLength) -- 当编码字符串需要数字字符串部分进位时,即数字字符串全部为9或空字符串(不是NULL,而是'')
BEGIN
IF @chLastLetterOfLetterChars = 'Z' -- 字母字符串最后一个字母字符是'Z'
BEGIN
SET @chvLetterChars = @chvLetterChars + 'A';
SET @chvNumChars =REPLICATE('', @tintNumCharsLength - );
END
ELSE -- 字母字符串最后一个字母字符不是'Z',则进位该自字母字符
BEGIN
SET @chvLetterChars = SUBSTRING(@chvLetterChars, , @tintLetterCharsLength - )
+ CHAR(ASCII(@chLastLetterOfLetterChars) + );
SET @chvNumChars =REPLICATE('', @tintNumCharsLength);
END
END
ELSE
BEGIN
SET @chvNumChars = CAST(CAST(@chvNumChars AS BIGINT) + AS VARCHAR()) -- 数字部分转换为bigint再递增1再转换为字符串
SET @chvNumChars = REPLICATE('', @tintNumCharsLength - LEN(@chvNumChars))
+ @chvNumChars; -- 数字字符串补充缺0
END SET @chvNextCodeChars =@chvLetterChars+ @chvNumChars; -- 组装下一个编码字符串 RETURN @chvNextCodeChars;
END
GO

测试的T-SQL代码如下:

 SELECT dbo.ufn_GenerateNexCodeChars('Z0')
,dbo.ufn_GenerateNexCodeChars('Z9')
,dbo.ufn_GenerateNexCodeChars('ZY')
,dbo.ufn_GenerateNexCodeChars('ZA9');
GO

执行后的查询结果如下:

SQL Server获取下一个编码字符串的实现方案分割和进位

SQL Server获取下一个编码字符串的实现方案分割和进位
SQL Server获取下一个编码字符串的实现方案分割和进位
博友如有其他更好的解决方案,也请不吝赐教,万分感谢。