在SQL中的列中生成/更新唯一的随机条形码编号

时间:2012-01-11 21:10:42

标签: sql

我们是一家制造公司,我有一个SQL表[FSDBGL],它包含我们拥有的每个项目的信息。这包括ItemNumber,ItemUPC和ItemStatus的列。对于必需的项目,ItemUPC列中的某些数据为空。

我需要做的是在ItemUPC列内分配/插入随机唯一条形码编号(尚未采用)。该数字必须为12位数字,并以“601040xxxxxx”开头,仅随机化最后6位数字。对于每个项目编号,不必对每一行进行此操作。

- 仅检查/更新[ItemNumber](在40000-01 - 50000-01之间)(最后的-01也可以是-02)

我需要忽略/排除以下列属性来获取数字:
- ItemStatus(仅当它被设置为'O'表示过时)
- ItemUPC(如果已经有条形码编号)

我想为此自定义SQL查询,我现在可以填充单元格,并在夜间进程中实现更新任何新创建的Item#。

这是CREATE Script视图:

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

2 个答案:

答案 0 :(得分:1)

随机数:您可以使用游标遍历每个缺少UPC的行,并使用ROUND(RAND() * 999999,0, 0)为每个行获取一个随机数,然后在执行更新之前检查冲突。游标查询的where子句应该非常简单... ItemNumber上的正则表达式,ItemStatus!='O',ItemUPC!= null或''或0(或默认值为)。

应该可以随时重新运行sproc,因为它使用随机数并检查碰撞。

更有效的方法是使用序列发出的数字而不是随机数。只要您能够将最后使用的数字存储在某个表的某个表中,我相信您可以使用一个查询添加所有UPC数字,而不必使用UPDATE ... FROM语法和{ {1}}用户变量的语法。


编辑:添加存储过程和其他评论

首先请注意,此数据库设计可能不是最佳选择。此表上没有主键,也没有索引。如果此表具有任何大量记录,则查询将变慢,并且此存储过程将非常慢。

我还必须做出一些假设。由于IMUPCCD不能为空,因此我假设当UPC为“空白”时,默认值为601040。由于没有主键,我无法通过游标更新,而是必须运行单独的更新语句,这也更慢。我还必须假设IMPN唯一标识一行数据。我不确定这些假设是否正确,因此您可能需要修改sproc以适合您的情况。

此外,原始问题引用了ItemStatus,但架构中没有给出状态列,因此我无法在测试中限制它的结果。但是,您可以轻松地将其添加到WHERE子句中的存储过程的SELECT @counter = @counter + 1语句中。

测试数据 (在名为stackoverflow的数据库中)

DECLARE blanksCursor CURSOR FOR ... WHERE ...

存储过程

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

运行存储过程

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

我用于此程序的资源:
MSDN - Creating Stored Procedure
MSDN - DECLARE CURSOR (Transact-SQL)

答案 1 :(得分:0)

这个答案的重点是如何在有&#34;空白&#34;时将公司前缀的UPC代码分配给新产品。在序列中或您正在重用非活动产品中的UPC代码(不推荐)。

UPC代码由3部分组成:

第一个是公司前缀。 第二个是项目参考 第三个是校验位。

在此示例中,公司前缀为0601040.前导零超出了此问题的范围。

项目参考是一系列数字,其大小取决于公司前缀中的位数。在这个例子中,我们在公司前缀中有6位数字(不计算前导零)和校验位的1位数,其中5位数字用于项目参考。这使得项目参考范围为0到99999.您需要查找此范围内未使用的数字。

使用UPC代码的其他11位数计算校验位: http://www.gs1.org/how-calculate-check-digit-manually

除非您需要存储无效的UPC代码,因为您接受来自外部系统的错误数据,因此将公司前缀和项目引用存储为表中的单独字段并使UPC成为索引的持久计算字段是有好处的:

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

使用这种方法,您不必担心无效的校验位,您可以轻松地将UPC代码分配给GS1分配的UCC块中的新产品。它还可以轻松地从新的公司前缀开始分配UPC代码。只需对函数进行少量更改,即可以相同的方式支持EAN13代码:

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