用零填充的 varchar(5) 字段中的第一个未使用的数字

Posted

技术标签:

【中文标题】用零填充的 varchar(5) 字段中的第一个未使用的数字【英文标题】:First unused number in a varchar(5) field padded with zeros 【发布时间】:2014-03-14 20:14:24 【问题描述】:

我有一个 varchar(5) 字段。我需要在所有记录中找到第一个未使用的数字,以便它们以前导零开头。就像如果有 00001 和 00003 我希望查询返回 00002。此外,许多记录包含字母并且看起来像“G0542”。这些可以忽略。

我知道我很接近。这似乎适用于 SQL Server 2005,但不适用于 2008 或 2012

http://sqlfiddle.com/#!3/4016a0/1

create table b_addr ( inst_no varchar(5) Unique );
insert into b_addr (inst_no) values ('00001');
insert into b_addr (inst_no) values ('00002');
insert into b_addr (inst_no) values ('00004');
--this is the problem line 
insert into b_addr (inst_no) values ('A0045');

With usedNos as( 
select  CAST(b_addr.inst_no AS INT) as inst 
from b_addr 
where b_addr.inst_no LIKE '[0-9][0-9][0-9][0-9][0-9]') 
SELECT 
RIGHT('00000' + CONVERT(VARCHAR(5), COALESCE(min(inst)+1, 0)),5) AS next_inst_no 
from usedNos where not exists (select null from usedNos usn where usn.inst = usedNos.inst +1) 

我如何构建它以便它在 sql server 2008+ 中也能工作?

【问题讨论】:

您为什么需要/想要这样做? 【参考方案1】:

这是您的查询的有效版本:

With usedNos as ( 
      select CAST(case when isnumeric(b_addr.inst_no) = 1
                       then b_addr.inst_no 
                  end AS INT) as inst 
      from b_addr 
     ) 
SELECT RIGHT('00000' + CONVERT(VARCHAR(5), COALESCE(min(inst)+1, 0)),5) AS next_inst_no 
from usedNos
where inst is not null and 
      not exists (select 1
                  from usedNos usn
                  where usn.inst = usedNos.inst +1
                 );

关键是在case内部使用isnumeric()。这保证不会尝试cast(),除非该值看起来像一个数字。如果它看起来不像一个数字,那么结果是NULL,它在外部where 子句中被过滤掉了。

您的where 子句:

      where b_addr.inst_no LIKE '[0-9][0-9][0-9][0-9][0-9]'

尝试做同样的事情。但是,SQL Server 不保证在 select 之前处理 where 子句——这就是您收到意外错误的原因。

【讨论】:

【参考方案2】:

您可以检查您尝试投射的数字是否为实数:

代替

CAST(b_addr.inst_no AS INT) as inst

做类似的事情

CAST(CASE WHEN ISNUMERIC(b_addr.inst_no) = 1 THEN b_addr.inst_no ELSE 0 END AS INT) as inst

这有点令人费解,但确实有效。

演示:http://sqlfiddle.com/#!3/1aee5/6

【讨论】:

【参考方案3】:

这是另一个使用patindex 而不是like。

http://sqlfiddle.com/#!6/1aee5/12

With usedNos as( 
    select   b_addr.inst_no  as inst 
    from b_addr 
    where patindex('[0-9][0-9][0-9][0-9][0-9]',b_addr.inst_no) > 0
    ) 

SELECT 
RIGHT('00000' + COALESCE(cast((min(cast(inst as int)) + 1) as varchar), '00000'),5) AS next_inst_no 
from usedNos 
where not exists (select null from usedNos usn where  cast(usn.inst as int) = cast(usedNos.inst as int) + 1  )

--should return 00003, trying to find the first unused inst_no and padd it with zeros to make it 5 chars. 

【讨论】:

快速搜索后我没有找到任何确定的东西,也没有进行任何测试来验证速度。【参考方案4】:
create table b_addr ( inst_no varchar(5) Unique );
insert into b_addr (inst_no) values ('00001');
insert into b_addr (inst_no) values ('00002');
insert into b_addr (inst_no) values ('00004');
--this is the problem line 
insert into b_addr (inst_no) values ('A0045');

--Specify how many of the "next" unused numbers you want.
DECLARE @HowMany INT = 10

--Common Table Expression construct to generate sequential numbers.
;WITH 
  L0   AS(SELECT 1 AS c UNION ALL SELECT 1),
  L1   AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
  L2   AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
  L3   AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
  L4   AS(SELECT 1 AS c FROM L3 AS A, L3 AS B),
  L5   AS(SELECT 1 AS c FROM L4 AS A, L4 AS B),
  L6   AS(SELECT 1 AS c FROM L5 AS A, L5 AS B),
  SequentialNumbers AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS Num FROM L6)
SELECT TOP(@HowMany) RIGHT('00000' + CAST(sn.Num AS VARCHAR), 5) AS NextNumbers
FROM SequentialNumbers sn
WHERE sn.Num NOT IN ( 
    SELECT  CAST(b_addr.inst_no AS INT) AS inst 
    FROM b_addr 
    WHERE b_addr.inst_no LIKE '[0-9][0-9][0-9][0-9][0-9]'
) 

【讨论】:

以上是关于用零填充的 varchar(5) 字段中的第一个未使用的数字的主要内容,如果未能解决你的问题,请参考以下文章

语法错误:创建一个用零填充的二维列表

用零填充数字的嵌套 IF [重复]

用零填充个位数

在某个索引后用零填充火炬张量

SQL Server中varchar(n)和nvarchar(n)

用零填充字符串的更好方法[重复]