Greg Ostry
Greg Ostry

Reputation: 1231

How to build smiliar VLOOKUP function in SQL

I have a small check digit algorithm in excel and it is basically a VLOOKUP function. Now I would like to make function in sql with the same result.

To check digit i ned a static table for this.

enter image description here I'm beginning with 0 (orange) and than my vlookup function is:

=SVERWEIS(C16;$A$4:$K$13;B17+2;FALSCH)

translated in englisch

=VLOOKUP(C16;$A$4:$K$13;B17+2;FALSE)

enter image description here

how do i check the "check digit" enter image description here

first of all i'm looking in the 0 (C16) column (orange cell) than in in the transfer column 9th row (b17) the combination of this two digits = 5 (my new transfer) next one now im looing in the transfer column 5th row and (B18) =6 -> that is the column the combination is now 5th(transfer) row and 6 column -> my new transfer is 9 next one the combination is now 9th row (transfer) and 9 column -> my new transfer is 3 and so one... the last digit should be 2 (C42) in the first example ... this number calculate minus 10 -> 10-2 = 8
What i have done so far:

i created a new table in sql that contains the CheckDigit Tbl.

CREATE TABLE CheckTbl(
   transfer    INTEGER  NOT NULL PRIMARY KEY 
  ,0           INTEGER  NOT NULL
  ,1           INTEGER  NOT NULL
  ,2           INTEGER  NOT NULL
  ,3           INTEGER  NOT NULL
  ,4           INTEGER  NOT NULL
  ,5           INTEGER  NOT NULL
  ,6           INTEGER  NOT NULL
  ,7           INTEGER  NOT NULL
  ,8           INTEGER  NOT NULL
  ,9           INTEGER  NOT NULL
  ,check_digit INTEGER  NOT NULL
);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (0,0,9,4,6,8,2,7,1,3,5,0);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (1,9,4,6,8,2,7,1,3,5,0,9);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (2,4,6,8,2,7,1,3,5,0,9,8);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (3,6,8,2,7,1,3,5,0,9,4,7);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (4,8,2,7,1,3,5,0,9,4,6,6);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (5,2,7,1,3,5,0,9,4,6,8,5);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (6,7,1,3,5,0,9,4,6,8,2,4);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (7,1,3,5,0,9,4,6,8,2,7,3);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (8,3,5,0,9,4,6,8,2,7,1,2);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (9,5,0,9,4,6,8,2,7,1,3,1);

my sql:

DECLARE @refNr nvarchar(30) = '9699100000030000201830'
DECLARE @str VARCHAR(50), @Inc INT, @len INT, @char VARCHAR(50)


SET @str = @refNr
SET @Inc = 1
SET @len = LEN(@str)

WHILE @Inc<= @len
BEGIN
  SET @char = COALESCE(@char+',' ,'') + SUBSTRING(@str, @Inc, 1)  
  SET @Inc=@Inc+1
END


SELECT [value] FROM string_split(@char, ',') WHERE RTRIM(value) <> ''; 

SELECT [transfer]
      ,[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]
      ,[check_digit]
  FROM [CCHelper].[dbo].[CheckTbl]


  SELECT 
    [value] 
  FROM 
    string_split(@char, ',') as SS
    LEFT JOIN CheckTbl CT on (SS.[value] = CT. .....)

I'm also splitting the long number into rows.

enter image description here

Unfortunately i don't know exactly how do write the on condition to join the tables. I need only one number... the check digit number.

SOLUTION: finally i had some time to rethink the solution:

Create FUNCTION [dbo].[CheckDigit] (
    @long_number VARCHAR(80))
RETURNS INT
AS
BEGIN

DECLARE @check_digit INT;

DECLARE @numbers VARCHAR(50) = '0946827135';
    DECLARE @check_digits TABLE (
        id INT IDENTITY(1,1),
        alg INT
    );
    DECLARE @numbers_len INT;
    SELECT @numbers_len = LEN(@numbers);
    DECLARE @item INT = 1;

   WHILE @item <= @numbers_len
    BEGIN
        INSERT INTO @check_digits (alg) SELECT CONVERT(INT, SUBSTRING(@numbers, @item, 1));
        SELECT @item = @item + 1;
    END;


DECLARE @offset TABLE (
        id INT IDENTITY(1,1),
        offset INT,
        r2 INT
        );
DECLARE @len INT;
SELECT @len = LEN(@long_number);

DECLARE @pos INT = 1;
DECLARE @rpr INT;
DECLARE @r2 INT = 0;

WHILE @pos <= @len
    BEGIN
        INSERT INTO @offset (offset) SELECT CONVERT(INT, SUBSTRING(@long_number, @pos, 1));
        SELECT @rpr = @r2 + (SELECT offset FROM @offset WHERE id = @pos)
        SELECT @r2 = (SELECT alg FROM @check_digits WHERE id= ((@rpr % 10)+1))
        UPDATE @offset SET r2 = @r2 WHERE id = @pos;
        SELECT @pos = @pos + 1;
    END;



SELECT @check_digit = (SELECT (10-r2) AS Result FROM @offset WHERE id = @len)%10
RETURN @check_digit;
END;

Now it's working.

Upvotes: 2

Views: 215

Answers (1)

Richard Hansell
Richard Hansell

Reputation: 5403

I don't think I'm actually 100% understanding how this works, but I worked through the Excel VLOOKUP, and I can get the answer for your first example, but it's not particularly pleasant to look at!

SET NOCOUNT ON;
DECLARE @check_digits TABLE (
    [transfer] INT,
    d0 INT,
    d1 INT,
    d2 INT,
    d3 INT,
    d4 INT,
    d5 INT,
    d6 INT,
    d7 INT,
    d8 INT,
    d9 INT,
    check_digit INT);
INSERT INTO @check_digits SELECT 0,0,9,4,6,8,2,7,1,3,5,0;
INSERT INTO @check_digits SELECT 1,9,4,6,8,2,7,1,3,5,0,9;
INSERT INTO @check_digits SELECT 2,4,6,8,2,7,1,3,5,0,9,8;
INSERT INTO @check_digits SELECT 3,6,8,2,7,1,3,5,0,9,4,7;
INSERT INTO @check_digits SELECT 4,8,2,7,1,3,5,0,9,4,6,6;
INSERT INTO @check_digits SELECT 5,2,7,1,3,5,0,9,4,6,8,5;
INSERT INTO @check_digits SELECT 6,7,1,3,5,0,9,4,6,8,2,4;
INSERT INTO @check_digits SELECT 7,1,3,5,0,9,4,6,8,2,7,3;
INSERT INTO @check_digits SELECT 8,3,5,0,9,4,6,8,2,7,1,2;
INSERT INTO @check_digits SELECT 9,5,0,9,4,6,8,2,7,1,3,1;

DECLARE @offset TABLE (
    id INT,
    offset INT);
INSERT INTO @offset
SELECT 1, 9
UNION ALL
SELECT 2, 6
UNION ALL
SELECT 3, 9
UNION ALL
SELECT 4, 9
UNION ALL
SELECT 5, 1
UNION ALL
SELECT 6, 0
UNION ALL
SELECT 7, 0
UNION ALL
SELECT 8, 0
UNION ALL
SELECT 9, 0
UNION ALL
SELECT 10, 0
UNION ALL
SELECT 11, 0
UNION ALL
SELECT 12, 3
UNION ALL
SELECT 13, 0
UNION ALL
SELECT 14, 0
UNION ALL
SELECT 15, 0
UNION ALL
SELECT 16, 0
UNION ALL
SELECT 17, 2
UNION ALL
SELECT 18, 0
UNION ALL
SELECT 19, 1
UNION ALL
SELECT 20, 8
UNION ALL
SELECT 21, 3
UNION ALL
SELECT 22, 0
UNION ALL
SELECT 23, 0
UNION ALL
SELECT 24, 0
UNION ALL
SELECT 25, 8
UNION ALL
SELECT 26, 7;

DECLARE @transfer INT = 0;
DECLARE @offset_value INT;
DECLARE @iterations INT = 1;
WHILE @iterations <= 26
BEGIN
    SELECT @offset_value = offset + 2 FROM @offset WHERE id = @iterations;
    SELECT @transfer = 
        CASE
            WHEN @offset_value = 2 THEN d0
            WHEN @offset_value = 3 THEN d1
            WHEN @offset_value = 4 THEN d2
            WHEN @offset_value = 5 THEN d3
            WHEN @offset_value = 6 THEN d4
            WHEN @offset_value = 7 THEN d5
            WHEN @offset_value = 8 THEN d6
            WHEN @offset_value = 9 THEN d7
            WHEN @offset_value = 10 THEN d8
            WHEN @offset_value = 11 THEN d9
        END
    FROM 
        @check_digits
    WHERE
        [transfer] = @transfer;
    SELECT @iterations = @iterations + 1;
END;
PRINT 'Check Digit is ' + CONVERT(CHAR(1), 10 - @transfer);
SET NOCOUNT OFF;

This really needs to be set-based, probably using recursion? However, I don't really see the business use for this. I assume that there's a long number 96991000000300002018300087 and you want the checksum for this? I don't really get where your (O) Orange numbers come into play, so I just set a number to zero (as in your example) and sort of ignored this part.

Anyway, if you actually run that (it's non-destructive, as in it doesn't materialise anything) then you get the right answer of 2. Where you would go from here is unclear. Probably a better way to get the long number in? Probably something to add recursion for each digit in your long number, etc.?


I think I probably spent too long on this, but I got it working as a UDF:

CREATE FUNCTION dbo.CheckDigit (
    @long_number VARCHAR(50))
RETURNS INT
AS
BEGIN
    --Hardcoded check digits
    DECLARE @check_digit INT;
    DECLARE @check_digits TABLE (
        [transfer] INT,
        d0 INT,
        d1 INT,
        d2 INT,
        d3 INT,
        d4 INT,
        d5 INT,
        d6 INT,
        d7 INT,
        d8 INT,
        d9 INT,
        check_digit INT);
    INSERT INTO @check_digits SELECT 0,0,9,4,6,8,2,7,1,3,5,0;
    INSERT INTO @check_digits SELECT 1,9,4,6,8,2,7,1,3,5,0,9;
    INSERT INTO @check_digits SELECT 2,4,6,8,2,7,1,3,5,0,9,8;
    INSERT INTO @check_digits SELECT 3,6,8,2,7,1,3,5,0,9,4,7;
    INSERT INTO @check_digits SELECT 4,8,2,7,1,3,5,0,9,4,6,6;
    INSERT INTO @check_digits SELECT 5,2,7,1,3,5,0,9,4,6,8,5;
    INSERT INTO @check_digits SELECT 6,7,1,3,5,0,9,4,6,8,2,4;
    INSERT INTO @check_digits SELECT 7,1,3,5,0,9,4,6,8,2,7,3;
    INSERT INTO @check_digits SELECT 8,3,5,0,9,4,6,8,2,7,1,2;
    INSERT INTO @check_digits SELECT 9,5,0,9,4,6,8,2,7,1,3,1;

    --Make the long number into an indexed list
    DECLARE @offset TABLE (
        id INT IDENTITY(1,1),
        offset INT);
    DECLARE @len INT;
    SELECT @len = LEN(@long_number);
    DECLARE @pos INT = 1;
    WHILE @pos <= @len
    BEGIN
        INSERT INTO @offset (offset) SELECT CONVERT(INT, SUBSTRING(@long_number, @pos, 1));
        SELECT @pos = @pos + 1;
    END;

    --Use recursive CTE
    WITH cte1 AS (
        SELECT
            1 AS iterations,
            offset,
            offset + 2 AS new_offset
        FROM
            @offset
        WHERE
            id = 1
        UNION ALL
        SELECT
            iterations + 1 AS iterations,
            o.offset,
            o.offset + 2 AS new_offset
        FROM
            cte1 c
            INNER JOIN @offset o ON o.id = c.iterations + 1
        WHERE
            c.iterations <= @len),
    cte2 AS (
        SELECT
            1 AS iterations,
            c.new_offset,
            CASE
                WHEN c.new_offset = 2 THEN d0
                WHEN c.new_offset = 3 THEN d1
                WHEN c.new_offset = 4 THEN d2
                WHEN c.new_offset = 5 THEN d3
                WHEN c.new_offset = 6 THEN d4
                WHEN c.new_offset = 7 THEN d5
                WHEN c.new_offset = 8 THEN d6
                WHEN c.new_offset = 9 THEN d7
                WHEN c.new_offset = 10 THEN d8
                WHEN c.new_offset = 11 THEN d9
            END AS [transfer]
        FROM 
            cte1 c
            INNER JOIN @check_digits cd ON cd.[transfer] = 0
        WHERE
            iterations = 1
        UNION ALL
        SELECT
            c.iterations + 1 AS iterations,
            c1.new_offset,
            CASE
                WHEN c1.new_offset = 2 THEN d0
                WHEN c1.new_offset = 3 THEN d1
                WHEN c1.new_offset = 4 THEN d2
                WHEN c1.new_offset = 5 THEN d3
                WHEN c1.new_offset = 6 THEN d4
                WHEN c1.new_offset = 7 THEN d5
                WHEN c1.new_offset = 8 THEN d6
                WHEN c1.new_offset = 9 THEN d7
                WHEN c1.new_offset = 10 THEN d8
                WHEN c1.new_offset = 11 THEN d9
            END AS [transfer]
        FROM 
            cte2 c
            INNER JOIN @check_digits cd ON cd.[transfer] = c.[transfer]
            INNER JOIN cte1 c1 ON c1.iterations = c.iterations + 1
        WHERE
            c1.iterations <= @len)
    SELECT @check_digit = 10 - [transfer] FROM cte2 WHERE iterations = @len;
    RETURN @check_digit;
END;
GO
SELECT dbo.CheckDigit('96991000000300002018300087');
SELECT dbo.CheckDigit('96991000000300002018300086');
SELECT dbo.CheckDigit('96991000000300002018300085');

...and I learned something today; I didn't know you could use recursion twice in the same query :D

If you run that then you should get 8, 2 and 4... which matches your Excel.

Upvotes: 1

Related Questions