SQL: Is there a possibility to convert numbers (1,2,3,4…) to letters (A,B,C,D…)

前端 未结 5 1646
伪装坚强ぢ
伪装坚强ぢ 2020-12-14 09:26

Is there a possibility to obtain letters (like A,B) instead of numbers (1,2) e.g. as a result of Dense_Rank function call(in MS Sql) ?

相关标签:
5条回答
  • 2020-12-14 09:35

    hint: try this in your SQL Enterprise manager

      select char(65), char(66), char(67)
    

    a full solution, for ranks up to 17,500 (or three letters, up to ZZZ) is:

    select 
        case When rnk < 703 Then ''
     else Char(64 + ((rnk-26) / 26 / 26)) End +
        case When rnk < 27 Then '' 
       When rnk < 703 Then Char(64 + ((rnk-1)/ 26))
     else Char(65 + ((rnk-1)% 702 / 26)) End +
        Char(65 + ((rnk - 1) % 26))  
    from (select Dense_Rank() 
         OVER (ORDER BY T.Sequence) rnk
          From YourTable t) z
    
    0 讨论(0)
  • 2020-12-14 09:43

    I used this as a basis for my function to convert integers to base26 character strings

    DECLARE @Input integer  = 3000
    
    DECLARE @Value     integer  
    DECLARE @Quotient  integer      = 0
    DECLARE @Remainder integer      = 0
    DECLARE @Output    varchar(max) = ''
    
    DECLARE @BASE char(26) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    
    SET @Value = @Input
    
    WHILE @Value > 0 BEGIN
    
      SET @Quotient  = @Value / 26
      SET @Remainder = @Value % 26
    
      SET @Output = substring(@BASE,@Remainder,1) + @Output
    
      SELECT @Value = @Quotient
    
    END
    
    SELECT @Output --- DKJ
    
    0 讨论(0)
  • 2020-12-14 09:44

    Not a direct answer - but if somebody has a alphabet conversion requirement with 3 characters, following is what I am doing.

    /*                    
    Function Desc: Convert integer value to 3 character alpha-numeric
    --Note: 1. This will return unique values from 0 to 17575, after that it startes again from AAA.
            2. Returns NULL If less than 0.
    
    --Test Values
        select dbo.udfGetBase26CharacterValue(0) --AAA
        select dbo.udfGetBase26CharacterValue(17575) --ZZZ
        select dbo.udfGetBase26CharacterValue(17576) --AAA
        select dbo.udfGetBase26CharacterValue(NULL) --NULL
        select dbo.udfGetBase26CharacterValue(-1) --NULL
    */
    
    
    CREATE FUNCTION [dbo].udfGetBase26CharacterValue
    (    
        @id INT
    )    
    RETURNS CHAR(3)   
    AS    
    BEGIN    
    
    IF ((@id < 0) OR (@id IS NULL))
    BEGIN
        Return NULL
    END
    
    --Convert to base 26
    Return  char(@id / power(26,2) % 26 + 65) +
                char(@id / 26 % 26 + 65) + 
                char(@id % 26 + 65)    
    
    END
    

    Another approach - get next character code (it is alpha-numeric result). If you pass '00A' it will return '00B'

    CREATE FUNCTION dbo.fnGetNextCharacterCode (@InputCode char(3))
    RETURNS char(3)
    AS
    BEGIN
    
    
    IF LEN(LTRIM(RTRIM(@InputCode))) = 2
    BEGIN
        SET @InputCode = '0'+LTRIM(RTRIM(@InputCode))
    END
    ELSE IF LEN(LTRIM(RTRIM(@InputCode))) = 1
    BEGIN
        SET @InputCode = '00'+LTRIM(RTRIM(@InputCode))
    END
    
    
    DECLARE @NewCode char(3)
    
    SELECT @NewCode =
                        CASE WHEN RIGHT(@InputCode,2) != 'ZZ' THEN LEFT(@InputCode,1)
                           ELSE CHAR(
                                        CASE LEFT(@InputCode,1) WHEN '9' THEN 64 
                                                           WHEN 'Z' THEN 47 
                                                           ELSE ASCII(LEFT(@InputCode,1)
                                                ) 
                                      END + 1
                                    )
                     END ---First Char
                     + 
                     CASE WHEN RIGHT(@InputCode,1) != 'Z' THEN SUBSTRING(@InputCode,2,1)
                           ELSE CHAR(
                                        CASE SUBSTRING(@InputCode,2,1) WHEN '9' THEN 64 
                                                           WHEN 'Z' THEN 47 
                                                           ELSE ASCII(SUBSTRING(@InputCode,2,1)) 
                                        END + 1
                                    )
                       END ---Second Char
                    + 
                    CHAR(CASE RIGHT(@InputCode,1) WHEN '9' THEN 64 
                                             WHEN 'Z' THEN 47 
                                             ELSE ASCII(RIGHT(@InputCode,1)) 
                            END + 1) ---Third Char
    
    RETURN @NewCode
    END
    GO
    
    0 讨论(0)
  • 2020-12-14 09:47

    Try this:

    SELECT
       Letters = Char(64 + T.Num),
       T.Col1,
       T.Col2
    FROM
       dbo.YourTable T
    ;
    

    Just be aware that when you get to 27 (past Z), things are going to get interesting, and not useful.

    If you wanted to start doubling up letters, as in ... X, Y, Z, AA, AB, AC, AD ... then it's going to get a bit trickier. This works in all versions of SQL Server. The SELECT clauses are just an alternate to a CASE statement (and 2 characters shorter, each).

    SELECT
       *,
       LetterCode =
          Coalesce((SELECT Char(65 + (N.Num - 475255) / 456976 % 26) WHERE N.Num >= 475255), '')
          + Coalesce((SELECT Char(65 + (N.Num - 18279) / 17576 % 26) WHERE N.Num >= 18279), '')
          + Coalesce((SELECT Char(65 + (N.Num - 703) / 676 % 26) WHERE N.Num >= 703), '')
          + Coalesce((SELECT Char(65 + (N.Num - 27) / 26 % 26) WHERE N.Num >= 27), '')
          + (SELECT Char(65 + (N.Num - 1) % 26))
    FROM dbo.YourTable N
    ORDER BY N.Num
    ;
    

    See a Live Demo at SQL Fiddle

    (Demo for SQL 2008 and up, note that I use Dense_Rank() to simulate a series of numbers)

    This will work from A to ZZZZZ, representing the values 1 to 12356630. The reason for all the craziness above instead of a more simple expression is because A doesn't simply represent 0, here. Before each threshold when the sequence kicks over to the next letter A added to the front, there is in effect a hidden, blank, digit--but it's not used again. So 5 letters long is not 26^5 combinations, it's 26 + 26^2 + 26^3 + 26^4 + 26^5!

    It took some REAL tinkering to get this code working right... I hope you or someone appreciates it! This can easily be extended to more letters just by adding another letter-generating expression with the right values.

    Since it appears I'm now square in the middle of a proof-of-manliness match, I did some performance testing. A WHILE loop is to me not a great way to compare performance because my query is designed to run against an entire set of rows at once. It doesn't make sense to me to run it a million times against one row (basically forcing it into virtual-UDF land) when it can be run once against a million rows, which is the use case scenario given by the OP for performing this against a large rowset. So here's the script to test against 1,000,000 rows (test script requires SQL Server 2005 and up).

    DECLARE
       @Buffer varchar(16),
       @Start datetime;
    
    SET @Start = GetDate();
    WITH A (N) AS (SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) A (N)),
    B (N) AS (SELECT 1 FROM A, A X),
    C (N) AS (SELECT 1 FROM B, B X),
    D (N) AS (SELECT 1 FROM C, B X),
    N (Num) AS (SELECT Row_Number() OVER (ORDER BY (SELECT 1)) FROM D)
    SELECT @Buffer = dbo.HinkyBase26(N.Num)
    FROM N
    ;
    SELECT [HABO Elapsed Milliseconds] = DateDiff( ms, @Start, GetDate());
    
    SET @Start = GetDate();
    WITH A (N) AS (SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) A (N)),
    B (N) AS (SELECT 1 FROM A, A X),
    C (N) AS (SELECT 1 FROM B, B X),
    D (N) AS (SELECT 1 FROM C, B X),
    N (Num) AS (SELECT Row_Number() OVER (ORDER BY (SELECT 1)) FROM D)
    SELECT
       @Buffer =
          Coalesce((SELECT Char(65 + (N.Num - 475255) / 456976 % 26) WHERE N.Num >= 475255), '')
          + Coalesce((SELECT Char(65 + (N.Num - 18279) / 17576 % 26) WHERE N.Num >= 18279), '')
          + Coalesce((SELECT Char(65 + (N.Num - 703) / 676 % 26) WHERE N.Num >= 703), '')
          + Coalesce((SELECT Char(65 + (N.Num - 27) / 26 % 26) WHERE N.Num >= 27), '')
          + (SELECT Char(65 + (N.Num - 1) % 26))   
    FROM N
    ;
    SELECT [ErikE Elapsed Milliseconds] = DateDiff( ms, @Start, GetDate());
    

    And the results:

    UDF: 17093 ms
    ErikE: 12056 ms
    

    Original Query

    I initially did this a "fun" way by generating 1 row per letter and pivot-concatenating using XML, but while it was indeed fun, it proved to be slow. Here is that version for posterity (SQL 2005 and up required for the Dense_Rank, but will work in SQL 2000 for just converting numbers to letters):

    WITH Ranks AS (
       SELECT
          Num = Dense_Rank() OVER (ORDER BY T.Sequence),
          T.Col1,
          T.Col2
       FROM
          dbo.YourTable T
    )
    SELECT
       *,
       LetterCode =
          (
             SELECT Char(65 + (R.Num - X.Low) / X.Div % 26)
             FROM
                (
                   SELECT 18279, 475254, 17576
                   UNION ALL SELECT 703, 18278, 676
                   UNION ALL SELECT 27, 702, 26
                   UNION ALL SELECT 1, 26, 1
                ) X (Low, High, Div)      
             WHERE R.Num >= X.Low
             FOR XML PATH(''), TYPE
          ).value('.[1]', 'varchar(4)')
    FROM Ranks R
    ORDER BY R.Num
    ;
    

    See a Live Demo at SQL Fiddle

    0 讨论(0)
  • 2020-12-14 09:54

    You can convert the values to an offset base-26 with a UDF:

    EDIT: Corrected function.

    create function dbo.HinkyBase26( @Value as BigInt ) returns VarChar(15) as
      begin
      -- Notes: 'A' = 0.  Negative numbers are not handled.
      declare @Result as VarChar(15) = '';
    
      if @Value = 0
        select @Result = 'A';
      else
        set @Value += 1;
      while @Value > 0
        select @Value -= 1, @Result = Char( ASCII( 'A' ) + @Value % 26 ) + @Result, @Value /= 26;
      return @Result;
      end;
    

    Sample values:

    select Arabic, dbo.HinkyBase26( Arabic ) as Alpha
      from ( values ( 0 ), ( 1 ), ( 25 ), ( 26 ), ( 51 ), ( 52 ),
        ( 27 * 26 - 1 ), ( 27 * 26 ),
        ( 33685567531 ) ) as Foo( Arabic );
    

    At ErikE's suggestion I ran a quick performance test on my notebook. 1,000,000 iterations of the UDF vs. the XML solution:

    declare @Count as Int;
    declare @Buffer as VarChar(16);
    declare @Start as DateTime;
    
    select @Count = 1000000, @Start = GetDate();
    while @Count > 0
      select @Buffer = dbo.HinkyBase26( @Count ), @Count -= 1;
    select DateDiff( ms, @Start, GetDate() ) as 'Elapsed Milliseconds'; -- 14,583    
    select @Count = 1000000, @Start = GetDate();
    while @Count > 0
      select @Buffer =
          (
             SELECT Char( ASCII( 'A' ) + (@Count - X.Low) / X.Div % 26)
             FROM
                (
                   SELECT 18279, 475254, 17576
                   UNION ALL SELECT 703, 18278, 676
                   UNION ALL SELECT 27, 702, 26
                   UNION ALL SELECT 1, 26, 1
                ) X (Low, High, Div)      
             WHERE @Count >= X.Low
             FOR XML PATH(''), TYPE
          ).value('.[1]', 'varchar(4)'), @Count -= 1;
    select DateDiff( ms, @Start, GetDate() ) as 'Elapsed Milliseconds'; -- 47,256
    

    The UDF was a little more than 3 times faster.

    0 讨论(0)
提交回复
热议问题