Generating/updating unique random barcode numbers in a column in SQL - sql

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

Related

PATINDEX to detect letters and six numbers

SQL Server 2012.
table a
Id name number
1 py/ut/455656/ip null
2 py/ut/jl/op null
3 py/utr//grt null
I want to retrieve the numbers
Id name number
1 py/ut/455656/ip 455656
2 py/ut/jl/op null
3 py/utr//grt null
here the sql script
update table a
set number=SUBSTRING(name,PATINDEX('py/u/[0-9]',name)+6,6)
I need to retrieve the number after py/ut and before the / . The script works well if there is a number. For the second row it is delivering jl/op
The number always get six algarisms.
check this :
declare #Number nvarchar(20)='py/ut/455656/ip'
Declare #intAlpha int
SET #intAlpha = PATINDEX('%[^0-9]%', #Number )
BEGIN
WHILE #intAlpha > 0
BEGIN
SET #Number = STUFF(#Number , #intAlpha, 1, '' )
SET #intAlpha = PATINDEX('%[^0-9]%', #Number )
END
END
select #Number
Simply add a where clause:
update table a
set number = SUBSTRING(name, PATINDEX('py/u/[0-9]', name) + 6, 6)
where name like '%py/u/[0-9]%'
DECLARE #a TABLE([name] NVARCHAR(MAX), number INT NULL)
INSERT #a([name]) VALUES ('py/ut/455656/ip'), ('py/ut/jl/op'), ('py/utr//grt')
UPDATE #a
SET number = SUBSTRING([name], PATINDEX('%/[0-9][0-9][0-9][0-9][0-9][0-9]/%', [name]) + 1, 6)
WHERE [name] LIKE '%/[0-9][0-9][0-9][0-9][0-9][0-9]/%'
SELECT * FROM #a
Make the pattern more or less specific to taste.
PATINDEX works like the LIKE operator, so the pattern you're using is actually returning 0 for both rows and is just coincidentally working for the value that has 6 numbers starting after the py/ut/ part. You need to add a wildcard to the pattern you're passing into PATINDEX and a WHERE clause to the UPDATE statement.
Try something like this:
-- Length of the path prefix, assumes it is constant
DECLARE #lenPrefix int
set #lenPrefix = 6
DECLARE #lenNumber int
SET #lenNumber = 6
UPDATE TABLE a
SET number=SUBSTRING(name, PATINDEX('py/ut/[0-9]%', name) + #lenPrefix, #lenNumber)
WHERE
PATINDEX('py/ut/[0-9]%', name) > 0
If name field contain only one number this script should work for you :
What did I do:
I have used PATINDEX() to find which point the number starts.
Also, again I used PATINDEX() with REVERSE() name to find end point.
I used LEN() to find total length the field.
Then finally I used SUBSTRING() to capture number from starting point to total length - (starting point) - (end point).
Check it:
--DROP TABLE #A
--GO
CREATE TABLE #A
(
id int
,name VARCHAR(100)
);
INSERT INTO #A
VALUES (1, 'py/ut/455656/ip')
, (2, 'py/ut/jl/op ')
, (3, 'py/utr//grt ')
SELECT
id
,name
/*
,PATINDEX('%[0-9]%', name) - 1 --starting poing
,PATINDEX('%[0-9]%', REVERSE(name)) - 1 --reverse starting point
*/
,CASE WHEN (PATINDEX('%[0-9]%', name) - 1)>0
THEN SUBSTRING(name
,PATINDEX('%[0-9]%', name),
LEN(NAME) - (PATINDEX('%[0-9]%', name) - 1) - (PATINDEX('%[0-9]%', REVERSE(name)) - 1)
)
ELSE null END Number
FROM #A

Shuffling numbers based on the numbers from the row

Let's say we have a 12-digit numbers in a given row.
AccountNumber
=============
136854775807
293910210121
763781239182
Is it possible to shuffle the numbers of a single row solely based on the numbers of that row? e.g. 136854775807 would become 573145887067
I have created a user-defined function to shuffle the numbers.
What I have done is, taken out each character and stored it into a table variable along with a random number. Then at last concatenated each character in the ascending order of the random number.
It is not possible to use RAND function inside a user-defined function. So created a VIEW for taking a random number.
View : random_num
create view dbo.[random_num]
as
select floor(rand()* 12) as [rnd];
It's not necessary that the random number should be between 0 and 12. We can give a larger number instead of 12.
User-defined function : fn_shuffle
create function dbo.[fn_shuffle](
#acc varchar(12)
)
returns varchar(12)
as begin
declare #tbl as table([a] varchar(1), [b] int);
declare #i as int = 1;
declare #l as int;
set #l = (select len(#acc));
while(#i <= #l)
begin
insert into #tbl([a], [b])
select substring(#acc, #i, 1), [rnd] from [random_num]
set #i += 1;
end
declare #res as varchar(12);
select #res = stuff((
select '' + [a]
from #tbl
order by [b], [a]
for xml path('')
)
, 1, 0, ''
);
return #res;
end
Then, you would be able to use the function like below.
select [acc_no],
dbo.[fn_shuffle]([acc_no]) as [shuffled]
from dbo.[your_table_name];
Find a demo here
I don't really see the utility, but you can. Here is one way:
select t.accountnumber, x.shuffled
from t cross apply
(select digit
from (values (substring(accountnumber, 1, 1)),
substring(accountnumber, 2, 1)),
. . .
substring(accountnumber, 12, 1))
)
) v(digit)
order by newid()
for xml path ('')
) x(shuffled);

Cannot insert the value NULL into column with procedure

This is not a duplicate.
I do understand what the issue means but I don't understand why because the variable contains data. I'm basically trying to make a char(4) column increase alone (just like identity with integers). If the table doesn't contain anything, the first value would be 'C001' otherwise, It simply increase based on the last record.
CREATE PROCEDURE ADD_CL(#nom VARCHAR(20),
#dn DATE)
AS
BEGIN
DECLARE #B CHAR(4)
DECLARE #B_to_int INT
DECLARE #B_new_value CHAR(4)
IF EXISTS(SELECT TOP 1 *
FROM CLIENT)
SET #B_new_value = 'C001'
ELSE
BEGIN
SELECT TOP 1 #B = code_client
FROM client
ORDER BY code_client DESC
SET #B_to_int = CAST(SUBSTRING(#B, 2, 3) AS INTEGER)
SET #B_to_int = #B_to_int + 1;
SET #B_new_value = LEFT(#B, 1) + RIGHT('00' + CAST(#B_to_int AS INT), 3)
END
INSERT INTO CLIENT
VALUES (#B_new_value,
#nom,
#dn)
END
Cannot insert the value NULL into column 'code_client', table 'dbo.CLIENT'; column does not allow nulls. INSERT fails.
#B_new_value represent code_client
Your If Exists should be If Not Exists.
So change
if exists(select TOP 1 * from CLIENT)
to
if not exists(select TOP 1 * from CLIENT)
Also you are adding 00 to your final #B_to_int which is cast as int. so it will show C2,C3 and so on.
If you want to retain the same format, cast it to varchar
SET #B_new_value = LEFT(#B,1) + '00' + CAST(#B_to_int as varchar)
Above line will work only till the count is 9. and then it will continue replicating itself with 1 because 10 will be 0010 and final output will be C0010. To eliminate this issue, use replicate and replicate 0 until 3 characters.
SET #B_new_value = LEFT(#B,1) + REPLICATE('0',3-LEN(#B_to_int)) + #B_to_int
Good Luck.
The other answers already tell you that you should be using NOT EXISTS.
This numbering scheme is quite possibly something you'll regret but you could simplify this a lot as well as making it safer in conditions of concurrency and when you run out of numbers by just doing
CREATE PROCEDURE ADD_CL(#nom VARCHAR(20),
#dn DATE)
AS
BEGIN
DECLARE #B VARCHAR(5);
SET XACT_ABORT ON;
BEGIN TRAN
SELECT #B = FORMAT(1 + RIGHT(ISNULL(MAX(code_client), 'C000'), 3), '\C000')
FROM CLIENT WITH(ROWLOCK, UPDLOCK, HOLDLOCK);
IF ( LEN(#B) > 4 )
THROW 50000, 'Exceeded range',1;
INSERT INTO CLIENT
VALUES (#B,
#nom,
#dn);
COMMIT
END
I believe the following should be 'NOT EXISTS'
if EXISTS(select TOP 1 * from CLIENT)

SQL Server: auto-generated custom format sequence number

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!!!

Cannot perform multi-row insert on trigger when inserting record with unique id

Here is my trigger:
ALTER TRIGGER DONORINFO_INSERT
ON [dbo].[DONORINFO] INSTEAD OF INSERT
AS
DECLARE #sequence AS VARCHAR(50) = ''
DECLARE #tranLen VARCHAR(10)
SET #sequence = (SELECT TOP 1 SUBSTRING([DONORID], 3, 8)
FROM [dbo].[DONORINFO]
ORDER BY [DONORID] DESC)
IF (#sequence IS NULL OR #sequence = '')
BEGIN
SELECT #sequence = REPLICATE('0', 7 ) + '1'
END
ELSE
BEGIN
SELECT #tranLen = LEN(#sequence)
SELECT #sequence = #sequence + 1
SELECT #tranLen = ABS(#tranLen - LEN(CAST(#sequence AS INT)))
SELECT #sequence = REPLICATE('0', #tranLen) + #sequence
END
DECLARE #DONORID AS [nvarchar](50) = 'DN' + CONVERT(VARCHAR, #sequence)
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
SELECT #DONORID, inserted.DONORNAME
FROM inserted
In the first lines of the script, I'm reading the DONORINFO table in which I checked if the unique id exists. After that, I will insert the record into that table. I tested the first time, the insert into select script works but for the second time around, it fails and sends and a violation of primary key error.
But if I tested row by row insert, it works.
This is the row by row insert script that works.
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
VALUES ('DN00000001', 'test')
If I run it twice, the records will be like this:
DONORID DONORNAME
---------------------
DN00000001 test
DN00000002 test
This is the insert into select script that doesn't work:
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
SELECT
'',
[NameOfDonor]
FROM
[dbo].[_TEMPENDOWMENTFUND] AS ENDF
WHERE
[ENDF].[NameOfDonor] NOT IN (SELECT [DONORNAME]
FROM [dbo].[DONORINFO])
The _TEMPDOWMENTFUND is a table I created that will store the data that was migrated from an Excel worksheet, the purpose of the trigger is that it will generate a unique DONORID for every record inserted on the DONORINFO table.
Now my problem is that, I want to perform the insert into select statement which is a multiple row insert, but I'm having a hard time figuring out what is going wrong to the trigger I created.
Any help would be appreciated. Thanks.
Make #sequence totally int (and probably rename it to last_id), add characters in the very end.
To number rows use ROW_NUMBER() in final select from INSERTED:
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
SELECT
'DN' + REPLICATE('0', ABS(#len_you_need - LEN(t.generated_id))) + CAST (t.generated_id as varchar(100)),
t.DONORNAME
FROM
(
SELECT
i.DONORNAME,
#sequence+ROW_NUMBER()OVER(ORDER BY i.DONORNAME) as generated_id
FROM inserted i
) t
#len_you_need - is the length of DONORID you need. I guess this may be a constant of 8 characters. In your source you are calculating this here:
SELECT #tranLen = LEN(#sequence)
t.rn is a "sequence" value generated in subquery given above, which has t alias. Renamed it to generated_id for clarity.
This block:
BEGIN
SELECT #tranLen = LEN(#sequence)
SELECT #sequence = #sequence + 1
SELECT #tranLen = ABS(#tranLen - LEN(CAST(#sequence AS INT)))
SELECT #sequence = REPLICATE('0', #tranLen) + #sequence
END
is unnecessary anymore.
Here is the complete solution #ivan-starostin helped me answer.
ALTER TRIGGER DONORINFO_INSERT ON [dbo].[DONORINFO]
INSTEAD OF INSERT, UPDATE
AS
DECLARE #sequence AS VARCHAR(50) = ''
DECLARE #tranLen VARCHAR(10)
SET #sequence = (SELECT TOP 1 SUBSTRING([DONORID], 3, 8) FROM [dbo].[DONORINFO] ORDER BY [DONORID] DESC)
IF (#sequence IS NULL OR #sequence = '')
BEGIN
SELECT #sequence = REPLICATE('0', 7 )
END
ELSE
BEGIN
SELECT #tranLen = LEN(#sequence)
SELECT #sequence = #sequence + 1
SELECT #tranLen = ABS(#tranLen - LEN(CAST(#sequence AS INT)))
SELECT #sequence = REPLICATE('0', #tranLen) + #sequence
END
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
SELECT
'DN' + REPLICATE('0', ABS(8 - LEN(t.generated_id))) + CAST (t.generated_id as varchar(100)),
t.DONORNAME
FROM
(
SELECT
i.DONORNAME,
#sequence+ROW_NUMBER()OVER(ORDER BY i.DONORNAME) as generated_id
FROM inserted i
) t
So if I ran those two different insert into select below...
INSERT INTO [dbo].[DONORINFO] ([DONORNAME])
SELECT
[NameOfDonor]
FROM [dbo].[_TEMPENDOWMENTFUND] AS ENDF
WHERE [ENDF].[NameOfDonor] NOT IN (SELECT [DONORNAME] FROM [dbo].[DONORINFO])
INSERT INTO [dbo].[DONORINFO] ([DONORNAME])
SELECT
[NameOfDonor]
FROM [dbo].[_TEMPENDOWED] AS ENDF
WHERE [ENDF].[NameOfDonor] NOT IN (SELECT [DONORNAME] FROM [dbo].[DONORINFO])
The donorid iteration will be something like this with these two different table sources (I already omitted donor names because of confidentiality).
DONORID DONORNAME (from _TEMPENDOWMENTFUND)
------------------------
DN00000001 test
DN00000002 test
DN00000003 test
DN00000004 test
DN00000005 test
DN00000006 test
DN00000007 test
DONORID DONORNAME (from _TEMPENDOWED)
------------------------
DN00000007 test
DN00000008 test
DN00000009 test
DN00000010 test
DN00000011 test
DN00000012 test
DN00000013 test