MS SQL - UPS Tracking Number Validation Function (1 Viewer)

Local time
Yesterday, 22:22
Joined
Feb 25, 2008
Messages
410
I found this function online at gist.github.com/1371323
It was written for PL/SQL. I'm wondering if it can be adapted to work in MS SQL. I've tried a bunch of stuff, but I just can't get the validator to 'like' it.

Any ideas?

Code:
function ups1z(p_tracking_number varchar2) return boolean is
    trk varchar2(18);
    tot number := 0;
    even number := 0;
    odd number := 0;
    num number := 0;
    chk number := 0;
    chr varchar2(1);
    val number := 0;
begin
	--if the data passed is 18 digits then strip everything but valid UPS tracking number characters.
	if length(p_tracking_number) = 18 then
        for x in 1..length(p_tracking_number) loop
            chr := upper(substr(p_tracking_number,x,1));
            if chr in('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') then
	            trk := trk || chr;
            end if;
        end loop;
	end if;
	--if the stripped data is still 18 digits then check it against the UPS check digit formula
	if length(trk) = 18 then
	    if substr(trk,1,2) = '1Z' then
	        for y in 3..17 loop
			    chr := substr(trk,y,1);
			    case
					when chr = '0' then val := '0';
					when chr = '1' then val := '1';
					when chr = '2' then val := '2';
					when chr = '3' then val := '3';
					when chr = '4' then val := '4';
					when chr = '5' then val := '5';
					when chr = '6' then val := '6';
					when chr = '7' then val := '7';
					when chr = '8' then val := '8';
					when chr = '9' then val := '9';
				    when chr = 'A' then val := '2';
					when chr = 'B' then val := '3';
					when chr = 'C' then val := '4';
					when chr = 'D' then val := '5';
					when chr = 'E' then val := '6';
					when chr = 'F' then val := '7';
					when chr = 'G' then val := '8';
					when chr = 'H' then val := '9';
					when chr = 'I' then val := '0';
					when chr = 'J' then val := '1';
					when chr = 'K' then val := '2';
					when chr = 'L' then val := '3';
					when chr = 'M' then val := '4';
					when chr = 'N' then val := '5';
					when chr = 'O' then val := '6';
					when chr = 'P' then val := '7';
					when chr = 'Q' then val := '8';
					when chr = 'R' then val := '9';
					when chr = 'S' then val := '0';
					when chr = 'T' then val := '1';
					when chr = 'U' then val := '2';
					when chr = 'V' then val := '3';
					when chr = 'W' then val := '4';
					when chr = 'X' then val := '5';
					when chr = 'Y' then val := '6';
					when chr = 'Z' then val := '7';
					else return false;
				end case;
			    if mod(y,2) = 0 then
			        even:=even+val;
			    else
			        odd:=odd+val;
			    end if;
		    end loop;
		    tot:=(even*2)+odd; num:=tot;
		    while mod(num,10) != 0 loop
		        num:=num+1;
		    end loop;
			chk := num - tot; if chk = 10 then chk := 0; end if;
		    if substr(trk,18,1)=(chk) then
		       return true;
		    end if;
		end if;
	end if;
	return false;
exception
   when others then
       return false;
end;
 

Isskint

Slowly Developing
Local time
Today, 06:22
Joined
Apr 25, 2012
Messages
1,302
Without some way to test it i may be totally wrong but this is how i interpret that code

Code:
Public Function ups1z(p_tracking_number As String) As Boolean
    Dim trk, chr As String
    Dim tot, even, odd, num, chk, val As Integer
 
    trk = ""
    chr = ""
    ups1z = False
 
    'if the data passed is 18 digits then strip everything but valid UPS tracking number characters.
    If Len(p_tracking_number) = 18 Then
        For x = 1 To Len(p_tracking_number)
            chr = UCase(Mid(p_tracking_number, x, 1))
            If InStr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", chr) > 0 Then
                trk = trk & chr 'trk := trk || chr;
            End If
        Next x
    End If
    'if the stripped data is still 18 digits then check it against the UPS check digit formula
    If Len(trk) = 18 Then
        If Left(trk, 2) = "1Z" Then
            For y = 3 To 17
                chr = Mid(trk, y, 1)
                Select Case chr
                    Case "0"
                        val = 0
                    Case "1"
                        val = 1
                    Case "2"
                        val = 2
                    Case "3"
                        val = 3
                    Case "4"
                        val = 4
                    Case "5"
                        val = 5
                    Case "6"
                        val = 6
                    Case "7"
                        val = 7
                    Case "8"
                        val = 8
                    Case "9"
                        val = 9
                    Case "A"
                        val = 2
                    Case "B"
                        val = 3
                    Case "C"
                        val = 4
                    Case "D"
                        val = 5
                    Case "E"
                        val = 6
                    Case "F"
                        val = 7
                    Case "G"
                        val = 8
                    Case "H"
                        val = 9
                    Case "I"
                        val = 0
                    Case "J"
                        val = 1
                    Case "K"
                        val = 2
                    Case "L"
                        val = 3
                    Case "M"
                        val = 4
                    Case "N"
                        val = 5
                    Case "O"
                        val = 6
                    Case "P"
                        val = 7
                    Case "Q"
                        val = 8
                    Case "R"
                        val = 9
                    Case "S"
                        val = 0
                    Case "T"
                        val = 1
                    Case "U"
                        val = 2
                    Case "V"
                        val = 3
                    Case "W"
                        val = 4
                    Case "X"
                        val = 5
                    Case "Y"
                        val = 6
                    Case "Z"
                        val = 7
                End Select
 
                If y Mod 2 = 0 Then
                    even = even + val
                Else
                    odd = odd + val
                End If
            Next y
 
            tot = (even * 2) + odd
            num = tot
            While num Mod 10 = 0
                num = num + 1
            Wend
 
            chk = num - tot
 
            If chk = 10 Then chk = 0
 
            If Mid(trk, 18, 1) = LTrim(Str(chk)) Then
               ups1z = True
            End If
        End If
    End If
End Function
 
Local time
Yesterday, 22:22
Joined
Feb 25, 2008
Messages
410
Yeah, it's pretty easy to get it working in VB or pretty much any language...
but I'm trying to incorporate this on my SQL Server as a user-defined function and refer to it through a CHECK CONSTRAINT statement on a field that will be accepting these tracking numbers.

I'm mostly just curious to see if it could be done,, I mean it's easy enough to include the function on the front-end or client-side app; which I will probably end up doing anyway.
 

mdlueck

Sr. Application Developer
Local time
Today, 01:22
Joined
Jun 23, 2011
Messages
2,631
I successfully moved some VBA string manipulation / conversion code to SQL Server UDF's. Please see this thread:

Assistance req translating VBA into SQL User Defined Function
http://www.access-programmers.co.uk/forums/showthread.php?t=230186

Your PL/SQL example code seems a bit more advanced than T-SQL. Specifically there is no CASE, only IF/ELSE blocks.
 
Local time
Yesterday, 22:22
Joined
Feb 25, 2008
Messages
410
Thanks mdlueck, I'll have to consider how to convert those IF statements to CASE statements.

Also, I notice that the sample code is using the mod function to test whether a value is odd or even, and again to calculate the final check digit comparitor. Upon checking the list of functions on SQL Server 2008 R2, there is no mod function, so that's another thing I'd have to translate and I don't even know where to start with that one.

Edit: I spoke too soon. In T-SQL, instead of using a mod or modulus function, there is the arithmetic operator '%' (modulo), so that's easy enough I guess.
 
Last edited:
Local time
Yesterday, 22:22
Joined
Feb 25, 2008
Messages
410
So after some fiddling around, I've managed to create something that works within a stored procedure.
I've tested approximately 15 valid tracking numbers with a 100% success rate.
I've also tampered with the numbers before running them through and the stored procedure always returns the proper result.

Enjoy!

Code:
<Invalid code removed>
 
Last edited:
Local time
Yesterday, 22:22
Joined
Feb 25, 2008
Messages
410
Well, I hope nobody used the code that I posted yesterday as it does not give valid results 100% of the time.
My initial sample size was obviously not large enough so I didn't catch it at first.
After more research, I found the following articles which helped me to correct my formulas.

Please see the following sources:
http://www.codeproject.com/Articles/21224/Calculating-the-UPS-Tracking-Number-Check-Digit
http://answers.google.com/answers/main?cmd=threadview&id=207899

I changed @trk and @chars to nvarchar(19), that way if a value longer than 18 digits is passed, the function will return invalid.
I have also converted the stored procedure to a scalar-valued function.

Here is my updated code which has been checked against a sample size of 100 diverse UPS tracking numbers:
Code:
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Author:	<Radford, Ross>
-- Create date: <November 9th, 2012>
-- Description:	<Computes, compares and returns the validity of a given UPS "1Z" type tracking number.>
-- =============================================

CREATE FUNCTION [dbo].[fUps1z18](@trk nvarchar(19))
RETURNS BIT
AS
BEGIN
	DECLARE @position int = 3;
	DECLARE @chars nvarchar(19) = @trk;
	DECLARE @checkdigit int = 0;
	DECLARE @runningtotal int = 0;

		WHILE @position < DATALENGTH(@chars) / 2
			BEGIN
				SET @runningtotal = @runningtotal +
					(
					SELECT CASE WHEN @position % 2 = 0 THEN -- Indicates character in even position
						(
						SELECT CASE WHEN ISNUMERIC(SUBSTRING(@chars, @position, 1)) <> 0 THEN -- Indicates numeric value
							(
							2 * SUBSTRING(@chars, @position, 1)
							)
						ELSE -- Indicates alpha value
							(
							((ASCII(SUBSTRING(@chars, @position, 1)) - 63) % 10) * 2
							)
						END
						)
					ELSE -- Indicates character in odd position
						(
						SELECT CASE WHEN ISNUMERIC(SUBSTRING(@chars, @position, 1)) <> 0 THEN -- Indicates numeric value
							(
							SUBSTRING(@chars, @position, 1)
							)
						ELSE -- Indicates alpha value
							(
							(ASCII(SUBSTRING(@chars, @position, 1)) - 63) % 10
							)
						END
						)
					END
					)
					SET @position = @position + 1
			END

			SELECT @checkdigit =
				CASE @runningtotal % 10
					WHEN 0 THEN
						(
						@runningtotal % 10
						)
					ELSE
						(
						10 -(@runningtotal % 10)
						)
				END

	RETURN
		(
		CASE WHEN @checkdigit = (SUBSTRING(@chars, 18, 1))
			THEN 1
			ELSE 0
		END
		)
END

GO
 
Last edited:

Users who are viewing this thread

Top Bottom