在SQL中将字符字符串转换为字母表字符串中的第n个字母

在SQL中将字符字符串转换为字母表字符串中的第n个字母,sql,sql-server,Sql,Sql Server,我必须构建一个进程,它接受一个VARCHAR字符串(例如“AHT559”),并通过将字母字符转换为基于字母表中第n个字母的整数,将其转换为仅限整数的字符串。因此,上述结果为:010820559 我以前在SAS中做过这项工作,但我对SQL相对较新。在SQL中实现这一点的最佳方法是什么 以下是我在SAS所做的工作: DO _i = 1 TO length( account ); IF (rank( char( account, _i ) ) -64) < 0 THEN agr

我必须构建一个进程,它接受一个VARCHAR字符串(例如“AHT559”),并通过将字母字符转换为基于字母表中第n个字母的整数,将其转换为仅限整数的字符串。因此,上述结果为:010820559

我以前在SAS中做过这项工作,但我对SQL相对较新。在SQL中实现这一点的最佳方法是什么

以下是我在SAS所做的工作:

DO _i = 1 TO length( account );
        IF (rank( char( account, _i ) ) -64)  < 0 THEN agreement_hash = CATS( agreement_hash, char( account, _i ) );
        ELSE IF  (rank( char( account, _i ) ) -64) < 10 THEN agreement_hash = CATS( agreement_hash, 0, rank( char( account, _i ) )-64 );
        ELSE agreement_hash = CATS( agreement_hash, rank( char( account, _i ) )-64 );
    END;
DO\u i=1至长度(账户);
如果(秩(char(account,_i))-64)<0,则协议散列=CATS(协议散列,char(account,_i));
否则,如果(秩(char(account,_i))-64)<10,则协议散列=CATS(协议散列,0,秩(char(account,_i))-64;
ELSE-agreement_hash=CATS(agreement_hash,rank(char(account,_i))-64);
终止

这可能最好使用CLR UDF完成,但完整答案对于这种格式来说太长了

基本上,您需要创建一个UDF(用户定义函数),该函数将字符串(nvarchar…)作为输入,并返回一个字符串作为输出。使用C#可以很容易地做到这一点,并且需要用CLR集成需求来包装它

您可以查看相关信息

代码可能类似于:

[Microsoft.SqlServer.Server.SqlFunction(
IsDeterministic=真,
IsPrecise=true,
SystemDataAccess=SystemDataAccessKind.None)]
公共静态SqlString ToNthAlpha(SqlString值)
{
if(value.IsNull)
返回值;
char[]chars=value.value.ToCharArray();
StringBuilder res=新的StringBuilder();
for(int i=0;i如果(chars[i]>='A'&&chars[i],您可以使用如下内容,可能作为标量函数来执行此转换

DECLARE @i INT    
DECLARE @Item NVARCHAR(4000) = 'AHT1234'

DECLARE @ItemTable TABLE 
(
    Item NCHAR(1)
)

SET @i = 1

--Split the input string into separate characters, store in temp table
WHILE (@i <= LEN(@Item))
BEGIN
    INSERT INTO @ItemTable(Item) 
    VALUES(SUBSTRING(@Item, @i, 1))
    SET @i = @i + 1
END 


DECLARE @AlphaTable TABLE (
    Letter NCHAR(1),
    Position NVARCHAR(2)
)

-- Populate this with the whole alphabet obviously.  Could be a permanent rather than temp table.
INSERT INTO @AlphaTable
        ( Letter, Position )
VALUES  ( N'A', '01'),
          (N'H', '08'),
          (N'T', '20')  

DECLARE @Output NVARCHAR(50)

-- Convert the output and concatenate it back to a single output.
SELECT @Output = COALESCE(@output, '') + Converted
FROM (
    SELECT CASE WHEN ISNUMERIC(Item) = 1
        THEN CONVERT(NVARCHAR(1), Item)
        ELSE (SELECT Position FROM @AlphaTable WHERE Letter = CONVERT(NCHAR(1), Item))
        END AS Converted
    FROM @ItemTable
) AS T1

SELECT @Output
DECLARE@i INT
声明@Item NVARCHAR(4000)=“AHT1234”
声明@ItemTable表
(
项目NCHAR(1)
)
设置@i=1
--将输入字符串拆分为单独的字符,存储在临时表中

虽然(@i这里有一个类似的sqlserver脚本,但在此语法中,任何不是大写字母的字符都假定为数字:

DECLARE @x varchar(100) = 'AHT559'
DECLARE @p int = len(@x)

WHILE @p > 0
SELECT @x = 
  CASE WHEN substring(@x, @p, 1) between 'A' and 'Z' 
  THEN stuff(@x, @p, 1, right(ascii(substring(@x, @p, 1)) - 64 + 100, 2))
  ELSE @x END,
  @p -= 1

SELECT @x
结果:

010820559
试试这个

DECLARE @STR    VARCHAR(MAX)= 'AHT559',
        @SP     INT,
        @SP_STR VARCHAR(50),
        @OUTPUT VARCHAR(MAX)=''
DECLARE @TEMP_STR VARCHAR(50)

SET @TEMP_STR = @STR

WHILE Patindex('%[A-Z]%', @TEMP_STR) <> 0
  BEGIN
      SELECT @SP = Patindex('%[A-Z]%', @TEMP_STR)
      SELECT @SP_STR = Upper(LEFT(@TEMP_STR, @SP))
      SELECT @SP_STR = ( Ascii(@SP_STR) - 65 ) + 1
      SELECT @TEMP_STR = Stuff(@TEMP_STR, 1, @SP, '')
      SET @OUTPUT += RIGHT('0' + @SP_STR, 2)
  END

SELECT @OUTPUT + Substring(@STR, Patindex('%[0-9]%', @STR), Len(@STR)) 
DECLARE@STR VARCHAR(MAX)='AHT559',
@SP INT,
@SP_STR VARCHAR(50),
@输出VARCHAR(最大值)=”
声明@TEMP_STR VARCHAR(50)
设置@TEMP_STR=@STR
而Patindex('%[A-Z]',@TEMP_STR)0
开始
选择@SP=Patindex('%[A-Z]',@TEMP_STR)
选择@SP_STR=Upper(左(@TEMP_STR,@SP))
选择@SP_STR=(Ascii(@SP_STR)-65)+1
选择@TEMP_STR=Stuff(@TEMP_STR,1,@SP,,)
设置@OUTPUT+=RIGHT('0'+@SP_STR,2)
终止
选择@OUTPUT+子字符串(@STR,Patindex(“%[0-9]]”,@STR),Len(@STR))

如果值的格式始终与您在注释中所述的格式相同,并且一次只需处理一个值,则您可以执行一些简单的字符串操作,使用其值将字符转换为整数,然后减去64以获得字母字符的编号:

SELECT ASCII('A')      -- produces 65
SELECT ASCII('A') - 64 -- produces 1
这有点冗长,可以用更少的代码行完成,但为了清晰起见,它是分开的

DECLARE @val NVARCHAR(10) = 'AHT559'

-- get first, second and third character numeric values
DECLARE @first INT = ASCII(SUBSTRING(@val, 1, 1)) - 64
DECLARE @second INT = ASCII(SUBSTRING(@val, 2, 1)) - 64
DECLARE @third INT = ASCII(SUBSTRING(@val, 3, 1)) - 64

-- join them together adding a '0' if < 10
SELECT  RIGHT('0' + CAST(@first  AS VARCHAR(2)), 2)
      + RIGHT('0' + CAST(@second AS VARCHAR(2)), 2)
      + RIGHT('0' + CAST(@third  AS VARCHAR(2)), 2)
      + RIGHT(@val, 3)

如何使用CTE创建前3个字母的每个组合,并使用该组合匹配:

MS SQL Server 2008架构设置

CREATE TABLE Accounts
(
    Account VARCHAR(6)
)

INSERT INTO Accounts
VALUES ('AHT559'), ('BXC556'),
       ('CST345')
;WITH AlphaToNum
AS
(
    SELECT *
    FROM (VALUES
        ('A', '01'), ('B', '02'), ('C', '03'), ('D', '04'),
        ('E', '05'), ('F', '06'), ('G', '07'), ('H', '08'),
        ('I', '09'), ('J', '10'), ('K', '11'), ('L', '12'),
        ('M', '13'), ('N', '14'), ('O', '15'), ('P', '16'),
        ('Q', '17'), ('R', '18'), ('S', '19'), ('T', '20'),
        ('U', '21'), ('V', '22'), ('W', '23'), ('X', '24'),
        ('Y', '25'), ('Z', '26')
        ) X(alpha, num)
),
MappingTable
As
(
    SELECT  A1.alpha + A2.alpha + A3.alpha as match, A1.num + A2.num + A3.num as val
    FROM AlphaToNum A1
    CROSS APPLY AlphaToNum A2
    CROSS APPLY AlphaToNum A3
)
SELECT A.Account, M.val + SUBSTRING(A.Account,4, 3) As ConvertedAccount
FROM MappingTable M
INNER JOIN Accounts A
    ON LEFT(A.Account,3) = M.match
| Account | ConvertedAccount |
|---------|------------------|
|  AHT559 |        010820559 |
|  BXC556 |        022403556 |
|  CST345 |        031920345 |
查询1

CREATE TABLE Accounts
(
    Account VARCHAR(6)
)

INSERT INTO Accounts
VALUES ('AHT559'), ('BXC556'),
       ('CST345')
;WITH AlphaToNum
AS
(
    SELECT *
    FROM (VALUES
        ('A', '01'), ('B', '02'), ('C', '03'), ('D', '04'),
        ('E', '05'), ('F', '06'), ('G', '07'), ('H', '08'),
        ('I', '09'), ('J', '10'), ('K', '11'), ('L', '12'),
        ('M', '13'), ('N', '14'), ('O', '15'), ('P', '16'),
        ('Q', '17'), ('R', '18'), ('S', '19'), ('T', '20'),
        ('U', '21'), ('V', '22'), ('W', '23'), ('X', '24'),
        ('Y', '25'), ('Z', '26')
        ) X(alpha, num)
),
MappingTable
As
(
    SELECT  A1.alpha + A2.alpha + A3.alpha as match, A1.num + A2.num + A3.num as val
    FROM AlphaToNum A1
    CROSS APPLY AlphaToNum A2
    CROSS APPLY AlphaToNum A3
)
SELECT A.Account, M.val + SUBSTRING(A.Account,4, 3) As ConvertedAccount
FROM MappingTable M
INNER JOIN Accounts A
    ON LEFT(A.Account,3) = M.match
| Account | ConvertedAccount |
|---------|------------------|
|  AHT559 |        010820559 |
|  BXC556 |        022403556 |
|  CST345 |        031920345 |

CREATE TABLE Accounts
(
    Account VARCHAR(6)
)

INSERT INTO Accounts
VALUES ('AHT559'), ('BXC556'),
       ('CST345')
;WITH AlphaToNum
AS
(
    SELECT *
    FROM (VALUES
        ('A', '01'), ('B', '02'), ('C', '03'), ('D', '04'),
        ('E', '05'), ('F', '06'), ('G', '07'), ('H', '08'),
        ('I', '09'), ('J', '10'), ('K', '11'), ('L', '12'),
        ('M', '13'), ('N', '14'), ('O', '15'), ('P', '16'),
        ('Q', '17'), ('R', '18'), ('S', '19'), ('T', '20'),
        ('U', '21'), ('V', '22'), ('W', '23'), ('X', '24'),
        ('Y', '25'), ('Z', '26')
        ) X(alpha, num)
),
MappingTable
As
(
    SELECT  A1.alpha + A2.alpha + A3.alpha as match, A1.num + A2.num + A3.num as val
    FROM AlphaToNum A1
    CROSS APPLY AlphaToNum A2
    CROSS APPLY AlphaToNum A3
)
SELECT A.Account, M.val + SUBSTRING(A.Account,4, 3) As ConvertedAccount
FROM MappingTable M
INNER JOIN Accounts A
    ON LEFT(A.Account,3) = M.match
| Account | ConvertedAccount |
|---------|------------------|
|  AHT559 |        010820559 |
|  BXC556 |        022403556 |
|  CST345 |        031920345 |

您的值是否有任何标准格式?如3个字符后总是跟3个数字?此外,您一次只处理一个值,还是从表中选择一列值?@Tanner我的值将始终采用该格式。我一次只处理一个值,然后在遍历该字符串后,我将连接所有t他将结果生成一个新字符串。@Herm,如果您希望在该数据量上提高效率(根据t-clausen回答的注释,值为4m),您需要更强大的工具。由于您的要求是一次迭代1,并且有一个简单的标量转换,而无需数据访问,CLR UDF将是一个很好的执行者。请看-1的要点是什么,无需解释您自己?我是这个问题的原始海报,我也不确定为什么它会遭到否决。即使它不是如果我在SQL/C#方面有更多的经验,我可能会将此作为解决方案。@Herm-我添加了一个C#函数,该函数应该可以执行您想要的操作。不幸的是,我目前无法测试它,但即使它不准确,也不会太远或太困难修复。您可以尝试一下,看看它是否能提高性能。这太完美了!如果我必须在大约4 mil行的数据上执行此操作,这是一个有效的解决方案吗?更新4 mil行永远不会是无痛的。如果您知道前3个字符始终是字母,可以修改它以提高效率。此方法可以包装在函数中。这是更好的,而且它可以很容易地转换为iTVF。