0

In a MySQL database, i have records with correct and incorrect luhn numbers (numbers that do not validate using the luhn algorithm).

Assuming the wrong number is due to a typing error on one digit, i would like to get the list of possible correct numbers. The goal is to filter the possible correct records with this list and then filter again with another criteria.

[edit] answering my own question, here are procedures for comparing a valid luhn number and an invalid luhn number resulting of a typing error on one digit (luhn_typo(invalid, valid)) or after swapping 2 digits (luhn_swap(invalid, valid)).

CREATE DEFINER=`root`@`localhost` FUNCTION `luhn_sum`(p_number VARCHAR(32)) RETURNS INT(11)
SQL SECURITY INVOKER
BEGIN
    DECLARE i, mysum, r, weight INT;

    SET weight = 1;
    SET mysum = 0;
    SET i = length(p_number);

    WHILE i > 0 DO
        SET r = substring(p_number, i, 1) * weight;
        SET mysum = mysum + IF(r > 9, r - 9, r);
        SET i = i - 1;
        SET weight = 3 - weight;
    END WHILE;

    RETURN mysum;
END;

CREATE DEFINER=`root`@`localhost` FUNCTION `luhn_typo`(p_number VARCHAR(32), t_number VARCHAR(32)) RETURNS TINYINT(1)
SQL SECURITY INVOKER
BEGIN
DECLARE r, val, new_val, len, str_len, weight,digit,new_digit INT;
DECLARE new_luhn VARCHAR(255);

  IF (p_number = t_number) THEN
      RETURN FALSE;
  END IF;

  SET r = luhn_sum(p_number) % 10;

  IF (r = 0) THEN
      RETURN FALSE;
  END IF;

  SET str_len = LENGTH(p_number);
  SET len = str_len;
  SET weight = 1;

  WHILE len > 0 DO
      SET digit = CAST(SUBSTRING(p_number, len, 1) AS SIGNED) * weight;
      SET val = IF(digit > 9, digit - 9, digit);

      SET new_val = IF(val < r, val - r + 10, val - r);
      SET new_digit = (IF(weight = 2 AND (new_val % 2 = 1), new_val + 9, new_val)) / weight;

      SET new_luhn = CONCAT(SUBSTRING(p_number, 1, len - 1), new_digit, SUBSTRING(p_number, len + 1, str_len - len));

      IF (new_luhn = t_number ) THEN
          RETURN TRUE;
      END IF;

      SET weight = 3 - weight;
      SET len = len - 1;
  END WHILE;

  RETURN  FALSE;
END;

CREATE DEFINER=`root`@`localhost` FUNCTION `luhn_swap`(p_number VARCHAR(32), t_number VARCHAR(32)) RETURNS VARCHAR(32) CHARSET latin1
  SQL SECURITY INVOKER
BEGIN
  DECLARE parity, r, pair_sum_delta, len INT;
  DECLARE digit_pair, str_len VARCHAR(2);

  IF (p_number = t_number) THEN
      RETURN FALSE;
  END IF;

  SET r = luhn_sum(p_number) % 10;

  IF (r = 0) THEN
      RETURN FALSE;
  END IF;

  SET str_len = LENGTH(p_number);
  SET len = str_len;
  SET parity = 1; -- odd

  WHILE len > 1 DO
      SET digit_pair = SUBSTRING(p_number, len - 1, 2);
      SET pair_sum_delta = (-luhn_sum(digit_pair) + luhn_sum(REVERSE(digit_pair))) * parity;

      IF ((r + pair_sum_delta) % 10 = 0) AND (t_number = CONCAT(SUBSTRING(p_number, 1, len -2), REVERSE(digit_pair), SUBSTRING(p_number, len + 1, str_len - len))) THEN
            RETURN TRUE;
      END IF;

      SET parity = -parity;
      SET len = len -1;    
  END WHILE;

  RETURN FALSE;
END;
cacaodev
  • 1
  • 1
  • 1
  • You seem to be asking someone to write an entire MySQL stored procedure for you. Presumably you can actually do a lot of that work yourself; can you narrow your question down to the specific part that you need help with? – ruakh Mar 15 '14 at 18:12
  • After a few hours struggling with a language that ignores the concept of array and where indexes are 1 based, i came up with a solution. – cacaodev Mar 18 '14 at 19:13
  • Rather than editing your question to add the answer, you can post the answer *as* an answer, and maybe get some upvotes. :-) – ruakh Mar 18 '14 at 20:48
  • The speed can be improved. First by marking the procedure as DETERMINISTIC and also by returning a set of candidates numbers and see if the field value is among them. Something like `WHERE FIELD IN luhn_from_typo(n)` ... but it does not seem possible to return from a function such set suitable for an IN clause. – cacaodev Mar 19 '14 at 20:33

0 Answers0