【问题标题】:Reverse only numerical parts of string in sql server在sql server中仅反转字符串的数字部分
【发布时间】:2015-08-16 00:11:03
【问题描述】:

使用 T-SQL,我试图找到最简单的方法来反转字符串中的数字。所以对于像Test123Hello 这样的字符串有Test321Hello。

[Before]           [After]
Test123Hello       Test321Hello
Tt143 Hello        Tt341 Hello
12Hll              21Hll
Tt123H3451end      Tt321H1543end

【问题讨论】:

  • 这只是为了好玩,还是一个愚蠢的任务,还是一个真正的工作问题?
  • 数字部分颠倒了,需要在我的数据中恢复
  • 很大程度上取决于字符串周围的参数。数字前后是否总是有单词,长度相同还是不同?
  • 可以是任意位数,任意位置吗?可以是同一个字符串中的两个(或多个)数字吗?
  • @jozi 你最好不要让 T-SQL 成为要求,而只是编写一个小脚本来完成这项工作。 T-SQL 不是为字符串操作而设计的,因此通用解决方案将不必要地复杂,除非您需要多次执行此操作(即使这样您也可以考虑使用 CLR 扩展来代替)

标签: sql sql-server string tsql regexp-replace


【解决方案1】:

你可以使用这个功能

    CREATE  FUNCTION [dbo].[fn_ReverseDigit_MA]
(
    @Str_IN nVARCHAR(max)   
)
RETURNS NVARCHAR(max)
AS
BEGIN   
    DECLARE @lenstr AS INT =LEN(@Str_IN)
    DECLARE @lastdigend AS INT=0


    while (@lastdigend<@lenstr)
    BEGIN

        DECLARE @strPart1 AS NVARCHAR(MAX)=LEFT(@Str_IN,@lastdigend)
        declare @lenstrPart1 AS INT=LEN(@strPart1)
        DECLARE @strPart2 AS NVARCHAR(MAX)=RIGHT(@Str_IN,@lenstr-@lastdigend)

        declare @digidx as int=patindex(N'%[0-9]%'  ,@strPart2)+@lenstrPart1
        IF(@digidx=@lenstrPart1)
        BEGIN
            BREAK;
        END     
        DECLARE @strStartdig AS NVARCHAR(MAX) = RIGHT(@Str_IN,@lenstr-@digidx+1)

        declare @NDidx as int=patindex(N'%[^0-9]%' ,@strStartdig)+@digidx-1
        IF(@NDidx<=@digidx)
        BEGIN
            SET @NDidx=@lenstr+1
        END
        DECLARE @strRet AS NVARCHAR(MAX)=LEFT(@Str_IN,@digidx-1) +REVERSE(SUBSTRING(@Str_IN,@digidx,@NDidx-@digidx)) +RIGHT(@Str_IN,@lenstr-@NDidx+1)
        SET @Str_IN=@strRet
        SET @lastdigend=@NDidx-1        
    END
    return @Str_IN  
END

【讨论】:

    【解决方案2】:

    只需使用PATINDEX 进行搜索,逐部分追加到结果字符串中:

    CREATE FUNCTION [dbo].[fn_ReverseDigits]
    (
        @Value nvarchar(max)   
    )
    RETURNS NVARCHAR(max)
    AS
    BEGIN
    
        IF @Value IS NULL
            RETURN NULL
    
        DECLARE 
            @TextIndex int = PATINDEX('%[^0-9]%', @Value), 
            @NumIndex int = PATINDEX('%[0-9]%', @Value), 
            @ResultValue nvarchar(max)  = ''
    
        WHILE LEN(@ResultValue) < LEN(@Value)
        BEGIN
    
            -- Set the index to end of the string if the index is 0
            SELECT @TextIndex = CASE WHEN @TextIndex = 0 THEN LEN(@Value) + 1 ELSE LEN(@ResultValue) + @TextIndex END
            SELECT @NumIndex = CASE WHEN @NumIndex = 0 THEN LEN(@Value) + 1 ELSE LEN(@ResultValue) + @NumIndex END
    
            IF @NumIndex < @TextIndex
                SELECT @ResultValue = @ResultValue + REVERSE(SUBSTRING(@Value, @NumIndex, @TextIndex -@NumIndex))
            ELSE
                SELECT @ResultValue = @ResultValue + (SUBSTRING(@Value, @TextIndex, @NumIndex - @TextIndex))
    
            -- Update index variables
            SELECT
                @TextIndex = PATINDEX('%[^0-9]%', SUBSTRING(@Value, LEN(@ResultValue) + 1, LEN(@Value) - LEN(@ResultValue))), 
                @NumIndex = PATINDEX('%[0-9]%', SUBSTRING(@Value, LEN(@ResultValue) + 1, LEN(@Value) - LEN(@ResultValue)))
    
        END
    
    
        RETURN @ResultValue
    END
    

    测试 SQL

    declare @Values table (Value varchar(20))
    INSERT @Values VALUES
    ('Test123Hello'),
    ('Tt143 Hello'),
    ('12Hll'), 
    ('Tt123H3451end'),
    (''),
    (NULL)
    
    SELECT Value, dbo.fn_ReverseDigits(Value) ReversedValue FROM @Values
    

    结果

    Value                ReversedValue
    -------------------- --------------------
    Test123Hello         Test321Hello
    Tt143 Hello          Tt341 Hello
    12Hll                21Hll
    Tt123H3451end        Tt321H1543end
    
    NULL                 NULL
    

    【讨论】:

      【解决方案3】:

      希望得到帮助:

      declare @s nvarchar(128) ='Test321Hello'
      declare @numStart as int, @numEnd as int
      select @numStart =patindex('%[0-9]%',@s)
      select @numEnd=len(@s)-patindex('%[0-9]%',REVERSE(@s))
      select 
      SUBSTRING(@s,0,@numstart)+
      reverse(SUBSTRING(@s,@numstart,@numend-@numstart+2))+
      SUBSTRING(@s,@numend+2,len(@s)-@numend)
      

      【讨论】:

      • 它适用于只有一个数字序列的字符串
      • 在这个例子中你的答案不起作用“Tt123H3451end”
      【解决方案4】:

      使用这个函数它也可以处理多次出现的数字

      create FUNCTION [dbo].[GetReverseNumberFromString] (@String VARCHAR(2000))
      RETURNS VARCHAR(1000)
      AS
      BEGIN
          DECLARE @Count INT
          DECLARE @IntNumbers VARCHAR(1000)
          declare @returnstring varchar(max)=@String;
          SET @Count = 0
          SET @IntNumbers = ''
      
          WHILE @Count <= LEN(@String)
          BEGIN
      
              IF SUBSTRING(@String, @Count, 1) >= '0'
                  AND SUBSTRING(@String, @Count, 1) <= '9'
              BEGIN
                  SET @IntNumbers = @IntNumbers + SUBSTRING(@String, @Count, 1)
      
              END
      
      
              IF (
                      SUBSTRING(@String, @Count + 1, 1) < '0'
                      OR SUBSTRING(@String, @Count + 1, 1) > '9'
                      )
                  AND SUBSTRING(@String, @Count, 1) >= '0'
                  AND SUBSTRING(@String, @Count, 1) <= '9'
              BEGIN
      
                  SET @IntNumbers = @IntNumbers + ','
              END
      
              SET @Count = @Count + 1
          END
      declare @RevStrings table (itemz varchar(50))
      
      INSERT INTO @RevStrings(itemz)
      select items from dbo.Split(@IntNumbers,',')
      
            select  @returnstring = Replace(@returnstring, itemz,REVERSE(itemz))from @RevStrings
          RETURN @returnstring
      END
      

      你的示例字符串

      select  [dbo].[GetReverseNumberFromString]('Tt123H3451end')
      

      结果

      Tt321H1543end
      

      更新:

      如果您没有拆分功能,请先创建它 我已将其包含在下面

      create FUNCTION Split
      (    
            @Input NVARCHAR(MAX),
            @Character CHAR(1)
      )
      RETURNS @Output TABLE (
            Items NVARCHAR(1000)
      )
      AS
      BEGIN
            DECLARE @StartIndex INT, @EndIndex INT
      
            SET @StartIndex = 1
            IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
            BEGIN
                  SET @Input = @Input + @Character
            END
      
            WHILE CHARINDEX(@Character, @Input) > 0
            BEGIN
                  SET @EndIndex = CHARINDEX(@Character, @Input)
      
                  INSERT INTO @Output(Items)
                  SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)
      
                  SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
            END
      
            RETURN
      END
      GO
      

      【讨论】:

      • 试试这个,如果你还有问题,请告诉我
      • 无效的对象名称'dbo.Split'。
      • 好的,所以你没有拆分功能等待我也可以包含它
      • 创建拆分功能,然后尝试。如果您再次遇到任何错误,请告诉我
      【解决方案5】:

      这是一个基于集合的方法:

      ;WITH Tally (n) AS
      (   
          SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
          FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
          CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n) 
          CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n) 
      ), UnpivotCTE AS (
          SELECT id, x.c, n, y.isNumber,
                 n - ROW_NUMBER() OVER (PARTITION BY id, y.isNumber 
                                        ORDER BY n) AS grp
          FROM mytable
          CROSS JOIN Tally
          CROSS APPLY (SELECT SUBSTRING(col, n, 1)) AS x(c)
          CROSS APPLY (SELECT ISNUMERIC(x.c)) AS y(isNumber)
          WHERE n <= LEN(col) 
      ), ToConcatCTE AS (
         SELECT id, c, n, isNumber,         
                grp + MIN(n) OVER (PARTITION BY id, isNumber, grp) AS grpAsc
         FROM UnpivotCTE
      )
      SELECT id, col,
             REPLACE(
              (SELECT c AS [text()]
               FROM ToConcatCTE AS t
               WHERE t.id = m.id
               ORDER BY id, 
                        grpAsc, 
                        CASE WHEN isNumber = 0 THEN n END,
                        CASE WHEN isNumber = 1 THEN n END DESC
               FOR XML PATH('')), '&#x20;',' ') AS col2
      FROM mytable AS m
      

      使用计数表来“反透视”字符串的所有字符。然后使用ROW_NUMBER 来识别数字和非数字字符的孤岛。最后,FOR XML PATH 用于重构数字岛颠倒的初始字符串:ORDER BY 用于对数字字符岛进行倒序排序。

      Fiddle Demo here

      【讨论】:

        【解决方案6】:

        这将执行您要求的特定字符串:

        select 
        substring('Test123Hello',1,4)
        +
        reverse(substring('Test123Hello',5,3))
        +
        substring('Test123Hello',8,5)
        

        从其余的值来看,您似乎需要为您获得的任何字母数字模式制作模板。例如,您可以将上述内容应用于任何具有以下形状的值:

        select * from [B&A] where [before] like '[a-z][a-z][a-z][a-z][0-9][0-9][0-9]
        [a-z][a-z][a-z][a-z][a-z]'
        

        换句话说,如果您将值(之前和之后)放入表 [B&A] 并调用列“之前”和“之后”,然后运行:

        select 
        substring(before,1,4)
        +
        reverse(substring(before,5,3))
        +
        substring(before,8,5) as [after]
        from [B&A] where [before] like '[a-z][a-z][a-z][a-z][0-9][0-9][0-9][a-z]
        [a-z][a-z][a-z][a-z]'
        

        然后它会给你'Test321Hello'。

        但其他 3 行不会受到影响,除非您创建了类似的 '[0-9][a-z]' 每个字母数字形状的类型模板,并将其应用于 [B&A] 表。您必须将结果选择到临时表或另一个表中。

        通过依次应用每个模板,您将获得大部分模板,然后您必须查看有多少行不受影响并检查字母数字形状并制作更多模板。最终你有一组代码,如果你运行它,它将捕获所有可能的组合。

        您可以坐下来以这种方式设计一个代码,该代码捕获 [a-z] 和 [0-9] 的所有可能组合。很大程度上取决于您要处理的最大字符数。

        【讨论】:

        • 最后,每一行都会有不同的模式。我怀疑 OP 想要通用解决方案
        • 我同意查看四行,因为它需要四种不同的模式。我不知道他得到的数据总是不同的,所以我认为逐渐每种模式都会重复出现,因此每行都不需要一个模式。一个通用的解决方案肯定会很好。是的。
        猜你喜欢
        • 2017-07-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-07
        • 1970-01-01
        • 2014-02-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多