I need to make a SQL Server sequence formatted like this VT-000/2015,
where the zero's is the bill number which increments every record, starting from one in same year which is 2015
e.g:
VT-001/2014,
VT-002/2014,
VT-003/2014,
VT-004/2014,
VT-001/2015,
VT-002/2015
How can I make this?
Materializing the row number in the table is problematic. I think a view is the right tool for this job.
Here is one way to do this by creating a view and using ROW_NUMBER():
CREATE TABLE PRODUCT (PRODUCT_ID int,YEAR INT);
CREATE VIEW dbo.VIEW_PRODUCT AS
SELECT
PRODUCT_ID,
[YEAR],
ROW_NUMBER() OVER(PARTITION BY [YEAR] ORDER by [YEAR]) AS RowNum,
'VT-' + RIGHT(replicate('0', 3)
+ cast(ROW_NUMBER() OVER(PARTITION BY [YEAR] ORDER by [YEAR]) AS VARCHAR(10)), 3)
+ '/' + cast(YEAR AS VARCHAR(4)) as SERIAL
FROM PRODUCT;
INSERT INTO PRODUCT VALUES
(1000,2014),
(2000,2014),
(3000,2015),
(4000,2015),
(5000,2015);
SQL Fiddle Demo
If you're on SQL Server 2012, you can use sequences and a computed column - like this:
Step 1: define your sequence
CREATE SEQUENCE dbo.YearlyNumbers
START WITH 1
INCREMENT BY 1 ;
GO
You can reset that sequence back to 1 every January 1 of a new year, to get new numbers starting at 1, for the new year
Step 2: define your table, include a computed column that concatenates together the various bits and pieces:
CREATE TABLE dbo.Product
(
ProductID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
ProductDate DATE,
-- default constraint gets a new value from the sequence on every insert
SequenceValue INT
CONSTRAINT DF_Product_SeqValue DEFAULT(NEXT VALUE FOR YearlyNumbers),
-- computed column puts together all bits to create the formatted ID you're looking for
FormattedID AS 'VT-' + RIGHT('0000' + CAST(SequenceValue AS VARCHAR(4)), 4) +
'/' + CAST(YEAR(ProductDate) AS VARCHAR(4)) PERSISTED
)
Now, when you insert values into your table:
INSERT INTO dbo.Product (ProductDate)
VALUES ('20150115'), ('20150331'), ('20150222'), ('20150414'), ('20150526')
the INSERT will fetch values from the sequence, and the computed column shows the formatted ID as requested:
SELECT * FROM dbo.Product
Consider your table is like this:
CREATE TABLE PRODUCT ([SERIAL] NVARCHAR(30), [YEAR] INT, [COUNTER] INT)
Here is the procedure you can use:
CREATE PROCEDURE PRODUCT_INSERT
#YEAR INT
AS
BEGIN
DECLARE #COUNTER INT
SELECT #COUNTER = ISNULL(MAX([COUNTER]), 0) + 1 FROM PRODUCT
WHERE [YEAR] = #YEAR
INSERT INTO PRODUCT
VALUES ('VT-' + REPLACE(STR(#COUNTER, 3), SPACE(1), '0') + '/' + CONVERT(nvarchar(4),#YEAR), #YEAR, #COUNTER)
END
Related
I have this table "ART":
COD - varchar(20) PK
DES - varchar(50)
Simple example data are:
COD DES
MM000000 AA
MM000001 BB
MM000010 CC
MM000145 DD
How do I increment the column COD by 1 every insert? The final pk format must be: 'MM' + 6 digits
DECLARE #a INT = 0;
WHILE #a < 100
BEGIN
SELECT 'MM' + RIGHT('000000' + CAST(#A as VARCHAR(6)),6)
SET #a += 1
END
CREATE TABLE #a
(
Id INT IDENTITY(1,1),
Code AS 'MM' + RIGHT('000000' + CAST(Id as VARCHAR(255)),6)
)
INSERT INTO #a DEFAULT VALUES
INSERT INTO #a DEFAULT VALUES
INSERT INTO #a DEFAULT VALUES
SELECt * FROM #a
but identity can be an unevenly growing value
I guess it is better and more safe to define the identity column as a computed column in the table definition
Please refer to SQL tutorial on Custom Sequence String as SQL Identity Column
The solution requires a simple identity column of integer data type increasing by 1 with every insert
Then a second column, actually a computed column created as follows will help you for the solution of your requirement
COD as dbo.udf_ZeroPaddingAndConcat('ID-', CAST(Id as nvarchar(10)),6,'0'),
Actually the custom function module is just doing the zero padding here as
SELECT #Prefix + RIGHT(REPLICATE(#PaddingChar, #Length) + CAST(#Id as nvarchar(10)), #Length)
You will also find the function source code in the same referenced document
I am working with Microsoft SQL Server 2014. In our requirement, custom formatted sequence number is include.
The sequence number format is CAT-YYYY-MM-NNNNNN. Sample data:
CAT-2016-10-000001
CAT-2016-10-000002
.
.
.
CAT-2016-10-999999
I don't want to use GUID or any other and I want to work with a procedure or function.
So, I am trying with this:
CREATE TABLE [category]
(
[id] int NOT NULL UNIQUE IDENTITY,
[category_no] nvarchar(20) NOT NULL,
[category_name] nvarchar(50) NOT NULL,
PRIMARY KEY ([id])
);
CREATE FUNCTION generate_category_no()
RETURNS CHAR(20)
AS
BEGIN
DECLARE #category_no CHAR(20)
SET #category_no = (SELECT MAX(category_no) FROM category)
IF #category_no IS NULL
SET #category_no = 'CAT-' + YEAR(getDate()) + '-' + MONTH(getDate()) + '-000001'
DECLARE #no int
SET #no = RIGHT(#category_no,6) + 1
RETURN 'CAT-' + YEAR(getDate()) + '-' + MONTH(getDate()) + '-' + right('00000' + CONVERT(VARCHAR(10),#no),6)
END
GO
ALTER TABLE category DROP COLUMN category_no;
ALTER TABLE category ADD category_no AS dbo.generate_category_no();
INSERT INTO category (category_name)
VALUES ('BMW'), ('JAGUAR');
When I run the above SQL in step-by-step, it is OK. It shown no error. But when I run the following command:
SELECT * FROM category;
it shows the following error:
Msg 217, Level 16, State 1, Line 1
Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32).
I don't know how to solve this one. And even I don't know my function has worked or not. I referenced from internet for this function.
ADDED
I need to reset sequence no for every month. Eg. for next month, no should be as follow:
CAT-2016-11-000001
Please, enlighten me. Thanks in advance!
Modify your function as below
ALTER TABLE category DROP COLUMN category_no;
alter FUNCTION dbo.generate_category_no( #id int)
RETURNS CHAR(20)
AS
BEGIN
RETURN 'CAT-' + cast(YEAR(getDate()) as varchar(10)) + '-' + cast(MONTH(getDate()) as varchar(10))+ '-' + right('00000' + CONVERT(VARCHAR(10),#id),6)
END
ALTER TABLE category ADD category_no AS dbo.generate_category_no(id);
INSERT INTO category
(category_name)
VALUES
('BMW13'),
('JAGUAR');
SELECT * FROM category will give the below result.
1 BMW CAT-2016-10-000001
2 JAGUAR CAT-2016-10-000002
3 BMW1 CAT-2016-10-000003
4 BMW13 CAT-2016-10-000004
Try this:
To initialize your new field:
ALTER TABLE category DROP COLUMN category_no;
ALTER TABLE category ADD category_no CHAR(20)
UPDATE category set category_no = dbo.generate_category_no()
For other insert:
CREATE TRIGGER [dbo].[category_i]
ON [dbo].[category]
AFTER INSERT
AS BEGIN
UPDATE category
SET category_no = dbo.generate_category_no()
FROM inserted
WHERE category.pk = inserted.pk
END
But you can try to use SEQUENCE feature, available on Sql Server by 2012 version
About SEQUENCE you can see here
Biggest flaw in your function is it will not work for Batch Insert's
Since you have ID auto generated, here is a easier way to do this
category_no AS Concat('CAT-', Year(Getdate()), '-', Month(Getdate()), '-', RIGHT(Concat('00000', id), 6))
Demo
CREATE TABLE #seq
(
id INT IDENTITY(1, 1),
name VARCHAR(10),
category_no AS Concat('CAT-', Year(Getdate()), '-', Month(Getdate()), '-', RIGHT(Concat('00000', id), 6))
)
INSERT INTO #seq
(name)
VALUES ('val')
Result :
id name category_no
-- ---- -----------
1 val CAT-2016-10-000001
Finally, I solved the problem. My Function look like as follow:
CREATE FUNCTION generate_category_no()
RETURNS CHAR(20)
AS
BEGIN
DECLARE #category_no CHAR(20)
SET #category_no = (SELECT MAX(category_no) FROM category WHERE category_no LIKE CONCAT('CAT-', YEAR(getDate()), '-', MONTH(getDate()), '-%'))
IF #category_no is null SET #category_no = CONCAT('CAT-', YEAR(getDate()), '-', MONTH(getDate()), '-000000')
DECLARE #no INT
SET #no = RIGHT(#category_no,6) + 1
RETURN CONCAT('CAT-', YEAR(getDate()), '-', MONTH(getDate()), '-', RIGHT('00000' + CONVERT(VARCHAR(10),#no),6))
END
GO
And I insert data as follow:
INSERT INTO category (category_no, category_name) VALUES (dbo.generate_category_no(),'BMW');
INSERT INTO category (category_no, category_name) VALUES (dbo.generate_category_no(),'JAGUAR');
One things is that We can call function from INSERT query.
So, when I run the following sql:
SELECT * FROM category;
It give the result as shown in below.
+---+--------------------+--------------+
|id |category_no |category_name |
+---+--------------------+--------------+
| 1 |CAT-2016-10-000001 | BMW |
| 2 |CAT-2016-10-000002 | JAGUAR |
+---+--------------------+--------------+
Thanks everybody for helping me. Thanks!!!
Consider the table in SQL Server 2012
789-0000000
The above number will be consider as a string in SQL Server 2012, but whenever I update the record I need increment to 1.
For example:
When I update the record 1 it should increment to 789-0000001
When I update the record 2 it should increment to 789-0000002
Finally increment should done only 789-0000000
The best solution is to use
an ID INT IDENTITY(1,1) column to get SQL Server to handle the automatic increment of your numeric value
a computed, persisted column to convert that numeric value to the value you need
So try this:
CREATE TABLE dbo.YourTable
(ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
CompanyID AS '789-' + RIGHT('000000' + CAST(ID AS VARCHAR(7)), 7) PERSISTED,
.... your other columns here....
)
Now, every time you insert a row into dbo.YourTable without specifying values for ID or CompanyID:
INSERT INTO dbo.YourTable(Col1, Col2, ..., ColN)
VALUES (Val1, Val2, ....., ValN)
then SQL Server will automatically and safely increase your ID value, and CompanyID will contain values like 789-0000001, 789-0000002,...... and so on - automatically, safely, reliably, no duplicates.
DECLARE #base int = 0
UPDATE TableX
SET
TableX.Value = 'Prefix' + RIGHT('0000000' + CAST(#base AS nvarchar),7),
#base = #base + 1
FROM
TableX
you can split the string
e.g.:
SELECT Item
FROM dbo.SplitString('Apple,Mango,Banana,Guava', ',')
then cast it
e.g.:
SELECT CAST(YourVarcharCol AS INT) FROM Table
then manually increment it
e.g.:
DECLARE max_id INT
SET #max_id = (SELECT MAX(id) FROM source_table)
DECLARE cursor_name CURSOR FOR
SELECT columns, to, copy
FROM source_table
OPEN cursor_name
FETCH NEXT FROM cursor_name
INTO #columns, #to, #cop
at update
e.g.:
declare #i int = SELECT ISNULL(MAX(interfaceID),0) + 1 FROM prices
update prices
set interfaceID = #i , #i = #i + 1
where interfaceID is null
you can understand how complicate this is and why
the solution using a constant to store that prefix is right one.
Declare #str varchar(max) = '789-0000000'
Select
SUBSTRING ( #str ,0 ,CHARINDEX ( '-' ,#str ))
+'-'
+
(SUBSTRING ( #str ,(CHARINDEX ( '-' ,#str)+1) ,(7-LEN(CAST(SUBSTRING ( #str ,CHARINDEX ( '-' ,#str)+1,LEN(#str)) as int))
)
)+
CAST(CAST(SUBSTRING ( #str ,CHARINDEX ( '-' ,#str)+1,LEN(#str)) as int)+1 as varchar))
When #str='789-0001947'
Output #str= 789-0001948
You can write a update trigger on the table with above logic.
We're a manufacturing company, and I have a SQL Table [FSDBGL] that holds information for every item we have. This includes columns for ItemNumber, ItemUPC, and ItemStatus. Some of the data in the ItemUPC column is empty for required items.
What I need to do, is assign/insert a random unique barcode number (that isn't already taken) inside of the ItemUPC column. The number needs to be 12 digits long, and be preceded with "601040xxxxxx", randomizing only the last 6 digits. This does not have to be done on every row for every item number.
-- Check/update only the [ItemNumber]'s (between 40000-01 - 50000-01) (the -01 at the end could also be a -02)
I need to ignore/exclude the following column attributes from getting a number:
-- ItemStatus (only if it's set at 'O' for Obsolete)
-- ItemUPC (if it already has a barcode number)
I would like to customize a SQL query for this that I can populate the cells now, and implement into a nightly process to update any newly-created Item#'s as well.
Here's the CREATE Script view:
USE [FSDBGL]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Mfg_ITMMAST](
[IMPN] [varchar](30) NOT NULL,
[IMDESC] [varchar](70) NOT NULL,
[IMUPCCD] [varchar](13) NOT NULL,
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
Random numbers: You could loop through each row that's missing the UPC using a cursor and get a random number for each one using ROUND(RAND() * 999999,0, 0), and then check for collisions before doing the update. The where clause for the cursor query should be pretty straightforward... regular expression on the ItemNumber, ItemStatus != 'O', ItemUPC != null or '' or 0 (or whatever the default value is).
The sproc should be rerunnable at any time, since it uses random numbers and checks for collisions.
A more efficient way would be to use serial-issued numbers instead of random. As long as you were able to store the last used number in a table somewhere, I believe you could add all the UPC numbers with one query instead of having to run several for each one by utilizing the UPDATE ... FROM syntax and the SELECT #counter = #counter + 1 syntax for user variables.
EDIT: Adding stored procedure and other comments
Let me first note that this database design is probably not optimal. There is no primary key on this table and no indexes. If this table has any large amount of records, queries are going to be slow, and this stored procedure is going to be very slow.
I also had to make some assumptions. Since the IMUPCCD can't be null, then I assume there is a default value of 601040 when the UPC is "blank". Since there was no primary key, I couldn't update through the cursor, but instead had to run a separate update statement, which is also slower. I also had to assume that IMPN uniquely identifies a row of data. I'm not sure if these assumptions are correct, so you may have to modify the sproc to suit your situation.
Also, the original question refers to ItemStatus, but no status column was given in the schema, so I couldn't limit results by it in my tests. However, you can easily add it to the stored procedure's DECLARE blanksCursor CURSOR FOR ... WHERE ... statement in the WHERE clause.
The Test Data (in a database called stackoverflow)
USE [stackoverflow]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
--DROP TABLE [dbo].[Mfg_ITMMAST];
CREATE TABLE [dbo].[Mfg_ITMMAST](
[IMPN] [varchar](30) NOT NULL,
[IMDESC] [varchar](70) NOT NULL,
[IMUPCCD] [varchar](13) NOT NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
INSERT [dbo].[Mfg_ITMMAST] ([IMPN], [IMDESC], [IMUPCCD]) VALUES (N'40000-01', N'test', N'601040')
INSERT [dbo].[Mfg_ITMMAST] ([IMPN], [IMDESC], [IMUPCCD]) VALUES (N'41023-01', N'test', N'601040123456')
INSERT [dbo].[Mfg_ITMMAST] ([IMPN], [IMDESC], [IMUPCCD]) VALUES (N'41001-02', N'test', N'601040')
INSERT [dbo].[Mfg_ITMMAST] ([IMPN], [IMDESC], [IMUPCCD]) VALUES (N'51001-01', N'test', N'601040')
INSERT [dbo].[Mfg_ITMMAST] ([IMPN], [IMDESC], [IMUPCCD]) VALUES (N'51001-02', N'test', N'601040')
INSERT [dbo].[Mfg_ITMMAST] ([IMPN], [IMDESC], [IMUPCCD]) VALUES (N'51014-02', N'test', N'601040234567')
INSERT [dbo].[Mfg_ITMMAST] ([IMPN], [IMDESC], [IMUPCCD]) VALUES (N'61001-01', N'test', N'601040')
The Stored Procedure
CREATE PROCEDURE uspScanForBlankUpcs
AS
-- setup variables for bringing in blank row data
DECLARE #IMPN [varchar](30), #IMUPCCD [varchar](13),
#blankUpc [varchar](13), #upcPrefix [varchar](6),
#random [varchar](6), #retryRandom bit;
SET #blankUpc = '601040'; -- This is the value of IMUPCCD when it is "blank"
SET #upcPrefix = '601040'; -- This is prefix for our randomly generated UPC
-- setup the cursor, query for items with "blank" UPCs
DECLARE blanksCursor CURSOR FOR
SELECT IMPN
FROM [Mfg_ITMMAST]
WHERE (LEFT(IMPN, 5) >= '40000' AND
LEFT(IMPN, 5) < '60000' AND
RIGHT(IMPN, 2) IN ('01','02')) AND
IMUPCCD = #blankUpc
;
-- open the cursor
OPEN blanksCursor;
-- load the next row from the cursor
FETCH NEXT FROM blanksCursor
INTO #IMPN;
-- loop through each row of the cursor
WHILE ##FETCH_STATUS = 0
BEGIN
--PRINT 'IMPN: ' + #IMPN;
-- try to create a new random number
SET #retryRandom = 1;
WHILE #retryRandom = 1
BEGIN
-- get a random number for the UPC, then left-pad it with zeros to 6 digits
SET #random = RIGHT('00000' + CONVERT(VARCHAR, FLOOR(RAND() * 999999)), 6);
-- concatenate the UPC prefix with the random number
SET #IMUPCCD = #upcPrefix + #random
--PRINT 'IMUPCCD: ' + #IMUPCCD;
-- see if this UPC already exists on another item
IF (SELECT COUNT(*) FROM [Mfg_ITMMAST] WHERE [IMUPCCD] = #IMUPCCD) > 0
SET #retryRandom = 1; -- UPC already existed (collision) try again
ELSE
SET #retryRandom = 0; -- didn't already exist, so exit out of loop
END
--PRINT 'Updating...';
-- Update the UPC with the random number
UPDATE [Mfg_ITMMAST]
SET IMUPCCD = #IMUPCCD
WHERE IMPN = #IMPN
;
-- Load the next result
FETCH NEXT FROM blanksCursor
INTO #IMPN;
END
CLOSE blanksCursor;
DEALLOCATE blanksCursor;
GO
Running the Stored Procedure
exec uspScanForBlankUpcs;
Resources that I used for this procedure:
MSDN - Creating Stored Procedure
MSDN - DECLARE CURSOR (Transact-SQL)
This answer focuses on how to assign UPC codes from your company prefix to new products when there are "gaps" in the sequence or you are reusing UPC codes from inactive product (not recommended).
UPC codes are made up of 3 parts:
The first is the company prefix.
The second is the item reference.
The third is the check digit.
In this example the company prefix is 0601040. The leading zero is beyond the scope of this question.
The item reference is a range of numbers the size of which depends up on the number of digits in the company prefix. In this example we have 6 digits in the company prefix (don't count the leading zero) and 1 digit for the check digit which leaves 5 digits for the item reference. This makes the item reference range 0 through 99999. You need to find numbers in this range that are not used.
The check digit is computed using the other 11 digits of the UPC code:
http://www.gs1.org/how-calculate-check-digit-manually
Unless you need to store invalid UPC codes because you accept bad data from an outside system there are advantages to storing the company prefix and item reference as separate fields in the table and making the UPC an indexed persistent computed field:
-- Function to output UPC code based on Company Prefix and Item Reference:
CREATE FUNCTION [dbo].[calc_UPC]
(#company_prefix varchar(10), #item_reference int)
RETURNS char(12)
WITH SCHEMABINDING
AS
BEGIN
declare
#upc char(12),
#checkdigit int
if SUBSTRING(#company_prefix, 1, 1) = 0
begin
set #upc = substring(#company_prefix, 2, 10) + right('000000000000' + ltrim(str(#item_reference)), 12 - len(#company_prefix))
set #checkdigit = (1000 - (
convert(int, substring(#upc, 1, 1)) * 3 +
convert(int, substring(#upc, 2, 1)) * 1 +
convert(int, substring(#upc, 3, 1)) * 3 +
convert(int, substring(#upc, 4, 1)) * 1 +
convert(int, substring(#upc, 5, 1)) * 3 +
convert(int, substring(#upc, 6, 1)) * 1 +
convert(int, substring(#upc, 7, 1)) * 3 +
convert(int, substring(#upc, 8, 1)) * 1 +
convert(int, substring(#upc, 9, 1)) * 3 +
convert(int, substring(#upc, 10, 1)) * 1 +
convert(int, substring(#upc, 11, 1)) * 3)) % 10
set #upc = rtrim(#upc) + ltrim(str(#checkdigit))
end
return #upc
END
GO
-- Example Table of products:
CREATE TABLE [dbo].[Product](
[product_id] [int] IDENTITY(1,1) NOT NULL,
[company_prefix] [varchar](10) NULL,
[item_reference] [int] NULL,
[upc] AS ([dbo].[calc_UPC]([company_prefix],[item_reference])) PERSISTED,
CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED
(
[product_id] ASC
))
ALTER TABLE [dbo].[Product] WITH CHECK ADD CONSTRAINT [item_reference_greater_equal_zero] CHECK (([item_reference]>=(0)))
GO
ALTER TABLE [dbo].[Product] CHECK CONSTRAINT [item_reference_greater_equal_zero]
GO
-- Existing records with UPC codes:
insert product (company_prefix, item_reference) values ( '0601040', 3)
insert product (company_prefix, item_reference) values ( '0601040', 5)
-- Example of 4 new products without UPC codes
insert product DEFAULT VALUES
insert product DEFAULT VALUES
insert product DEFAULT VALUES
insert product DEFAULT VALUES
GO
-- Next we need a table of all possible item references.
-- This is the best implementation I have found for generating numbers:
--Creates a table of sequential numbers, useful for all sorts of things
--Created 08/26/05 by Oskar Austegard from article at
--http://msdn.microsoft.com/library/en-us/dnsqlpro03/html/sp03k1.asp
--Limits: #Min and #Max must be between -2147483647 and 2147483647, including.
--If #Max <= #Min, only a single record with #Min is created
CREATE FUNCTION [dbo].[NumberTable] (#Min int, #Max int)
RETURNS #T TABLE (Number int NOT NULL PRIMARY KEY)
AS
BEGIN
-- Seed the table with the min value
INSERT #T VALUES (#Min)
--Loop until all the rows are created, inserting ever more records for each iteration (1, 2, 4, etc)
WHILE ##ROWCOUNT > 0
BEGIN
INSERT #T
--Get the next values by adding the current max - start value + 1 to each existing number
--need to calculate increment value first to avoid arithmetic overflow near limits of int
SELECT t.Number + (x.MaxNumber - #Min + 1)
FROM #T t
CROSS JOIN (SELECT MaxNumber = MAX(Number) FROM #T) x --Current max
WHERE
--Do not exceed the Max - shift the increment to the right side to take advantage of index
t.Number <= #Max - (x.MaxNumber - #Min + 1)
END
RETURN
END
GO
-- For 10,000 numbers the performance of this function is good,
-- but when the range is known I prefer the performance I get with a static table:
-- Create a table of numbers between 0 and 99999
CREATE table Numbers (number int)
insert Numbers (number)
select n.Number
from dbo.NumberTable(0, 99999) n
-- Now we can easily assign UPC codes using the available item reference values in your Company Prefix in a single update:
declare #company_prefix varchar(10)
set #company_prefix = '0601040' -- The function requires the leading zero
update
p
set
item_reference = n.number,
company_prefix = #company_prefix
from
(
select
p.product_id,
ROW_NUMBER() OVER (order by product_id) [row]
from
dbo.product p
where
p.company_prefix is null
) u
inner join dbo.product p on p.product_id = u.product_id
inner join
(
select
s.Number,
ROW_NUMBER() over (order by s.Number) [row]
from
(
select n.Number from
(
select
n.Number
from
dbo.Numbers n --Table(#sequence, #size - 1) n
left outer join dbo.Product p
on p.company_prefix = #company_prefix
and n.Number = p.item_reference
where
p.product_id is null
) n
) s
) n on n.[row] = u.[row]
GO
select * from product
Using this approach you don't need to worry about invalid check digits and you can easily assign UPC codes to new products from your GS1 assigned UCC block. It also makes it easy to start assigning UPC codes from a new company prefix. You can support EAN13 codes the same way with a small change to the function:
CREATE FUNCTION [dbo].[calc_EAN13]
(#company_prefix varchar(10), #item_reference int)
RETURNS char(13)
WITH SCHEMABINDING
AS
BEGIN
declare
#ean13 char(13),
#checkdigit int
set #ean13 = #company_prefix
set #ean13 = #company_prefix + right('0000000000000' + ltrim(str(#item_reference)), 12 - len(#company_prefix))
set #checkdigit = (1000 - (
convert(int, substring(#ean13, 1, 1)) * 1 +
convert(int, substring(#ean13, 2, 1)) * 3 +
convert(int, substring(#ean13, 3, 1)) * 1 +
convert(int, substring(#ean13, 4, 1)) * 3 +
convert(int, substring(#ean13, 5, 1)) * 1 +
convert(int, substring(#ean13, 6, 1)) * 3 +
convert(int, substring(#ean13, 7, 1)) * 1 +
convert(int, substring(#ean13, 8, 1)) * 3 +
convert(int, substring(#ean13, 9, 1)) * 1 +
convert(int, substring(#ean13, 10, 1)) * 3 +
convert(int, substring(#ean13, 11, 1)) * 1 +
convert(int, substring(#ean13, 12, 1)) * 3)) % 10
set #ean13 = rtrim(#ean13) + ltrim(str(#checkdigit))
return #ean13
END
GO
I'm tasked to create an increasing sequence number per day for a project. Multiple processes (theoretically on multiple machines) need to generate this. It ends up as
[date]_[number]
like
20101215_00000001
20101215_00000002
...
20101216_00000001
20101216_00000002
...
Since I'm using an SQL Server (2008) in this project anyway, I tried to do this with T-SQL/SQL magic. This is where I am right now:
I created a table containing the sequence number like this:
CREATE TABLE [dbo].[SequenceTable](
[SequenceId] [bigint] IDENTITY(1,1) NOT NULL,
[SequenceDate] [date] NOT NULL,
[SequenceNumber] [int] NULL
) ON [PRIMARY]
My naive solution so far is a trigger, after insert, that sets the SequenceNumber:
CREATE TRIGGER [dbo].[GenerateMessageId]
ON [dbo].[SequenceTable]
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- The ID of the record we just inserted
DECLARE #InsertedId bigint;
SET #InsertedId = (SELECT SequenceId FROM Inserted)
-- The next SequenceNumber that we're adding to the new record
DECLARE #SequenceNumber int;
SET #SequenceNumber = (
SELECT SequenceNumber FROM
(
SELECT SequenceId, ROW_NUMBER() OVER(PARTITION BY SequenceDate ORDER BY SequenceDate ASC) AS SequenceNumber
FROM SequenceTable
) tmp
WHERE SequenceId = #InsertedId
)
-- Update the record and set the SequenceNumber
UPDATE
SequenceTable
SET
SequenceTable.SequenceNumber = ''+#SequenceNumber
FROM
SequenceTable
INNER JOIN
inserted ON SequenceTable.SequenceId = inserted.SequenceId
END
As I said, that's rather naive, and keeps a full day of rows just for a single number that I never need again anyway: I do an insert, get the generated sequence number and ignore the table afterwards. No need to store them on my side, I just need to generate them once. In addition I'm pretty sure this isn't going to scale well, gradually getting slower the more rows the table contains (i.e. I don't want to fall into that "worked on my dev machine with 10.000 rows only" trap).
I guess the current way was more me looking at SQL with some creativity, but the result seems to be - erm - less useful. More clever ideas?
Forget about that SequenceTable. You should just create two columns on your final table: a datetime and a identity. And if you really need them to be combined, just add a computed column.
I guess it would be something like that:
CREATE TABLE [dbo].[SomeTable] (
[SequenceId] [bigint] IDENTITY(1,1) NOT NULL,
[SequenceDate] [date] NOT NULL,
[SequenceNumber] AS (CAST(SequenceDate AS VARCHAR(10)) + '_' + RIGHT('0000000000' + CAST(SequenceID AS VARCHAR(10)), 10)) PERSISTED
) ON [PRIMARY]
That way will scale - you are not creating any kind of intermediary or temporary data.
Edit I still think that the answer above is the best solution. BUT there is another option: computed columns can reference functions...
So do this:
CREATE FUNCTION dbo.GetNextSequence (
#sequenceDate DATE,
#sequenceId BIGINT
) RETURNS VARCHAR(17)
AS
BEGIN
DECLARE #date VARCHAR(8)
SET #date = CONVERT(VARCHAR, #sequenceDate, 112)
DECLARE #number BIGINT
SELECT
#number = COALESCE(MAX(aux.SequenceId) - MIN(aux.SequenceId) + 2, 1)
FROM
SomeTable aux
WHERE
aux.SequenceDate = #sequenceDate
AND aux.SequenceId < #sequenceId
DECLARE #result VARCHAR(17)
SET #result = #date + '_' + RIGHT('00000000' + CAST(#number AS VARCHAR(8)), 8)
RETURN #result
END
GO
CREATE TABLE [dbo].[SomeTable] (
[SequenceId] [bigint] IDENTITY(1,1) NOT NULL,
[SequenceDate] [date] NOT NULL,
[SequenceNumber] AS (dbo.GetNextSequence(SequenceDate, SequenceId))
) ON [PRIMARY]
GO
INSERT INTO SomeTable(SequenceDate) values ('2010-12-14')
INSERT INTO SomeTable(SequenceDate) values ('2010-12-15')
INSERT INTO SomeTable(SequenceDate) values ('2010-12-15')
INSERT INTO SomeTable(SequenceDate) values ('2010-12-15')
GO
SELECT * FROM SomeTable
GO
SequenceId SequenceDate SequenceNumber
-------------------- ------------ -----------------
1 2010-12-14 20101214_00000001
2 2010-12-15 20101215_00000001
3 2010-12-15 20101215_00000002
4 2010-12-15 20101215_00000003
(4 row(s) affected)
It's ugly, but works, right? :-) No temporary table whatsoever, no views, no triggers, and it will have a decent performance (with at least an index over SequenceId and SequenceDate, of course). And you can remove records (since and identity is being used for the resulting computed field).
If you can create the actual table with a different name, and perform all of your other operations through a view, then it might fit the bill. It does also require that no transaction is ever deleted (so you'd need to add appropriate trigger/permission on the view/table to prevent that):
create table dbo.TFake (
T1ID int IDENTITY(1,1) not null,
T1Date datetime not null,
Val1 varchar(20) not null,
constraint PK_T1ID PRIMARY KEY (T1ID)
)
go
create view dbo.T
with schemabinding
as
select
T1Date,
CONVERT(char(8),T1Date,112) + '_' + RIGHT('00000000' + CONVERT(varchar(8),ROW_NUMBER() OVER (PARTITION BY CONVERT(char(8),T1Date,112) ORDER BY T1ID)),8) as T_ID,
Val1
from
dbo.TFake
go
insert into T(T1Date,Val1)
select '20101201','ABC' union all
select '20101201','DEF' union all
select '20101202','GHI'
go
select * from T
Result:
T1Date T_ID Val1
2010-12-01 00:00:00.000 20101201_00000001 ABC
2010-12-01 00:00:00.000 20101201_00000002 DEF
2010-12-02 00:00:00.000 20101202_00000001 GHI
You can, of course, also hide the date column from the view and make it default to CURRENT_TIMESTAMP.
You could do something like
CREATE TABLE SequenceTableStorage (
SequenceId bigint identity not null,
SequenceDate date NOT NULL,
OtherCol int NOT NULL,
)
CREATE VIEW SequenceTable AS
SELECT x.SequenceDate, (CAST(SequenceDate AS VARCHAR(10)) + '_' + RIGHT('0000000000' + CAST(SequenceID - (SELECT min(SequenceId) + 1 FROM SequenceTableStorage y WHERE y.SequenceDate = x.SequenceDate) AS VARCHAR(10)), 10)) AS SequenceNumber, OtherCol
FROM SequenceTableStorage x
If you create an index on the SequenceDate and SequenceId, I don't think the performance will be too bad.
Edit:
The code above might miss some sequence numbers, for example if a transaction inserts a row and then rolls back (the identity value will then be lost in space).
This can be fixed with this view, whose performance might or might not be good enough.
CREATE VIEW SequenceTable AS
SELECT SequenceDate, (CAST(SequenceDate AS VARCHAR(10)) + '_' + RIGHT('0000000000' + row_number() OVER(PARTITION BY SequenceDate ORDER BY SequenceId)
FROM SequenceTableStorage
My guess is that it will be good enough until you start getting millions of sequence numbers per day.
I tried this way to create session codes for user logging and its working;
CREATE FUNCTION [dbo].[GetSessionSeqCode]()
RETURNS VARCHAR(15)
AS
BEGIN
DECLARE #Count INT;
DECLARE #SeqNo VARCHAR(15)
SELECT #Count = ISNULL(COUNT(SessionCode),0)
FROM UserSessionLog
WHERE SUBSTRING(SessionCode,0,9) = CONVERT(VARCHAR(8), GETDATE(), 112)
SET #SeqNo = CONVERT(VARCHAR(8), GETDATE(), 112) +'-' + FORMAT(#Count+1,'D3');
RETURN #SeqNo
END
generated codes are:
'20170822-001'
,'20170822-002'
,'20170822-003'
If you don't mind the numbers not starting at one you could use DATEDIFF(dd, 0, GETDATE()) which is the number of days since 1-1-1900. That will increment every day.