我被要求为主键列生成自定义ID值。查询如下,
SELECT * FROM SC_TD_GoodsInward WHERE EntityId = @EntityId
SELECT @GoodsInwardId=IIF((SELECT COUNT(*) FROM SC_TD_GoodsInward)>0, (Select 'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+CONVERT(varchar,@EntityId)+'_'+(SELECT RIGHT('0000'+CONVERT(VARCHAR,CONVERT(INT,RIGHT(MAX(GoodsInwardId),4))+1),4) from SC_TD_GoodsInward)), (SELECT 'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+CONVERT(varchar,@EntityId)+'_0001'))
这里SC_TD_GoodsInward是一个表,GoodsInwardId是要生成的值。我也得到了理想的输出。例子。
GI_131118_1_0001
GI_131212_1_0002
GI_131212_1_0003
但是,当最后一位数字达到 9999 时,上述条件会失败。我模拟了查询,结果是,
GI_131226_1_9997
GI_140102_1_9998
GI_140102_1_9999
GI_140102_1_0000
GI_140102_1_0000
GI_140102_1_0000
GI_140102_1_0000
GI_140102_1_0000
9999之后,它进入0000并且之后不再增加。因此,在将来,我最终会遇到PK重复错误。如何回收这些值,以便在9999之后,它继续为0000,1000 ......等。我在上述查询中缺少什么?
注意:请在查询中将@EntityId
值视为1。
我正在使用SQL SERVER 2012。
答案 0 :(得分:1)
您遇到此问题是因为一旦最后4位数字到达9999
,无论插入了多少行,9999
都将保持最高数字,并且您丢弃了最重要的数字
我会对此进行重新构建,以便在单独的计数器表(作为INTEGER)中跟踪GoodsInwardId
的最后一次使用的INT部分值,然后根据需要将模数(%)设置为10000。如果存在对PK生成器的并发调用,请记住锁定计数器表行。
此外,即使您保留了所有数字(例如在其他字段中),请注意订购CHAR
如下
1
11
2
22
3
然后应用MAX()
将返回3,而不是22。
编辑 - 澄清计数器表替换
计数器表看起来像这样:
CREATE TABLE PK_Counters
(
TableName NVARCHAR(100) PRIMARY KEY,
LastValue INT
);
(您的@EntityID
可能是反PK列的另一个候选人。)
然后在每次调用自定义PK密钥生成PROC时递增并获取适用的计数器:
UPDATE PK_Counters
SET LastValue = LastValue + 1
WHERE TableName = 'SC_TD_GoodsInward';
Select
'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)
+RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)
+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'
+CONVERT(varchar,@EntityId)+'_'
+(SELECT RIGHT('0000'+ CONVERT(NVARCHAR, LastValue % 10000),4)
FROM PK_Counters
WHERE TableName = 'SC_TD_GoodsInward');
你也可以在计数器表中模拟LastValue(而不是在查询中),虽然我相信有更多关于通过离开计数器非模数插入的记录数的信息。
Re:性能 - 通过PK从小表中选择一个整数值,然后应用modulo比从SUBSTRING
选择MAX要快得多(这几乎肯定是扫描)
答案 1 :(得分:1)
DECLARE @entityid INT = 1;
SELECT ('GI_'
+ SUBSTRING(convert(varchar, getdate(), 112),3,6) -- yymmdd today DATE
+ '_' + CAST(@entityid AS VARCHAR(50)) + '_' --@entity parameter
+ CASE MAX(t.GI_id + 1) --take last number + 1
WHEN 10000 THEN
'0000' --reset
ELSE
RIGHT( CAST('0000' AS VARCHAR(4)) +
CAST(MAX(t.GI_id + 1) AS VARCHAR(4))
, 4)
END) PK
FROM
(
SELECT TOP 1
CAST(SUBSTRING(GoodsInwardId,11,1) AS INT) AS GI_entity,
CAST(SUBSTRING(GoodsInwardId,4,6) AS INT) AS GI_date,
CAST(RIGHT(GoodsInwardId,4) AS INT) AS GI_id
FROM SC_TD_GoodsInward
WHERE CAST(SUBSTRING(GoodsInwardId,11,1) AS INT) = @entityid
ORDER BY gi_date DESC, rowTimestamp DESC, gi_id DESC
) AS t
这应该采用最后的GoodInwardId
记录,按日期DESC排序并取其数字“id”。然后添加+ 1以返回NEW ID并将其与今天的日期和您通过的@entityid相结合。如果> 9999,请从0000再次开始。
您需要一个时间戳类型列,以便在同一日期+相同的交易时间内订购两个。否则你可能会重复。
答案 2 :(得分:1)
在提出问题的解决方案之前,您的问题上几点:
我的解决方案的一些要点
此字段的任何方式都是唯一的。
解决方案:(以下代码给出了下一个GoodsInwardId的值,根据要求使用它以适应您的解决方案)
declare @previous nvarchar(30);
declare @today nvarchar(30);
declare @newID nvarchar(30);
select @previous=substring(max(GoodsInwardId),4,6) from SC_TD_GoodsInward;
Select @today=RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)
+RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2);
if @previous=@today
BEGIN
Select @newID='GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)
+RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)
+'_'+CONVERT(varchar,1)+'_'+(SELECT RIGHT('0000'+
CONVERT(VARCHAR,CONVERT(INT,RIGHT(MAX(GoodsInwardId),4))+1),4)
from SC_TD_GoodsInward);
END
else
BEGIN
SET @newID='GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)
+RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)
+'_'+CONVERT(varchar,1)+'_0000';
END
select @newID;
T-SQL创建所需的结构(可能的猜测)
表格:
CREATE TABLE [dbo].[SC_TD_GoodsInward](
[EntityId] [int] NULL,
[GoodsInwardId] [nvarchar](30) NULL
)
表的样本记录:
insert into dbo.SC_TD_GoodsInward values(1,'GI_140102_1_0000');
insert into dbo.SC_TD_GoodsInward values(1,'GI_140101_1_9999');
insert into dbo.SC_TD_GoodsInward values(1,'GI_140101_1_0001');
**在您的情况下它是一个可能的解决方案,尽管完美的解决方案是拥有标识列(如果需要,使用重新设置)并将其与当前日期绑定为计算列。
答案 3 :(得分:0)
我已经更简化了答案,并通过以下查询到达。
IF (SELECT COUNT(GoodsInwardId) FROM SC_TD_GoodsInward WHERE EntityId = @EntityId)=0
BEGIN
SELECT @GoodsInwardId= 'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+
RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+
RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+
CONVERT(varchar,@EntityId)+'_0001'
END
ELSE
BEGIN
SELECT * FROM SC_TD_GoodsInward WHERE EntityId = @EntityId AND CONVERT(varchar,CreatedOn,103) = CONVERT(varchar,GETDATE(),103)
SELECT @GoodsInwardId=IIF(@@ROWCOUNT>0,
(Select 'GI_'+
RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+
RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+
RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+
CONVERT(varchar,@EntityId)+'_'+
(SELECT RIGHT('0000'+CONVERT(VARCHAR,CONVERT(INT,RIGHT(MAX(GoodsInwardId),4))+1),4) from SC_TD_GoodsInward WHERE CONVERT(varchar,CreatedOn,103) = CONVERT(varchar,GETDATE(),103))),
(SELECT 'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+
RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+
RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+
CONVERT(varchar,@EntityId)+'_0001'))
END
select * from SC_TD_GoodsInward