【问题标题】:Calculating a checksum in SQL在 SQL 中计算校验和
【发布时间】:2023-03-08 14:05:01
【问题描述】:

我有一个巴西 CPF 号码的数据库字段,并想检查它们的有效性。这些是 11 位字符串,即 9 位和 2 位校验和。

我目前在 MS Excel 中实现了校验和(见下文),但我想找到一种在 SQL 中执行此操作的方法。

校验和的工作原理如下:(抓紧,这太疯狂了。)

  • CPF 编号以 ABCDEFGHI / JK 形式或直接写为 ABCDEFGHIJK,其中的数字不能全部相同。
  • J 称为 CPF 编号的第 1 位校验。
  • K 称为 CPF 编号的第二个校验位。

第一个数字(J):

  • 将前 9 位的每个数字乘以一个常数:
    10*A + 9*B + 8*C + 7*D + 6*E + 5*F + 4*G + 3*H + 2*I

  • 将此和除以 11,如果余数为 0 或 1,则 J 将为 0。如果余数 >=2,则 J 将为 11 - remainder

第二位(K):(相同的计算,但包括数字J)

  • 将前 10 位的每个数字乘以一个常数:
    11A + 10B + 9C + 8D + 7E + 6F + 5G + 4H + 3I + 2J

  • 将此和除以 11,如果余数为 0 或 1,则 K 将为 0。如果余数 >=2,则 K 将为 11 - remainder

--MS Excel中的实现--
假设 CPF 在 A2 中。
欢迎在这里进行优化,但并不是这个问题的重点。
数字 J:=IF(MOD(SUM(MID($A2,1,1)*10,MID($A2,2,1)*9,MID($A2,3,1)*8,MID($A2,4,1)*7,MID($A2,5,1)*6,MID($A2,6,1)*5,MID($A2,7,1)*4,MID($A2,8,1)*3,MID($A2,9,1)*2),11)<=1,NUMBERVALUE(LEFT(RIGHT($A2,2),1))=0,NUMBERVALUE(LEFT(RIGHT($A2,2),1))=(11-MOD(SUM(MID($A2,1,1)*10,MID($A2,2,1)*9,MID($A2,3,1)*8,MID($A2,4,1)*7,MID($A2,5,1)*6,MID($A2,6,1)*5,MID($A2,7,1)*4,MID($A2,8,1)*3,MID($A2,9,1)*2),11)))
数字 K: =IF(MOD(SUM(MID($A2,1,1)*11,MID($A2,2,1)*10,MID($A2,3,1)*9,MID($A2,4,1)*8,MID($A2,5,1)*7,MID($A2,6,1)*6,MID($A2,7,1)*5,MID($A2,8,1)*4,MID($A2,9,1)*3,MID($A2,10,1)*2),11)<=1,NUMBERVALUE(LEFT(RIGHT($A2,1),1))=0,NUMBERVALUE(LEFT(RIGHT($A2,1),1))=(11-MOD(SUM(MID($A2,1,1)*11,MID($A2,2,1)*10,MID($A2,3,1)*9,MID($A2,4,1)*8,MID($A2,5,1)*7,MID($A2,6,1)*6,MID($A2,7,1)*5,MID($A2,8,1)*4,MID($A2,9,1)*3,MID($A2,10,1)*2),11)))

【问题讨论】:

标签: sql oracle algorithm checksum


【解决方案1】:

我的测试表:

-- Create a table called CPF
CREATE TABLE CPF(Id integer PRIMARY KEY, No integer);

-- Create few records in this table 
INSERT INTO CPF VALUES(1, 12345678901);

我的嵌套query

SELECT No, 
(CASE WHEN (J != J2) THEN 'J wrong!' ELSE 'J ok!' END) as Jchk,
(CASE WHEN (K != K2) THEN 'K wrong!' ELSE 'K ok!' END) as Kchk
FROM 
(SELECT No, J, K,
(CASE WHEN MJ < 2 THEN 0 ELSE 11 - MJ END) as J2,
(CASE WHEN MK < 2 THEN 0 ELSE 11 - MK END) as K2
FROM 
(SELECT No, J, K,
MOD(10*A + 9*B + 8*C + 7*D + 6*E + 5*F + 4*G + 3*H + 2*I, 11) as MJ,
MOD(11*A + 10*B + 9*C + 8*D + 7*E + 6*F + 5*G + 4*H + 3*I + 2*J, 11) as MK 
FROM 
 (SELECT
  No,
  substr(to_char(No), 1, 1) as A,
  substr(to_char(No), 2, 1) as B,
  substr(to_char(No), 3, 1) as C,
  substr(to_char(No), 4, 1) as D,
  substr(to_char(No), 5, 1) as E,
  substr(to_char(No), 6, 1) as F,
  substr(to_char(No), 7, 1) as G,
  substr(to_char(No), 8, 1) as H,
  substr(to_char(No), 9, 1) as I,
  substr(to_char(No), 10, 1) as J,
  substr(to_char(No), 11, 1) as K
  FROM CPF)))
  ;

【讨论】:

  • 添加了一个正则表达式来检查所有数字是否相同,并添加了一个长度不超过 11 个字符的检查。 (CASE WHEN length(CPF)&gt;11 OR regexp_like (CPF, '^(\d)\1*$') OR (J!=J2) OR (K!=K2) THEN 'INVALID' ELSE 'VALID' END) as CPF_VALID
【解决方案2】:

假设您有一个表,其中包含一个 id 主键列和一个 cpf 列是 NUMBER(9,0) 数据类型,那么类似于:

WITH digits ( id, a, b, c, d, e, f, g, h, i ) AS (
  SELECT id,
         MOD( TRUNC( cpf / 1e8 ), 10 ),
         MOD( TRUNC( cpf / 1e7 ), 10 ),
         MOD( TRUNC( cpf / 1e6 ), 10 ),
         MOD( TRUNC( cpf / 1e5 ), 10 ),
         MOD( TRUNC( cpf / 1e4 ), 10 ),
         MOD( TRUNC( cpf / 1e3 ), 10 ),
         MOD( TRUNC( cpf / 1e2 ), 10 ),
         MOD( TRUNC( cpf / 1e1 ), 10 ),
         MOD( TRUNC( cpf / 1e0 ), 10 )
  FROM   your_table
),
values1 ( id, j, k ) AS (
  SELECT id,
         MOD( 10*A +  9*B +  8*C +  7*D +  6*E +  5*F +  4*G +  3*H + 2*I, 11 ),
         11*A + 10*B +  9*C +  8*D +  7*E +  6*F +  5*G +  4*H + 3*I
  FROM   digits
),
values2 ( id, j, k ) AS (
  SELECT id,
         CASE WHEN j <= 1 THEN 0 ELSE 11 - j END,
         MOD( k + 2 * CASE WHEN j <= 1 THEN 0 ELSE 11 - j END, 11 )
  FROM   values1
)
SELECT id,
       j,
       CASE WHEN k <= 1 THEN 0 ELSE 11 - k END AS k
FROM   values2

【讨论】:

  • 首先,感谢您的参与。假设我从 9 位字符串开始并添加校验位。我从 11 位数字字符串开始并进行比较。
【解决方案3】:

@SAR622:很好的问题,感谢算法。

这是一个用于 SQL Server 的 t-SQL 解决方案,以防万一。请注意,Cadastro de Pessoas Físicas (CPF) 数字只能有 11 位数字(以零开头),即它们不能超过 10^12-1。如果您在数据集中注意到 14 位数字,这些可能是发给企业的 Cadastro Nacional da Pessoa Jurídica (CNPJ) 数字(或拼写错误或其他)。可以生成(批量)和验证(单独)here 的假 CPF 和 CNPJ 号码。此外,this site 提供了有关由其 CNPJ 定位的企业的更多信息(将其视为隐式 CNPJ 验证)。验证 CPF 编号时,请记住检查该编号是否在 [0, 10^12-1] 范围内。您可能需要删除任何标点符号和其他无效字符(作为用户,我们往往会打错字)。

这个输入表有前 5 个无效的 CPF 号码和后 4 个有效的号码:

IF OBJECT_ID('tempdb..#x') IS NOT NULL DROP TABLE #x;
CREATE TABLE #x  (CPF BIGINT default NULL);
INSERT INTO #x (CPF) VALUES (12345678900);
INSERT INTO #x (CPF) VALUES (11);
INSERT INTO #x (CPF) VALUES (1010101010101010);
INSERT INTO #x (CPF) VALUES (11111179011525590);
INSERT INTO #x (CPF) VALUES (-32081397641);
INSERT INTO #x (CPF) VALUES (00000008726210061);
INSERT INTO #x (CPF) VALUES (56000608314);
INSERT INTO #x (CPF) VALUES (73570630706);
INSERT INTO #x (CPF) VALUES (93957133564);

以下 t-SQL 函数将实现模块化,但可能会比后面的原始 t-SQL 慢。或者,您可以使用 TABLE 输入/输出或存储过程创建 t-SQL 函数。

ALTER FUNCTION fnIsCPF(@n BIGINT) RETURNS INT AS
BEGIN
    DECLARE @isValid BIT = 0;
    IF (@n > 0 AND @n < 100000000000)
    BEGIN
        --Parse out numbers
        DECLARE @a TINYINT = FLOOR( @n / 10000000000)% 10;
        DECLARE @b TINYINT = FLOOR( @n / 1000000000)% 10;
        DECLARE @c TINYINT = FLOOR( @n / 100000000)% 10;
        DECLARE @d TINYINT = FLOOR( @n / 10000000)% 10;
        DECLARE @e TINYINT = FLOOR( @n / 1000000)% 10;
        DECLARE @f TINYINT = FLOOR( @n / 100000)% 10;
        DECLARE @g TINYINT = FLOOR( @n / 10000)% 10;
        DECLARE @h TINYINT = FLOOR( @n / 1000)% 10;
        DECLARE @i TINYINT = FLOOR( @n / 100)% 10;

        DECLARE @j TINYINT =  ISNULL(NULLIF(NULLIF(11-( 10*@a + 9*@b + 8*@c + 7*@d + 6*@e + 5*@f + 4*@g + 3*@h + 2*@i) % 11, 11), 10), 0);
        DECLARE @k TINYINT =  ISNULL(NULLIF(NULLIF(11 - (11*@a +10*@b + 9*@c + 8*@d + 7*@e + 6*@f + 5*@g + 4*@h + 3*@i + 2 * @j)% 11, 11), 10), 0);
        RETURN CASE WHEN @j=FLOOR(@n / 10)% 10 AND @k=FLOOR(@n)% 10 THEN 1 ELSE 0 END
    END;
    RETURN @isValid;
END;

输出是:

SELECT CPF, isValid=dbo.fnIsCPF(CPF) FROM #x

CPF                 isValid
12345678900         0
11                  0
1010101010101010    0
11111179011525590   0
-32081397641        0
8726210061          1
56000608314         1
73570630706         1
93957133564         1

表的 t-SQL:

WITH digits ( CPF, a, b, c, d, e, f, g, h, i ) AS (
  SELECT CPF,
    FLOOR( CPF / 10000000000)% 10,
    FLOOR( CPF / 1000000000)% 10,
    FLOOR( CPF / 100000000)% 10,
    FLOOR( CPF / 10000000)% 10,
    FLOOR( CPF / 1000000)% 10,
    FLOOR( CPF / 100000)% 10,
    FLOOR( CPF / 10000)% 10,
    FLOOR( CPF / 1000)% 10,
    FLOOR( CPF / 100)% 10
  FROM   #x
),
jk ( CPF, j, k ) AS (
  SELECT CPF, ISNULL(NULLIF(NULLIF(11-( 10*A + 9*B + 8*C + 7*D + 6*E + 5*F + 4*G + 3*H + 2*I) % 11, 11), 10), 0),
    11*A +10*B + 9*C + 8*D + 7*E + 6*F + 5*G + 4*H + 3*I
  FROM digits
),
jk2 ( CPF, j, k ) AS (
  SELECT CPF, j, ISNULL(NULLIF(NULLIF(11 - (k + 2 * j)% 11, 11), 10), 0)
  FROM jk
)
SELECT CPF, isValid=CASE WHEN CPF>0 AND CPF<99999999999 AND j=FLOOR( CPF / 10)% 10 AND k=FLOOR( CPF)% 10 THEN 1 ELSE 0 END
FROM jk2

产生相同的输出。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    • 2015-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多