3

I have a table containing data like this in a historical table.

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

I need modify or select the data so that the result-set looks like this.

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

In a nutshell insert the pound before the number and the closing parenthesis.

I tried using CHARINDEX but what I have tried did not work.

Asynchronous
  • 3,917
  • 19
  • 62
  • 96
  • Will it always be single digit numbers? – shree.pat18 Jun 26 '14 at 02:39
  • Yes, mostly between 1 to 9. – Asynchronous Jun 26 '14 at 03:22
  • I was thinking you could split the string based on the ) http://stackoverflow.com/questions/4266957/how-to-split-string-and-save-into-an-array-in-t-sql And then append your # And then pull the data back together. http://stackoverflow.com/questions/194852/concatenate-many-rows-into-a-single-text-string I started a fiddle. http://sqlfiddle.com/#!6/4f6fa/24 – Brian Boyd Jun 26 '14 at 03:49
  • Mmm...down below you say "it might be 3 or 10". Are any double-digit indexes possible or not? Also, just to clarify the requried complexity of any solution, is it possible for the text being indexed to contain numbers or not? – mdisibio Jun 26 '14 at 04:28
  • 1
    This is a good demonstration of why, if your data has a structure, that structure ought to be *visible* in the database. If there was a table called MemberColors that contained columns `Member_ID, Sequence, Color` and the rows `(1,1,Red)`, `(1,2,Blue)` and `(1,3,Green)` then obtaining either the *current* string value or the newly required string value would be a trivial *formatting* concern that you could attempt in SQL or just perform at a higher level when presenting the data on screen. – Damien_The_Unbeliever Jun 26 '14 at 07:06

6 Answers6

2

I'm not sure how predictable your data is, so I will offer a suggestion and you can expand it with similar PATINDEXs for any other corner cases.

My assumptions in this suggestion are that each text might start with any single digit ( e.g "1)" ) with no preceeding space and that you have only one or two digit indices (simply expand my suggestion for higher indices), and that the only numbers to convert are those which are followed by a parens.

-- 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

So, if you started with these three rows:

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

The above routine would yield:

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
mdisibio
  • 3,148
  • 31
  • 47
  • I've updated my answer to not require the index to be preceeded by a space. It just has to be any one or two digits followed by a parens. Again, it all depends on the exact requirements. – mdisibio Jun 26 '14 at 15:19
2

You can use Replace (Colors, ' 1', ' #1') Command to convert digit 1 to #1. and must use Replace command for each digits. In other word your query must be write as a following:

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

Another method:

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
mehdi lotfi
  • 11,194
  • 18
  • 82
  • 128
  • What if the colors are food dyes: `1) Red 2) Blue LAKE 40 3) Red LAKE 5` ? – mdisibio Jun 26 '14 at 03:57
  • I was being humorous in my first comment, but seriously, what happens to two digit numbers when you apply your statement? `10) Red 11) Blue 12) Green` – mdisibio Jun 26 '14 at 04:08
  • Please check again. I edit my post. replace `1,'#1'` to `' 1',' #1'`(Use space before your digits and # character). – mehdi lotfi Jun 26 '14 at 04:11
  • I appreciate your effort. Let me gently point out that this will now ignore any of the first digits at the beginning of the string that are not preceeded by a space. Also, just a side note, you are missing the replacement for '3' :) – mdisibio Jun 26 '14 at 04:18
  • @mdisibio. I'm sorry. I forget to match number 3. my post edited again. – mehdi lotfi Jun 26 '14 at 04:26
  • Cool. Now to take the first digit without a space into consideration? Can you figure it out? Try `STUFF`ing and un `STUFF`ing your entire expression. I have a working expression, but don't want to steal your thunder. – mdisibio Jun 26 '14 at 04:36
  • 1
    Please see my edit. I change Color to ' ' + Color and use LTRIM command after replace – mehdi lotfi Jun 26 '14 at 04:54
  • You got it! Valant effort. So I would say that this will solve the OP's requirements **assumming** that the text can contain no digits except for the indices, that all digits are indices, and that all indices except possibly the first are preceeded by spaces. – mdisibio Jun 26 '14 at 05:04
  • @mdisibio. I write another method for your purpose. please check again. I have serious decisions to solve your problem. – mehdi lotfi Jun 26 '14 at 05:18
2

Try this using PATINDEX(), STUFF(), CTE and Recursive query (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

RESULTS:

| MEMBER_ID | COLOR                           |
|-----------|---------------------------------|
|         1 | #1) Red #2) Blue #3) Green      |
|         2 | #1) Yellow #2) Black #3) Orange |
Kaf
  • 33,101
  • 7
  • 58
  • 78
1

Assuming MS SQL Server... Easiest way is: Select MEMBER_ID, REPLACE(REPLACE(REPLACE(COLORS, '1', '#1'), '2', '#2'), '3', '#3') AS COLORS

BAReese
  • 491
  • 2
  • 5
  • Yes is SQL Server, the problem is, you do not know the number of colors. So the numbers are not fixed. It may be 3 or 10. In that case the replace wont work as you are suggesting. – Asynchronous Jun 26 '14 at 03:22
  • You could do it for the maximum number of colors that you will have. If you won't have more than 10 colors, just do 10 `REPLACE`s. It won't hurt anything if a column has less than 10 colors. It's not very elegant, and it would suck if you had hundreds of colors, but if you only have between 1 and 9, this might be the easiest solution. – Jerrad Jun 26 '14 at 03:30
  • What happens to two digit numbers when you apply your statement? `10) Red 11) Blue 12) Green` – mdisibio Jun 26 '14 at 04:09
  • I did not think of using the `REPLACE` in the manner you described but it seemed to be simpler and very straight forward. And yes, I noticed that the colors does not exceed 10 so this is a workable solution. Thanks everyone! – Asynchronous Jun 28 '14 at 18:11
1

If Numbers stored in sequence [1), 2), 3)...] you can do like the following

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

And you can update it in table. You can make this as UDF.

Jesuraja
  • 3,774
  • 4
  • 24
  • 48
1

Here's a function that works:

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

It basically finds the parenthesis and then works backwards looking for numbers until it doesn't find any more, then inserts the #. It works for any number of colors and handles edge cases like numbers, parentheses, and # in the color name.

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

becomes

#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

Jerrad
  • 5,240
  • 1
  • 18
  • 23