2012-05-31 67 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

相關問題