【问题标题】:t-sql - Apply function to a column without sqlFunctiont-sql - 将函数应用于没有 sqlFunction 的列
【发布时间】:2019-02-12 11:03:30
【问题描述】:

我必须将数字代码转换为字母代码,如下所示:

1234 -> ABCD

其中 0 = 0、1 = A、2 = b 等等。

这是我的功能和使用方法:

Create function dbo.DecodeNumToChar
(
    @change varchar(10),
    @foo varchar(10)
) returns varchar(10)
as
begin
    DECLARE @II int = 1,
            @result varchar(10)
    ;WITH x AS 
    (
        SELECT @II as ii, STUFF(@foo,@II,1,SUBSTRING(@change,CAST(SUBSTRING(@foo,@II,1) AS INT)+1,1)) AS AA
        UNION ALL
        --SELECT @II+1
        SELECT  ii+1, STUFF(AA,ii+1,1,SUBSTRING(@change,CAST(SUBSTRING(@foo,ii+1,1) AS INT)+1,1)) AS AA
        FROM x 
        where ii+1 <= LEN(@foo)
    )

    select top 1 @result = AA from x order by ii desc
    return @result
end
--------------------------------------------
select brand_code, dbo.DecodeNumToChar('0ABCDEFGHI', brand_code) 
from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)

此函数运行良好,但在生产数据库中我们无权创建函数。

我试过把这个函数变成这样的 CTE

declare @change varchar(9) = 'ABCDEFGHI'
DECLARE @II int = 0

;WITH x AS 
(
  SELECT TOP (10) n = ROW_NUMBER() OVER (ORDER BY Number)
  FROM master.dbo.spt_values ORDER BY Number
),
innerCTE as
(
    SELECT x.n, SUBSTRING(t.brand_code, x.n, 1) chnum, 
    case SUBSTRING(t.brand_code, x.n, 1)
        when '0' then '0'
        else char(65-1+SUBSTRING(t.brand_code, x.n, 1))
    end chalfa, t.brand_code
    FROM x INNER JOIN (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)
    ON x.n <= LEN(t.brand_code)
),
CTE as
(
    select n, chnum, chalfa, brand_code, stuff(brand_code, n, 1, chalfa) as code
    from innerCTE
    union all 
    select n+1, chnum, chalfa, brand_code, STUFF(code, n+1, 1, chalfa) as code
    from cte
    where n+1 <= LEN(cte.brand_code)
)
--select * from innerCTE
select * from CTE;

或者像这个例子一样使用 CROSS APPLY:Example_1

或使用带有 PIVOT 的 CROSS APPLY,例如:Example_2

但是我的 SQL 经验很少,我无法得到正确的结果。

我想要这个:

brand_code  decoded_code
1234        ABCD
5834        EHCD
9905        II0E
0250        0BE0

谢谢

【问题讨论】:

  • 我只使用 10 个嵌套的 REPLACE 假设您不在一个确实有 DECODE 的版本上
  • Martin 的提示是最简单直接的。
  • 看起来是使用CLR Integration的好时机
  • @Jodrell 你绝对是对的,但我不知道我是否可以在 PROD 环境中使用

标签: sql-server tsql common-table-expression cross-apply


【解决方案1】:

如果使用 SQL 2017+

DECLARE @integerValues TABLE ([I] INT);

INSERT INTO @integerValues ([I])
VALUES
    (1234),
    (6485834),
    (99084705),
    (1124601);

SELECT
            T.[I],
            STRING_AGG(
                CASE SUBSTRING(T.[S], V.[number] + 1, 1)
                    WHEN '9' THEN 'I'
                    WHEN '8' THEN 'H'
                    WHEN '7' THEN 'G'
                    WHEN '6' THEN 'F'
                    WHEN '5' THEN 'E'
                    WHEN '4' THEN 'D'
                    WHEN '3' THEN 'C'
                    WHEN '2' THEN 'B'
                    WHEN '1' THEN 'A'
                    ELSE '0'
                END,
                '') WITHIN GROUP (ORDER BY T.[I]) [S]
    FROM
            (SELECT [I], CAST([I] AS VARCHAR(10)) [S] FROM @integerValues) T
        JOIN
            [master]..[spt_values] V ON V.[number] < LEN(T.[S])
    WHERE
        V.[type] = 'P';

或者,如果是 2016 年或更早

DECLARE @integerValues TABLE ([I] INT);

INSERT INTO @integerValues ([I])
VALUES
    (1234),
    (6485834),
    (99084705),
    (1124601);

SELECT
        [I],
        REPLACE(
            REPLACE(
                REPLACE(
                    REPLACE(
                        REPLACE(
                            REPLACE(
                                REPLACE(
                                    REPLACE(
                                        REPLACE(
                                            CAST([I] AS VARCHAR(10)),
                                            '1',
                                            'A'),
                                        '2',
                                        'B'),
                                    '3',
                                    'C'),
                                '4',
                                'D'),
                            '5',
                            'E'),
                        '6',
                        'F'),
                    '7',
                    'G'),
                '8',
                'H'),
            '9',
            'I') [S]
    FROM
        @integerValues;

【讨论】:

    【解决方案2】:

    以下解决方案使用 FOR XML 子查询和一些 mod (%) 操作对整数值进行拆分每个数字,然后使用映射表将每个数字与字符相关联。 FOR XML 按顺序返回数字。

    DECLARE @IntegerValues TABLE (Integer INT)
    
    INSERT INTO @IntegerValues (Integer)
    VALUES
        (1234),
        (6485834),
        (99084705),
        (1124601)
    
    SELECT
        T.Integer,
        Conversion = (
                SELECT
                    '' + M.Character -- Must have no declared alias (for xml)
                FROM
                    (VALUES
                        (1, T.Integer                       % 10),
                        (2, T.Integer /  10                 % 10),
                        (3, T.Integer /  100                % 10),
                        (4, T.Integer /  1000               % 10),
                        (5, T.Integer /  10000              % 10),
                        (6, T.Integer /  100000             % 10),
                        (7, T.Integer /  1000000            % 10),
                        (8, T.Integer /  10000000           % 10),
                        (9, T.Integer /  100000000          % 10),
                        (10, T.Integer / 1000000000         % 10),
                        (11, T.Integer / 10000000000        % 10)
                    ) AS X(OrdinalPosition, SplitDigit)
                    INNER JOIN (VALUES
                        (0, '0'),
                        (1, 'A'),
                        (2, 'B'),
                        (3, 'C'),
                        (4, 'D'),
                        (5, 'E'),
                        (6, 'F'),
                        (7, 'G'),
                        (8, 'H'),
                        (9, 'I')
                    ) AS M(Digit, Character) ON X.SplitDigit = M.Digit
                WHERE
                    X.OrdinalPosition <= FLOOR(LOG10(T.Integer)) + 1 -- This expression returns the number of digits
                ORDER BY
                    X.OrdinalPosition DESC
                FOR XML
                    PATH('')
            )
    FROM
        @IntegerValues AS T
    

    结果:

    Integer     Conversion
    1234        ABCD
    6485834     FDHEHCD
    99084705    II0HDG0E
    1124601     AABDF0A
    

    可能有一种更简洁的方法来编写映射和 mod 操作,但这应该提供一个想法。

    【讨论】:

    • 嗨@EzLo,感谢您的解决方案,它正在工作。同时,我已经解决了另一个问题,您可以在我的回答中阅读该问题
    【解决方案3】:

    我找到了使用递归 CTE 的解决方案。

    declare @change varchar(10) = '0ABCDEFGHI'
    DECLARE @II int = 1;
    
    with BRANDS as
    (
        select * from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)
    ),
    CTE as
    (
        select  @II as ii, BRAND_CODE, 
                STUFF(brand_code,@II,1,SUBSTRING(@change,CAST(SUBSTRING(brand_code,@II,1) AS INT)+1,1)) AS AA
        from BRANDS
        union all
        select  c.ii+1, b.BRAND_CODE, 
                STUFF(AA,c.ii+1,1,SUBSTRING(@change,CAST(SUBSTRING(AA,c.ii+1,1) AS INT)+1,1)) AS AA
        from BRANDS b
        inner join CTE c on b.BRAND_CODE = c.BRAND_CODE
        where c.ii < LEN(b.BRAND_CODE)
    )
    
    select BRAND_CODE, AA as NewCode from CTE where ii = len(brand_code) order by BRAND_CODE, ii
    

    【讨论】:

    • 如果您需要一次性转换数千条记录,递归 + 字符串函数可能会影响性能。尝试使用大量测试值的解决方案,看看它是否符合您的要求。也请尝试 Martin 的建议,它非常简单,可能是迄今为止发布的最快的解决方案。
    • @EzLo 我明白了......所以......在我的例子中,有 300 条记录和代码,长度为 3 位数 :-)
    • @GlaucoCucchiar 如果这是一次性操作,那没关系。如果您更频繁地需要这个并且您的长度不超过 3 位,我会考虑一个从 0 到 999 的物理表,其中代码在第二列中。只需将其加入您的集合即可生成一次并使用它...
    【解决方案4】:

    这是@Martin Smith 建议使用 10 次嵌套替换的代码。

    DECLARE @change char(10) = '0ABCDEFGHI';
    --------------------------------------------
    select brand_code,
        REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( 
        REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( brand_code
        , '0', SUBSTRING( @change, 1, 1)) , '1', SUBSTRING( @change, 2, 1)) 
        , '2', SUBSTRING( @change, 3, 1)) , '3', SUBSTRING( @change, 4, 1)) 
        , '4', SUBSTRING( @change, 5, 1)) , '5', SUBSTRING( @change, 6, 1)) 
        , '6', SUBSTRING( @change, 7, 1)) , '7', SUBSTRING( @change, 8, 1)) 
        , '8', SUBSTRING( @change, 9, 1)) , '9', SUBSTRING( @change, 10, 1)) 
    from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code);
    

    【讨论】:

    • 我不确定你为什么使用SUBSTRING
    • 我正在使用它,以防用于更改值的字符串发生更改。如果值保持不变,我同意硬编码值就足够了,我们可以删除 SUBSTRING。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-26
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多