验证 GST 识别号 (GSTIN)

Sun*_*til 1 sql-server constraint sql-server-2008-r2 data-validation

下图描述了 GST 标识号的格式:

在此处输入图片说明

  • 根据 2011 年印度人口普查,前 2 位数字表示唯一的州代码。例如,新德里的州代码是“07”,卡纳塔克邦的州代码是“29”。

  • 接下来的 10 个字符表示纳税人的PAN永久帐号)。

  • 第 13 位数字表示具有相同PAN 的纳税人的注册号(或实体号)。

  • 默认情况下,第 14 位数字是“Z”——目前没有任何意图。

  • 第 15 位数字是校验和数字- 可以是数字或字母字符。

也许我可以通过 PATINDEX 或 Regex 进行验证?

Han*_*non 5

为了补充 Aaron 完全有效的答案,您可能决定通过验证校验位的正确性来更深入地验证 GSTIN 号码,校验位显示为号码的最后一位。

下面的代码创建了许多函数,a 将使用这些函数CHECK CONSTRAINT来验证新行以及对 GSTIN 编号的任何修改。

前两个函数提供将 GSTIN 编号中包含的 ASCII 字符任意映射到 base-36;即0映射到09映射到9A映射到10Z映射到35

IF OBJECT_ID(N'dbo.map_char', N'FN') IS NOT NULL
DROP FUNCTION dbo.map_char;
GO
CREATE FUNCTION dbo.map_char(@c char(1))
RETURNS int
AS
BEGIN
    DECLARE @val int;
    SET @c = UPPER(@c);
    IF ASCII(@c) >= 48 AND ASCII(@c) <= 57
        SET @val = ASCII(@c) - 48;
    IF ASCII(@c) >= 65 AND ASCII(@c) <= 90
        SET @val = (ASCII(@c) - 65) + 10;
    RETURN @val;
END
GO
IF OBJECT_ID(N'dbo.unmap_char', N'FN') IS NOT NULL
DROP FUNCTION dbo.unmap_char;
GO
CREATE FUNCTION dbo.unmap_char(@v int)
RETURNS char(1)
AS
BEGIN
    DECLARE @c char(1);
    IF @v >= 0 AND @v <=9 
        SET @c = CHAR(@v + 48);
    IF @v >= 10 AND @v <= 90
        SET @c = CHAR((@v + 65) - 10);
    RETURN @c;
END
GO
Run Code Online (Sandbox Code Playgroud)

我一直无法确定印度政府是否使用相同的编码,但它似乎适用于提供的值。

此代码使用上述dbo.map_char函数来验证给定 GSTIN 号码中的校验位。

CREATE FUNCTION dbo.fn_validate_gstin
(
    @inp char(15)
)
RETURNS tinyint
AS
BEGIN
    DECLARE @return tinyint;
    DECLARE @i int = LEN(@inp);
    DECLARE @factor int = 1;
    DECLARE @char char(1);
    DECLARE @codepoint int;
    DECLARE @addend int;
    DECLARE @sum int = 0;
    IF @i <> 15 /* GSTIN MUST be 15 characters to be valid */
    BEGIN
        SET @return = 0;
    END
    ELSE
    BEGIN
        WHILE @i > 0
        BEGIN
            SET @codepoint = dbo.map_char(SUBSTRING(@inp, @i, 1));
            SET @addend = @factor * @codepoint;
            SET @addend = (@addend / 36) + (@addend % 36);
            SET @sum += @addend;
            IF @factor = 2 SET @factor = 1 ELSE SET @factor = 2;
            SET @i -= 1;
        END
    END
    DECLARE @remainder int = @sum % 36;
    IF @remainder = 0 SET @return = 1 ELSE SET @return = 0;
    RETURN @return;
END
GO
Run Code Online (Sandbox Code Playgroud)

请注意,此代码中绝对没有错误检查;我把它留给读者作为练习。1如果 GSTIN 包含有效校验位作为最后一位,则该函数返回。如果校验位无效,则返回 0。

这里我创建了一个实现dbo.fn_validate_gstin函数的表:

CREATE TABLE dbo.t
(
    i char(15) NOT NULL
        CONSTRAINT CK_t_valid_gstin
        CHECK (dbo.fn_validate_gstin(i) = 1)
);
Run Code Online (Sandbox Code Playgroud)

我们在这里插入几个“测试”GSTIN 编号:

INSERT INTO dbo.t (i) VALUES ('123456789012345');
INSERT INTO dbo.t (i) VALUES ('27AAFFM5744C1ZE');
INSERT INTO dbo.t (i) VALUES ('27AAACE7932L1ZC');
Run Code Online (Sandbox Code Playgroud)

第一次插入失败。第二次和第三次插入成功。尝试插入无效的 GSTIN 编号会导致此错误:

消息 547,级别 16,状态 0,第 80 行
INSERT 语句与 CHECK 约束“CK_t_valid_gstin”冲突。
冲突发生在数据库“tempdb”、表“dbo.t”、列“i”中。

请注意,作为约束一部分的标量函数的存在会阻止使用并行性的查询。这对您的系统可能有问题,也可能没有问题。如果您需要并行性,您可以考虑INSTEAD OF在表上使用触发器来检查插入或更新时的 GSTIN 编号。例如:

IF OBJECT_ID(N'dbo.t_with_trigger', N'U') IS NOT NULL
DROP TABLE dbo.t_with_trigger;
GO
CREATE TABLE dbo.t_with_trigger
(
    i char(15) NOT NULL
);

GO
CREATE TRIGGER t_validate
ON dbo.t_with_trigger
INSTEAD OF INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRANSACTION
    DECLARE @bOk bit = 1;
    IF EXISTS (
        SELECT TOP(1) 1
        FROM inserted i
        WHERE dbo.fn_validate_gstin(i.i) = 0
        UNION ALL
        SELECT TOP(1) 1
        FROM deleted d
        WHERE dbo.fn_validate_gstin(d.i) = 0
        )
    BEGIN
        SET @bOk = 0; 
    END
    IF @bOk = 1 
    BEGIN
        DELETE
        FROM dbo.t_with_trigger
        FROM dbo.t_with_trigger t
            INNER JOIN deleted d ON t.i = d.i;
        INSERT INTO dbo.t_with_trigger(i)
        SELECT i.i
        FROM inserted i;
        COMMIT TRANSACTION;
    END
    ELSE
    BEGIN
        ROLLBACK TRANSACTION;
        DECLARE @msg varchar(1000);
        SET @msg = 'Attempted to insert/update using an invalid GSTIN number.';
        RAISERROR (@msg, 14, 1);
    END
END
GO
Run Code Online (Sandbox Code Playgroud)

插入无效的 GSTIN 号码:

INSERT INTO dbo.t_with_trigger (i) 
VALUES ('123456789012345')
    , ('27AAFFM5744C1ZE')
    , ('27AAACE7932L1ZC');
Run Code Online (Sandbox Code Playgroud)

导致此错误:

消息 50000,级别 14,状态 1,过程 t_validate,第 37 行 [批处理起始行 129]
尝试使用无效的 GSTIN 编号插入/更新。
消息 3609,级别 16,状态 1,第 130 行
事务在触发器中结束。该批次已中止。

我的实现基于LUHN 算法