SQL: есть ли возможность конвертировать числа (1,2,3,4...) в письма (A, B, C, D...)

Есть ли возможность получать буквы (например, A, B) вместо чисел (1,2), например. в результате вызова функции Dense_Rank (в MS Sql)?

Ответ 1

Попробуйте следующее:

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

Просто помните, что когда вы доберетесь до 27 (прошлое Z), все станет интересным и не полезно.

Если вы хотите начать удвоение букв, как в ... X, Y, Z, AA, AB, AC, AD ..., тогда это будет немного сложнее. Это работает во всех версиях SQL Server. Предложения SELECT являются просто альтернативой оператору CASE (и на 2 символа короче, каждый).

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
;

Посмотрите демо-версию в SQL Fiddle

(Демо для SQL 2008 и выше, обратите внимание, что я использую Dense_Rank() для имитации серии чисел)

Это будет работать от A до ZZZZZ, представляя значения 1 до 12356630. Причина всего сумасшествия выше, чем простое выражение, потому что A здесь не просто представляет 0. Перед каждым порогом, когда последовательность переместится на следующую букву A, добавленную к фронту, на самом деле есть скрытая, пустая цифра, но она не используется снова. Таким образом, длина 5 букв не составляет 26 ^ 5 комбинаций, это 26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5!

Потребовалось некоторое РЕАЛЬНОЕ мастеринг, чтобы заставить этот код работать правильно... Надеюсь, вы или кто-то это оценит! Это можно легко расширить до большего количества букв, просто добавив другое выражающее букву выражение с правильными значениями.

Так как кажется, что я сейчас квадрат посреди матча с доказательством мужественности, я провел некоторое тестирование производительности. Цикл WHILE - это отличный способ сравнить производительность, потому что мой запрос предназначен для одновременного запуска всего набора строк. Для меня не имеет смысла запускать его миллион раз против одной строки (в основном, заставляя ее в виртуальную землю UDF), когда ее можно запустить один раз против миллиона строк, что является сценарием с использованием сценария, заданного OP для выполнения это против большого набора строк. Итак, здесь script для тестирования против 1 000 000 строк (для теста script требуется SQL Server 2005 и выше).

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());

И результаты:

UDF: 17093 ms
ErikE: 12056 ms

Исходный запрос

Я изначально сделал это "забавным" способом, создав 1 строку на каждую букву и сводную конкатенацию с использованием XML, но, хотя это было действительно весело, это оказалось медленным. Вот эта версия для потомков (SQL 2005 и выше требуется для Dense_Rank, но будет работать в SQL 2000 для просто преобразования чисел в буквы):

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
;

Посмотрите демо-версию в SQL Fiddle

Ответ 2

Подсказка

: попробуйте это в своем менеджере SQL Enterprise

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

полное решение, для рангов до 17 500 (или трех букв, до ZZZ):

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

Ответ 3

Вы можете преобразовать значения в базу смещения-26 с помощью UDF:

EDIT: исправлена ​​функция.

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;

Примеры значений:

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

В предложении ErikE я провел быстрый тест производительности на своем ноутбуке. 1,000,000 итераций UDF по сравнению с XML-решением:

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

UDF был чуть более чем в 3 раза быстрее.

Ответ 4

Не прямой ответ - но если у кого-то есть требование преобразования алфавита с 3 символами, следующее - это то, что я делаю.

/*                    
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

Другой подход - получить следующий код символа (это альфа-числовой результат). Если вы пройдете '00A', он вернет '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