是否有可能获得字母(如 A,B)而不是数字(1,2),例如由于 Dense_Rank 函数调用(在 MS Sql 中)?
5 回答
尝试这个:
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及以上的Demo,注意我Dense_Rank()
是用来模拟一系列数字的)
这将从A
to 开始ZZZZZ
,表示1
to的值12356630
。上面所有的疯狂而不是更简单的表达的原因是因为这里A
不简单地表示0
, 。在序列跳转到A
添加到前面的下一个字母的每个阈值之前,实际上有一个隐藏的空白数字 - 但它不会再次使用。所以 5 个字母长不是 26^5 组合,而是 26 + 26^2 + 26^3 + 26^4 + 26^5!
让这段代码正常工作需要一些真正的修补......我希望你或有人欣赏它!只需添加另一个具有正确值的字母生成表达式,就可以轻松地将其扩展到更多字母。
因为看起来我现在正处于男子气概匹配的中间,所以我做了一些性能测试。循环对WHILE
我来说不是比较性能的好方法,因为我的查询旨在一次针对整组行运行。当它可以针对一百万行运行一次时,对我来说对一行运行一百万次(基本上强制它进入虚拟 UDF 土地)是没有意义的,这是 OP 给出的用于执行的用例场景这针对大型行集。所以这里是针对 1,000,000 行进行测试的脚本(测试脚本需要 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 上查看现场演示
提示:在您的 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
您可以使用 UDF 将值转换为偏移量 base-26:
编辑:更正的功能。
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 的建议下,我在笔记本上进行了快速性能测试。UDF 与 XML 解决方案的 1,000,000 次迭代:
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 倍多一点。
不是直接的答案 - 但如果有人有 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
我用它作为我的函数的基础,将整数转换为 base26 字符串
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