TSQL: Replace same field multiple times in one que

2019-07-22 12:38发布

Given a table of replacements, can all the replacements be applied to a column in a table in the same query?

Disclaimer: I can do this with a cursor, or with dynamic sql to create a nested string of replacements; I want to know if it could be done concisely.

I have a replacements table,

create table #replacements(old varchar(max), new varchar(max))
insert into #replacements values
('X','Y'),
('A','B'),
('W','V'),
('C','D')

and a table of values to replace:

create table #value(value varchar(max))
insert into #value values
('XA'),
('WC')

I'd like to perform these in one query which gives back:

YB

VD

Is there any way to do this? I tried,

update v
set value = replace(value,old,new)
from #value v,
#replacements

but this gives (only the first row in the full join is done):

YA

WC

标签: tsql replace
3条回答
Root(大扎)
2楼-- · 2019-07-22 13:13

Temp tables for data sample

IF OBJECT_ID('Tempdb..#replacements') IS NOT NULL 
    DROP TABLE #replacements
IF OBJECT_ID('Tempdb..#value') IS NOT NULL 
    DROP TABLE #value;

CREATE TABLE #replacements
    (
      old VARCHAR(MAX) ,
      new VARCHAR(MAX)
    )
INSERT  INTO #replacements
VALUES  ( 'X', 'Y' ),
        ( 'A', 'B' ),
        ( 'W', 'V' ),
        ( 'C', 'D' )
CREATE TABLE #value ( value VARCHAR(MAX) )
INSERT  INTO #value
VALUES  ( 'XA' ),
        ( 'WC' )

Data before update

enter image description here


Solution using dynamic query

DECLARE @Query AS VARCHAR(MAX)
SET @Query = 'UPDATE #value SET Value = '
    + ( SELECT  REPLICATE('REPLACE(', ( SELECT  COUNT(*) - 1
                                        FROM    #replacements
                                      )) + 'REPLACE(value'
                + ( SELECT  ',''' + r.old + ''' ,''' + r.new + ''') '
                    FROM    #replacements AS r
                  FOR
                    XML PATH('')
                  ) AS R
      )

EXEC  (@Query)

Data after update

enter image description here

查看更多
ゆ 、 Hurt°
3楼-- · 2019-07-22 13:23

JBond's answer is simple but it's SLOW for any large number of rows since it will be RBAR. His function has to grab each value one by one and then run it through your replacement table. With a large number of rows, you'll see some serious performance issues.

Here's a dynamic query that is, in my opinion, a little simpler than Vasily's, although both really do the same thing. The code should perform really well for any number of rows. Try it out:

DECLARE @Replace VARCHAR(MAX);

SELECT @Replace = COALESCE('REPLACE(' + @Replace,'REPLACE(value') + ',''' + old + ''',''' + new + ''')'
FROM #replacements


EXEC
(
    'UPDATE #value
    SET Value = ' + @Replace
)

Recursive Solution

WITH CTE_replacements
AS
(
    SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) row_num,
            old,
            new
    FROM #replacements
),
CTE_recursion
AS
(
    SELECT  REPLACE(value,old,new) AS value,
            1 AS cnt
    FROM #value
    CROSS APPLY (SELECT old,new FROM CTE_replacements WHERE row_num = 1) CA

    UNION ALL

    SELECT  REPLACE(value,old,new) AS value,
            cnt + 1
    FROM cte_recursion A
    CROSS APPLY (SELECT old,new FROM CTE_replacements WHERE row_num = cnt + 1) CA

)

SELECT TOP(SELECT COUNT(*) FROM #value) *
FROM CTE_recursion
ORDER BY 2 DESC
OPTION (MAXRECURSION 0)

Compared to the dynamic SQL solution, this is not great. Compared to the function, it is better. What the recursion does is apply each change one by one to the entire dataset. So the row in replacement is applied to all the data. Then the second change(row) is applied to the entire dataset etc... So for a small number of changes because it goes through it RBAR, and then a not too large value set, it will work just fine.

查看更多
神经病院院长
4楼-- · 2019-07-22 13:26

I am giving this answer based on the assumption that the example is only using temporary tables for our convenience.

However, you could use a scalar function that does this for you.

Note - As mentioned above. This assumes that the table in the function does not need to be a temporary table.

Function:

CREATE FUNCTION dbo.ReplaceCharacters (@value VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
    SELECT @value = REPLACE(@value, r.old, r.new)
    FROM replacements r --Assumes this is no longer a temp table

    RETURN @value
END

This way you can just call the function directly from the #value.

View the result set:

SELECT
    v.value CurrentValue,
    dbo.ReplaceCharacters(v.value) ReplacedValue
FROM #value v

Output:

+--------------+----------------+
| CurrentValue | ReplacedValue |
+--------------+----------------+
|     XA       |     YB         |
|     WC       |     VD         |
+--------------+----------------+

Apply the changes:

UPDATE #value
SET value = dbo.ReplaceCharacters(value)
查看更多
登录 后发表回答