Converting Numbers to Letters

  • Hi Everyone,

    I am looking to convert numbers in a delimited string to corresponding letter.
    For example, if the string is '2,3,4' I want to convert it to 'B,C,D'
    Important to note that their can be both semi-colon and comma delimiters, and I do want to preserve the delimiters.
    Any help would be much appreciated!


    -- sample data

    DECLARE @Keys TABLE
    (
        ID INT IDENTITY(1,1)
        ,[Keys] VARCHAR(50)
    )

    INSERT INTO @Keys([Keys])
    SELECT '5;7,3'
    UNION ALL SELECT '15'
    UNION ALL SELECT '7;8'
    UNION ALL SELECT '4;6'
    UNION ALL SELECT '4;7'
    UNION ALL SELECT '1;2;3'
    UNION ALL SELECT '2,3,4'
    UNION ALL SELECT '2;4'
    UNION ALL SELECT '2;3'
    UNION ALL SELECT '3;2'
    UNION ALL SELECT '26'
    UNION ALL SELECT '2'
    UNION ALL SELECT '6'

  • Here's an option on how to do it.
    You might want to check the following articles to understand what's happening in the code and to get the function used.
    http://www.sqlservercentral.com/articles/String+Manipulation/94365/
    http://www.sqlservercentral.com/articles/comma+separated+list/71700/

    SELECT k.ID,
      k.Keys,
      ( SELECT CASE WHEN s.Matched = 1 THEN CHAR( s.Item + 64) ELSE s.Item END
        FROM @Keys i
        CROSS APPLY dbo.PatternSplitCM(i.Keys, '%[0-9]%') s
        WHERE k.ID = i.ID
        ORDER BY s.ItemNumber
        FOR XML PATH(''), TYPE).value('./text()[1]', 'varchar(max)') AS NewKeys
    FROM @Keys k
    GROUP BY k.ID,
      k.Keys;

    Luis C.
    General Disclaimer:
    Are you seriously taking the advice and code from someone from the internet without testing it? Do you at least understand it? Or can it easily kill your server?

    How to post data/code on a forum to get the best help: Option 1 / Option 2
  • You might be able to do something simpler, depending on your specific data and requirements:


    SELECT
      [Keys],
      REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
      REPLACE([Keys],
      '26', 'Z'), '25', 'Y'), '24', 'X'), '23', 'W'), '22', 'V'),
      '21', 'U'), '20', 'T'), '19', 'S'), '18', 'R'), '17', 'Q'),
      '16', 'P'), '15', 'O'), '14', 'N'), '13', 'M'), '12', 'L'),
      '11', 'K'), '10', 'J'), '9', 'I'), '8', 'H'), '7', 'G'),
      '6', 'F'), '5', 'E'), '4', 'D'), '3', 'C'), '2', 'B'),
      '1', 'A')
    FROM @Keys

    SQL DBA,SQL Server MVP(07, 08, 09) "It's a dog-eat-dog world, and I'm wearing Milk-Bone underwear." "Norm", on "Cheers". Also from "Cheers", from "Carla": "You need to know 3 things about Tortelli men: Tortelli men draw women like flies; Tortelli men treat women like flies; Tortelli men's brains are in their flies".

  • Base 10 consists of numbers 0 through 9.  How do you want to handle tens, hundreds, etc.  What are you trying to accomplish?

  • @ScottPletcher I am trying to avoid 26 REPLACE. That is what I have currently.
    @Bill Talada I don't think there would ever be a number greater than 26. If there was, it would probably be "AA"

  • Why avoid 26 REPLACEs?  They perform pretty well; I guess unless maybe you have a huge number of rows?!

    SQL DBA,SQL Server MVP(07, 08, 09) "It's a dog-eat-dog world, and I'm wearing Milk-Bone underwear." "Norm", on "Cheers". Also from "Cheers", from "Carla": "You need to know 3 things about Tortelli men: Tortelli men draw women like flies; Tortelli men treat women like flies; Tortelli men's brains are in their flies".

  • You still need to represent zero.  Using A-J as 0-9, you could represent 10 as BA and 11 as BB.  That would drastically shorten your REPLACE list and thus your CPU time.

  • @ScottPletcher Yes, lots of data rows. REPLACE performs OK, but I thought I could find a better way.
    @Bill Talada There will never be zero. Numbers start at 1 and go through 26. I can't change the data I am being given.

    Update: We can use CHAR(64 + Number) to get the number value. So value 27 would be [

  • Another option for you:

    Create a custom function to do both the split and the translation at the same time and add a quick query around it.   Here's the custom function, based on the LEAD version of Jeff Moden's DelimitedSplit8K string splitter:

    CREATE FUNCTION dbo.DelimitedSplit8K_LEAD_CS_TRAN (
    --===== Define I/O parameters
        @pString VARCHAR(8000),
        @pDelimiter1 CHAR(1) = ',',
        @pDelimiter2 CHAR(1) = ';'
    )
    RETURNS TABLE WITH SCHEMABINDING AS
    RETURN
    --===== "Inline" CTE Driven "Tally Table” produces values from 0 up to 10,000...
      -- enough to cover VARCHAR(8000)
    WITH ALPHA AS (
        -- Create the value list in table form to translate directly in the function
        SELECT KEY_VALUE, THE_CHAR
        FROM (
            VALUES    ('1', 'A'),
                    ('2', 'B'),
                    ('3', 'C'),
                    ('4', 'D'),
                    ('5', 'E'),
                    ('6', 'F'),
                    ('7', 'G'),
                    ('8', 'H'),
                    ('9', 'I'),
                    ('10', 'J'),
                    ('11', 'K'),
                    ('12', 'L'),
                    ('13', 'M'),
                    ('14', 'N'),
                    ('15', 'O'),
                    ('16', 'P'),
                    ('17', 'Q'),
                    ('18', 'R'),
                    ('19', 'S'),
                    ('20', 'T'),
                    ('21', 'U'),
                    ('22', 'V'),
                    ('23', 'W'),
                    ('24', 'X'),
                    ('25', 'Y'),
                    ('26', 'Z')
            ) AS X (KEY_VALUE, THE_CHAR)
    ),
        E1(N) AS (

            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
            SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    ),                        --10E+1 or 10 rows
        E2(N) AS (

            SELECT 1
            FROM E1 a, E1 b
    ),                        --10E+2 or 100 rows
        E4(N) AS (

            SELECT 1
            FROM E2 a, E2 b
    ),                        --10E+4 or 10,000 rows max
        cteTally AS (
            --==== This provides the "zero base" and limits the number of rows right up front
            -- for both a performance gain and prevention of accidental "overruns"
            SELECT 0 AS N
            UNION ALL
            SELECT TOP (DATALENGTH(ISNULL(@pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
            FROM E4
    ),
        cteStart AS (
            --==== This returns N+1 (starting position of each "element" just once for each delimiter)
            SELECT t.N+1 AS N1, D.Delimiter
            FROM cteTally t
                LEFT OUTER JOIN (SELECT @pDelimiter1 AS Delimiter UNION ALL SELECT @pDelimiter2) AS D
                    ON SUBSTRING(@pString,t.N,1) = D.Delimiter
            WHERE D.Delimiter IS NOT NULL
                OR t.N = 0
    ),
        THE_SPLIT AS (
            --===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
            SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1),
                Item = SUBSTRING(@pString,s.N1,ISNULL(NULLIF((LEAD(s.N1,1,1) OVER (ORDER BY s.N1) - 1),0)-s.N1,8000)),
                s.Delimiter
            FROM cteStart s
    )
    SELECT S.ItemNumber, A.THE_CHAR, S.Delimiter, S.Item
    FROM THE_SPLIT AS S
        LEFT OUTER JOIN ALPHA AS A
            ON S.Item = A.KEY_VALUE;

    GO

    And here's the query to get the results, based on the function:

    DECLARE @Keys TABLE
    (
      ID INT IDENTITY(1,1)
      ,[Keys] VARCHAR(50)
    )

    INSERT INTO @Keys([Keys])
    SELECT '5;7,3'
    UNION ALL SELECT '15'
    UNION ALL SELECT '7;8'
    UNION ALL SELECT '4;6'
    UNION ALL SELECT '4;7'
    UNION ALL SELECT '1;2;3'
    UNION ALL SELECT '2,3,4'
    UNION ALL SELECT '2;4'
    UNION ALL SELECT '2;3'
    UNION ALL SELECT '3;2'
    UNION ALL SELECT '26'
    UNION ALL SELECT '2'
    UNION ALL SELECT '6';

    WITH ALL_DATA AS (

        SELECT K.ID, K.Keys, S.Item, S.ItemNumber, S.Delimiter, S.THE_CHAR
        FROM @Keys AS K
            CROSS APPLY dbo.DelimitedSplit8K_LEAD_CS_TRAN (K.Keys, ',', ';') AS S
    )
    SELECT D.ID, D.Keys,
        (
        SELECT AD.THE_CHAR + LEAD(Delimiter, 1, '') OVER(PARTITION BY AD.ID ORDER BY AD.ItemNumber)
        FROM ALL_DATA AS AD
        WHERE AD.ID = D.ID
        FOR XML PATH('')
        ) AS KeyString
    FROM ALL_DATA AS D
    GROUP BY D.ID, D.Keys
    ORDER BY D.ID;

    You may be able to incorporate the outer query into the function yet, so have fun with it...

    Steve (aka sgmunson) πŸ™‚ πŸ™‚ πŸ™‚
    Rent Servers for Income (picks and shovels strategy)

Viewing 9 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic. Login to reply