2012-05-31 33 views
3

我有一个表,我想根据另一个表中的值更新它的一个varchar字段。通过基于来自另一个表的值替换子字符串来更新表字段

我有如下表:

ID Constraint_Value 
---------------------------- 
1 (OldVal_1) (OldVal_2) 
2 (OldVal_2) (OldVal_1) 

...我想使用的数据从下表进行更新:

oldValue newValue 
---------------------------- 
OldVal_1 NewVal_1 
OldVal_2 NewVal_2 

更新后,我的目标为以下内容:

ID Constraint_Value 
---------------------------- 
1  (NewVal_1) (NewVal_2) 
2  (NewVal_2) (NewVal_1) 

以下SQL说明了我的问题(您可以在SQL管理梭哈中运行IO无需任何设置):

IF OBJECT_ID('tempdb..#tmpConstraint') IS NOT NULL DROP TABLE #tmpConstraint 
GO 
CREATE TABLE tempdb..#tmpConstraint (constraint_id INT PRIMARY KEY, constraint_value varchar(256)) 
GO 

IF OBJECT_ID('tempdb..#tmpUpdates') IS NOT NULL DROP TABLE #tmpUpdates 
GO 
CREATE TABLE tempdb..#tmpUpdates (oldValue varchar(256), newValue varchar(256)) 
GO 

insert into #tmpConstraint 
values (1, '(OldVal_1) (OldVal_2)') 

insert into #tmpConstraint 
values (2, '(OldVal_2) (OldVal_1)') 

insert into #tmpUpdates 
values ('OldVal_1', 'NewVal_1') 

insert into #tmpUpdates 
values ('OldVal_2', 'NewVal_2') 

select * from #tmpConstraint 

update c 
set constraint_value = REPLACE(constraint_value, u.oldValue, u.newValue) 
from #tmpConstraint c 
cross join #tmpUpdates u 

select * from #tmpConstraint 

这使结果:

(Before) 
1 (OldVal_1) (OldVal_2) 
2 (OldVal_2) (OldVal_1) 

(After) 
1 (NewVal_1) (OldVal_2) 
2 (OldVal_2) (NewVal_1) 

正如你所看到的只是OldVal_1已更新。 OldVal_2保持不变。

如何使用查找表中的所有数据更新字段?

+1

我意识到这个问题是相似的另一个问题我也问过(http://stackoverflow.com/questions/10836092/how-to-update-a-table- based-on-an-xml-parameter),但是我已经从这个问题的等式中移除了XML,所以希望这可能会产生一些不同的方法。当然,我会用另一个问题的任何有用答案来更新这个问题。 –

+1

..多值列,已知最严重的SQL反模式之一(为此以及其他原因)。请主轴,折叠和破坏原始设计师。它看起来像SQL Server的某些版本支持'UPDATE'语句的CTE - 它们在那种情况下是否支持_recursive_?如果是这样,你可以编写一个CTE来组装新的'constraint_value' ...值。否则,我能想到的唯一的事情就是多次运行语句,只要一行有一个旧值的实例。 –

+0

@ X-Zero - 作为原创设计师,我会在写完此评论后立即开始打磨,折叠和肢解自己!然而,为了给你一些上下文,constraint_value字段中的实际值是一个公式(例如“(((100)/ OldVal_1)* OldVal_2)”),我必须更新某些元素(原因过于冗长说明)。正如你可以想象的那样,一个公式不适合存储在关系数据库中,尤其是当这个公式可能变成更多的未来条件算法时。它仍然是一个反模式! –

回答

1

UPDATE只会影响每个源行一次。所以我知道的最简单的解决方法是一个游标。

DECLARE @o VARCHAR(256), @n VARCHAR(256); 

DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY 
FOR SELECT oldValue, newValue FROM #tmpUpdates; 

OPEN c; 

FETCH c INTO @o, @n; 

WHILE @@FETCH_STATUS = 0 
BEGIN 
    UPDATE #tmpConstraint 
     -- note full match only: 
     SET constraint_value = REPLACE(constraint_value, '(' + @o + ')', '(' + @n + ')') 
     -- and note we only touch rows where there is a full match: 
     WHERE constraint_value LIKE '%(' + @o + ')%'; 

    FETCH c INTO @o, @n; 
END 

CLOSE c; 
DEALLOCATE c; 

SELECT constraint_id, constraint_value FROM #tmpConstraint; 

结果:

constraint_id constraint_value 
------------- --------------------- 
1    (NewVal_1) (NewVal_2) 
2    (NewVal_2) (NewVal_1) 
+1

或者,您可以跳过游标,将WHERE子句添加到原始更新中,并使用WHILE @@ ROWCOUNT> 0(这也可以处理级联更改,其中NewVal_n = OldVal_m,并且您希望OldVal_n最终为NewVal_m)。 – GilM

+1

@GilM是的,这是行得通的,但我认为显式游标更简单一些(即使它是更多的代码),不应该有任何不同。你的第二个想法需要非常仔细的计划,以确保级联按正确的顺序进行处理(再次用光标保证更容易)。 –

+1

使用@@ ROWCOUNT解决方案,顺序无关紧要(但循环是可能的,因此必须处理)。但是,我对此没有信心。逐行处理更容易理解,这是一件好事。我主要通过偶尔使用游标来实现我的和平(由于编码和sql引擎错误,他们以前曾经是很多维护问题的源头,所以我仍然对它们有点怀疑)。 WHILE @@ ROWCOUNT> 0是在CTE之前遍历任意深层次结构的有效方法,所以我仍然对这种模式有所了解。 – GilM

相关问题