【问题标题】:Stuff or Insert string before specific group of strings在特定的字符串组之前填充或插入字符串
【发布时间】:2014-06-27 01:28:22
【问题描述】:

我在历史表中有一个包含此类数据的表。

MEMBER_ID   COLORS
1           1) Red 2) Blue 3) Green

我需要修改或选择数据,使结果集看起来像这样。

MEMBER_ID   COLORS
1           #1) Red #2) Blue #3) Green

简而言之,在数字和右括号之前插入井号。

我尝试使用CHARINDEX,但我尝试过的方法不起作用。

【问题讨论】:

  • 会一直是个位数吗?
  • 是的,大多在 1 到 9 之间。
  • 我在想您可以根据 ) stackoverflow.com/questions/4266957/… 拆分字符串,然后附加您的 # 然后将数据拉回一起。 stackoverflow.com/questions/194852/… 我开始拉小提琴。 sqlfiddle.com/#!6/4f6fa/24
  • 嗯......在下面你说“它可能是 3 或 10”。是否有任何两位数的索引?另外,为了澄清任何解决方案所需的复杂性,被索引的文本是否可能包含数字?
  • 这很好地说明了为什么如果您的数据具有结构,那么该结构应该在数据库中可见。如果有一个名为 MemberColors 的表,其中包含列 Member_ID, Sequence, Color 和行 (1,1,Red)(1,2,Blue)(1,3,Green),那么获取 current 字符串值或新需要的字符串值将是琐碎的格式化问题,您可以在 SQL 中尝试或在屏幕上显示数据时仅在更高级别上执行。

标签: sql-server tsql


【解决方案1】:

您可以使用Replace (Colors, ' 1', ' #1') 命令将数字1 转换为#1。并且必须对每个数字使用替换命令。换句话说,您的查询必须写成如下:

SELECT Member_Id, LTRIM(REPLACE(
                        REPLACE(
                            REPLACE(
                                REPLACE(
                                    REPLACE(
                                        REPLACE(
                                            REPLACE(
                                                REPLACE(
                                                    REPLACE(' '+Colors,' 1',' #1')
                                                    ,' 2',' #2')
                                                ,' 3', ' #3')
                                            ,' 4',' #4')
                                        ,' 5',' #5')
                                    ,' 6',' #6')
                                ,' 7',' #7')
                            ,' 8',' #8')
                        ,' 9',' #9')) AS Colors
From YourTable

另一种方法:

CREATE FUNCTION dbo.f(@T NVARCHAR(100))
RETURNS NVARCHAR(100)
AS BEGIN
    DECLARE @R NVARCHAR(100)='',
            @IsDigit BIT = 0,
            @Index INT = 1
    WHILE @Index<=LEN(@T)BEGIN
        IF (SUBSTRING(@T,@Index,1) IN ('1','2','3','4','5','6','7','8','9') AND @IsDigit = 0) BEGIN
            SET @R = @R + '#'+SUBSTRING(@T,@Index,1)
            SET @IsDigit = 1
        END ELSE BEGIN
            SET @R = @R + SUBSTRING(@T,@Index,1)
            SET @IsDigit = 0
        END
        SET @Index = @Index + 1
    END
    RETURN @R
END

Select Member_ID, dbo.f(Colors)
From YourTable

【讨论】:

  • 如果颜色是食用染料怎么办:1) Red 2) Blue LAKE 40 3) Red LAKE 5
  • 我的第一条评论很幽默,但说真的,当你应用你的陈述时,两位数会发生什么? 10) Red 11) Blue 12) Green
  • 请再次检查。我编辑我的帖子。将1,'#1' 替换为' 1',' #1'(在数字和# 字符前使用空格)。
  • 感谢您的努力。让我轻轻地指出,现在这将忽略字符串开头没有空格的任何第一个数字。另外,只是一个旁注,你错过了 '3' 的替代品 :)
  • 请看我的编辑。我将 Color 更改为 ' ' + Color 并在替换后使用 LTRIM 命令
【解决方案2】:

尝试使用PATINDEX()STUFF()CTE 和递归查询 (Fiddle Demo):

--//Sample data
DECLARE @T TABLE (MEMBER_ID INT, COLOR VARCHAR(100))

INSERT @T (MEMBER_ID, COLOR)
VALUES (1, '1) Red 2) Blue 3) Green'), (2, '1) Yellow 2) Black 3) Orange')


--//Replace @T with your table name
;WITH CTE AS
(
    SELECT MEMBER_ID,  STUFF(COLOR, PATINDEX('%[0-9][)][ ]%', COLOR), 0, '#') COLOR, 1 NUMBER
    FROM @T
    UNION ALL
    SELECT CTE.MEMBER_ID, STUFF(CTE.COLOR, PATINDEX('%[^#][0-9][)][ ]%', CTE.COLOR) + 1, 0, '#'), NUMBER + 1 
    FROM CTE JOIN @T T
        ON CTE.MEMBER_ID = T.MEMBER_ID
    WHERE PATINDEX('%[^#][0-9][)][ ]%', CTE.COLOR) > 0
),
CTE2 AS
(
    SELECT *, ROW_NUMBER() OVER (PARTITION BY MEMBER_ID ORDER BY NUMBER DESC) rn
    FROM CTE
)
SELECT MEMBER_ID,COLOR FROM CTE2 WHERE RN = 1

结果:

| MEMBER_ID | COLOR                           |
|-----------|---------------------------------|
|         1 | #1) Red #2) Blue #3) Green      |
|         2 | #1) Yellow #2) Black #3) Orange |

【讨论】:

    【解决方案3】:

    我不确定您的数据的可预测性如何,因此我会提供一个建议,您可以使用类似的PATINDEXs 扩展它以用于任何其他极端情况。

    我在此建议中的假设是每个文本可能以任何单个数字开头(例如 "1)" )且前面没有空格,并且您只有一个或两位数的索引(只是扩展我对更高索引的建议),并且唯一要转换的数字是那些后跟括号的数字。

    -- first update, done only once
    -- starts with 1)  ...any single digit, no preceeding space, followed by parens
    ;WITH TMP AS (SELECT Id FROM #SRC WHERE PATINDEX('[0-9][)]%', Colors) = 1)
        UPDATE MyTable SET Colors = STUFF(Colors, 1, 0, '#')
         WHERE Id IN (SELECT Id FROM TMP);
    
    -- (you could repeat the above for strings starting with a double digit, if necessary)
    
    -- next two updates are looped until pattern is no longer found
    DECLARE @affected int = 1;
    WHILE @affected > 0
      BEGIN
        -- e.g. 1)  ...any single digit not yet converted and followed by parens
        ;WITH TMP AS (SELECT Id FROM #SRC WHERE PATINDEX('%[^#0-9][0-9][)]%', Colors) > 1)
            UPDATE MyTable SET Colors = STUFF(Colors, PATINDEX('%[^#0-9][0-9][)]%', Colors)+1, 0, '#')
             WHERE Id IN (SELECT Id FROM TMP);    
         SET @affected = @@rowcount;
      END
    
    SET @affected = 1;    
    WHILE @affected > 0
      BEGIN
        -- e.g. 10)  ...any two digits not yet converted and followed by parens
        ;WITH TMP AS (SELECT Id FROM #SRC WHERE PATINDEX('%[^#0-9][0-9][0-9][)]%', Colors) > 1)
            UPDATE MyTable SET Colors = STUFF(Colors, PATINDEX('%[^#0-9][0-9][0-9][)]%', Colors)+1, 0, '#')
             WHERE Id IN (SELECT Id FROM TMP);    
         SET @affected = @@rowcount;
      END
    

    所以,如果你从这三行开始:

    Id  Colors
    1   1) Red 2) Blue 3) Green
    2   1) Red 20) Blue 30) Green
    3   1) Red 20) Blue 3) Green
    4   9) Red 10) Blue No.4 11) Green
    

    上面的例程会产生:

    Id  Colors
    1   #1) Red #2) Blue #3) Green
    2   #1) Red #20) Blue #30) Green
    3   #1) Red #20) Blue #3) Green
    4   #9) Red #10) Blue No.4 #11) Green
    

    【讨论】:

    • 我已经更新了我的答案,不要求索引前面有空格。它必须是任何一位或两位数字,后跟一个括号。同样,这完全取决于确切的要求。
    【解决方案4】:

    假设 MS SQL Server... 最简单的方法是: 选择 MEMBER_ID, REPLACE(REPLACE(REPLACE(COLORS, '1', '#1'), '2', '#2'), '3', '#3') 作为颜色

    【讨论】:

    • 是的就是SQL Server,问题是,你不知道颜色的数量。所以数字不是固定的。它可能是 3 或 10。在这种情况下,替换将无法按照您的建议工作。
    • 您可以使用最大数量的颜色。如果您的颜色不超过 10 种,则只需执行 10 个REPLACEs。如果一列的颜色少于 10 种,则不会有任何影响。它不是很优雅,如果你有数百种颜色会很糟糕,但如果你只有 1 到 9 种颜色,这可能是最简单的解决方案。
    • 应用报表时两位数会发生什么情况? 10) Red 11) Blue 12) Green
    • 我没想过以您描述的方式使用REPLACE,但它似乎更简单且非常直接。是的,我注意到颜色不超过 10,所以这是一个可行的解决方案。谢谢大家!
    【解决方案5】:

    如果Numbers stored in sequence [1), 2), 3)...] 你可以这样做

    DECLARE @Color VARCHAR(1000) = '1) Red 2) Blue 3) Green'
    DECLARE @Count INT = 1
    DECLARE @Total INT = LEN(@Color) - LEN(REPLACE(@Color,') ',')')) -- Get Total Colors
    -- Loop
    WHILE @Count <= @Total
    BEGIN
        -- Adding '#'
        SET @Color = REPLACE(' ' + LTRIM(@Color),' ' + CAST(@Count AS VARCHAR) + ')', ' #' + CAST(@Count AS VARCHAR) + ')')
        SET @Count = @Count + 1
    END
    

    您可以在表格中更新它。您可以将其设为 UDF。

    【讨论】:

      【解决方案6】:

      这是一个有效的函数:

      create function doColors(@input varchar(max))
      returns varchar(max)
      as
      begin
          declare @parenIndex int
          declare @numIndex int = 1
          select @parenIndex = CHARINDEX(')', @input, 0)
      
          while @parenIndex > 0
          begin
              set @numIndex = 1
              while isnumeric(SUBSTRING(@input, @parenIndex-@numIndex, 1)) = 1
              begin
                  set @numindex = @numIndex + 1
              end
              if @numIndex > 1 and SUBSTRING(@input, @parenIndex-(@numIndex), 1) = ' '
              begin
                  set @input = stuff(@input, @parenIndex-(@numIndex-1), 0, '#')
              end
              select @parenIndex = CHARINDEX(')', @input, @parenIndex+2)
          end
          return @input
      end
      

      它基本上找到括号,然后向后查找数字,直到找不到任何数字,然后插入#。它适用于任意数量的颜色,并处理颜色名称中的数字、括号和# 等边缘情况。

      1) Red 12) 33 Orange 144) Pink 147) Purple #12 150) Turquoise (light blue) 1024) Brown
      1) Mauve 2) Perrywinkle (13) 3) Black (#12)
      

      变成

      #1) Red #12) 33 Orange #144) Pink #147) Purple #12 #150) Turquoise (light blue) #1024) Brown
      #1) Mauve #2) Perrywinkle (13) #3) Black (#12)
      

      SQL Fiddle

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-04
        • 2012-12-01
        • 1970-01-01
        相关资源
        最近更新 更多