Quantcast
Channel: sql-server – Tech ABC to XYZ
Viewing all articles
Browse latest Browse all 30

Incrementing custom primary key values in SQL [ANSWERED]

$
0
0

Incrementing custom primary key values in SQL

Asked By: Mahe
Originally Asked On: 2014-01-02 12:39:18
Asked Via: stackoverflow

I am asked to generate custom ID values for primary key columns. The query is as follows,

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')) 

Here the SC_TD_GoodsInward is a table, GoodsInwardId is the value to be generated. I am getting the desired outputs too. Examples.

GI_131118_1_0001
GI_131212_1_0002
GI_131212_1_0003

But, the above condition fails when the last digits reach 9999. I simulated the query and the results were,

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

After 9999, it goes to 0000 and does not increment thereafter. So, in the future, I will eventually run into a PK duplicate error. How can i recycle the values so that after 9999, it goes on as 0000, 0001 … etc. What am I missing in the above query?

NOTE: Please consider the @EntityId value to be 1 in the query.
I am using SQL SERVER 2012.

He received 4 answers
eventually accepting:

Punter015’s answer to

Incrementing custom primary key values in SQL

Before giving a solution for the question few points on your question:

  1. As the Custom primary key consists of mainly three parts Date(140102), physical location where transaction takes place (entityID), 4 place number(9999).
  2. According to the design on a single date in a single physical location there cannot be more than 9999 transactions — My Solution will also contain the same limitation.

Some points on my solution

  1. The 4 place digit is tied up to the date which means for a new date the count starts from 0000. For Example
    GI_140102_1_0001,
    GI_140102_1_0002,
    GI_140102_1_0003,
    GI_140103_1_0000,
    GI_140104_1_0000

Any way the this field will be unique.

  1. The solution compares the latest date in the record to the current date.
    The Logic:
    If current date and latest date in the record matches
    Then it increments 4 place digit by the value by 1
    If the current date and the latest date in the record does not matched
    The it sets the 4 place digit by the value 0000.

The Solution: (Below code gives out the value which will be the next GoodsInwardId, Use it as per requirement to fit in to your solution)

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 to create the required structure (Probable Guess)

For the table:

CREATE TABLE [dbo].[SC_TD_GoodsInward](
    [EntityId] [int] NULL,
    [GoodsInwardId] [nvarchar](30) NULL
)

Sample records for the table:

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');

**Its a probable solution in your situation although the perfect solution would be to have identity column (use reseed if required) and tie it with the current date as a computed column.

The answer with the highest score with 1 points was:

StuartLC’s answer to

Incrementing custom primary key values in SQL

You get this problem because once the last 4 digits reach 9999, 9999 will remain the highest number no matter how many rows are inserted, and you are throwing away the most significant digit(s).

I would remodel this to track the last used INT portion value of GoodsInwardId in a separate counter table (as an INTEGER), and then MODULUS (%) this by 10000 if need be. If there are concurrent calls to the PK generator, remember to lock the counter table row.

Also, even if you kept all the digits (e.g. in another field), note that ordering a CHAR is as follows

1
11
2
22
3

and then applying MAX() will return 3, not 22.

Edit – Clarification of counter table alternative

The counter table would look something like this:

CREATE TABLE PK_Counters
(
    TableName NVARCHAR(100) PRIMARY KEY,
    LastValue INT
);

(Your @EntityID might be another candidate for the counter PK column.)

You then increment and fetch the applicable counter on each call to your custom PK Key generation 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');

You could also modulo the LastValue in the counter table (and not in the query), although I believe there is more information about the number of records inserted by leaving the counter un-modulo-ed.

Fiddle here

Re : Performance – Selecting a single integer value from a small table by its PK and then applying modulo will be significantly quicker than selecting MAX from a SUBSTRING (which would almost certainly be a scan)

If the selected answer did not help you out, the other answers might!

All Answers For: Incrementing custom primary key values in SQL

StuartLC’s answer to

Incrementing custom primary key values in SQL

You get this problem because once the last 4 digits reach 9999, 9999 will remain the highest number no matter how many rows are inserted, and you are throwing away the most significant digit(s).

I would remodel this to track the last used INT portion value of GoodsInwardId in a separate counter table (as an INTEGER), and then MODULUS (%) this by 10000 if need be. If there are concurrent calls to the PK generator, remember to lock the counter table row.

Also, even if you kept all the digits (e.g. in another field), note that ordering a CHAR is as follows

1
11
2
22
3

and then applying MAX() will return 3, not 22.

Edit – Clarification of counter table alternative

The counter table would look something like this:

CREATE TABLE PK_Counters
(
    TableName NVARCHAR(100) PRIMARY KEY,
    LastValue INT
);

(Your @EntityID might be another candidate for the counter PK column.)

You then increment and fetch the applicable counter on each call to your custom PK Key generation 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');

You could also modulo the LastValue in the counter table (and not in the query), although I believe there is more information about the number of records inserted by leaving the counter un-modulo-ed.

Fiddle here

Re : Performance – Selecting a single integer value from a small table by its PK and then applying modulo will be significantly quicker than selecting MAX from a SUBSTRING (which would almost certainly be a scan)

Vland’s answer to

Incrementing custom primary key values in SQL

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

This should take the last GoodInwardId record, ordered by date DESC and take its numeric “id”. Then add + 1 to return the NEW id and combine it with today’s date and the @entityid you passed. If >9999, start again from 0000.

You need a timestamp type column tho, to order two inserted in the same date + same transaction time. Otherwise you could get duplicates.

Punter015’s answer to

Incrementing custom primary key values in SQL

Before giving a solution for the question few points on your question:

  1. As the Custom primary key consists of mainly three parts Date(140102), physical location where transaction takes place (entityID), 4 place number(9999).
  2. According to the design on a single date in a single physical location there cannot be more than 9999 transactions — My Solution will also contain the same limitation.

Some points on my solution

  1. The 4 place digit is tied up to the date which means for a new date the count starts from 0000. For Example
    GI_140102_1_0001,
    GI_140102_1_0002,
    GI_140102_1_0003,
    GI_140103_1_0000,
    GI_140104_1_0000

Any way the this field will be unique.

  1. The solution compares the latest date in the record to the current date.
    The Logic:
    If current date and latest date in the record matches
    Then it increments 4 place digit by the value by 1
    If the current date and the latest date in the record does not matched
    The it sets the 4 place digit by the value 0000.

The Solution: (Below code gives out the value which will be the next GoodsInwardId, Use it as per requirement to fit in to your solution)

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 to create the required structure (Probable Guess)

For the table:

CREATE TABLE [dbo].[SC_TD_GoodsInward](
    [EntityId] [int] NULL,
    [GoodsInwardId] [nvarchar](30) NULL
)

Sample records for the table:

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');

**Its a probable solution in your situation although the perfect solution would be to have identity column (use reseed if required) and tie it with the current date as a computed column.

Mahe’s answer to

Incrementing custom primary key values in SQL

I have simplified the answer even more and arrived with the following query.

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

Of course, you should really check out the original question.

The post Incrementing custom primary key values in SQL [ANSWERED] appeared first on Tech ABC to XYZ.


Viewing all articles
Browse latest Browse all 30

Trending Articles