2012-05-31 6 views
2

У меня есть таблица, и я хочу обновить одно из своих полей varchar на основе значений в XML-параметре.Как обновить таблицу на основе XML-параметра

У меня есть следующая таблица:

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

, и я хочу использовать следующий XML для обновления Constraint_Value поля:

<qaUpdates> 
    <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate> 
    <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate> 
</qaUpdates> 

После обновления, я стремлюсь следующее:

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

Следующий SQL иллюстрирует мою проблему (которую вы можете запустить в SQL Manag Студия без ление любой настройки):

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 

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

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

select * from #tmpConstraint 

declare @myXML XML 
set @myXML = N'<qaUpdates> 
    <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate> 
    <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate> 
</qaUpdates>' 

update c 
set constraint_value = REPLACE(constraint_value, Child.value('(old)[1]', 'varchar(50)'), Child.value('(new)[1]', 'varchar(50)')) 
from #tmpConstraint c 
cross join @myXML.nodes('/qaUpdates/qaUpdate') as N(Child) 

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 остался прежним.

Как обновить поле со всеми элементами, указанными в параметре xml?

ответ

2

Использование рекурсивного cte позволяет мне получить результат, который вы ищете. Как показано ниже. Но, по крайней мере, это не курсор/while-loop;)

declare @tmpConstraint table (ID int , Constraint_Value varchar(256)) 
insert into @tmpConstraint values 
(1, '(OldVal_1) (OldVal_2)'), 
(2, '(OldVal_2) (OldVal_1)') 

declare @myXML XML 
set @myXML = N'<qaUpdates> 
    <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate> 
    <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate> 
</qaUpdates>' 

declare @xmlData table (oldValue varchar(256), newValue varchar(256)) 
insert into @xmlData 
select 
    oldValue = Child.value('(old)[1]', 'varchar(50)'), 
    newValue = Child.value('(new)[1]', 'varchar(50)') 
from @myXML.nodes('/qaUpdates/qaUpdate') as N(Child) 

Выше была только настройка для следующего.

;with cte (ID, Constraint_Value, CLevel) 
as 
(
    select c.ID, c.Constraint_Value, 1 
    from @tmpConstraint c 

    union all 

    select p.ID, cast(replace(p.Constraint_Value, x.oldValue, x.newValue) as varchar(256)), p.CLevel + 1 
    from cte p 
    join @xmlData x on p.Constraint_Value like '%' + x.oldValue + '%' 
) 
update c 
set c.Constraint_Value = t.Constraint_Value 
from @tmpConstraint c 
join (
    select 
     *, 
     rn = row_number() over (partition by ID order by CLevel desc) 
    from cte 
) t on t.ID = c.ID and rn = 1 

select * from @tmpConstraint 
+0

Отличный ответ, @mouters. Использование cte's - очень умное решение. Благодарю. –

+0

без рада я мог бы помочь –

+0

Это приятное решение, но это действительно петля, скрытая в CTE. Также имейте в виду, что CTE может содержать в себе гораздо больше строк, чем исходная таблица (для каждой строки с n соответствующими значениями вы получите 1 + n + n (n-1) + n (n-1) (n- 2) + ... n! Строк). Также имейте в виду, что значение OPTION по умолчанию (MAXRECURSION n) равно 100. Но для таблиц умеренного размера с несколькими совпадениями в строке мне это нравится. – GilM

1

Я думаю, что проблема здесь не имеет ничего общего с XML. Это то, что один UPDATE будет обновлять каждую строку только один раз, независимо от того, сколько присоединившихся строк существует. Я думаю, что вы могли бы добавить ИНЕК и цикл WHILE, чтобы получить все ваши замены:

WHILE @@ROWCOUNT>0 
BEGIN 
    update c 
    set constraint_value = REPLACE(constraint_value, Child.value('(old)[1]', 'varchar(50)'), Child.value('(new)[1]', 'varchar(50)')) 
    from #tmpConstraint c 
    cross join @myXML.nodes('/qaUpdates/qaUpdate') as N(Child) 
    WHERE constraint_value LIKE '%' + Child.value('(old)[1]', 'varchar(50)') + '%' 
END 

Просто убедитесь, что следует за этим заявление, которое устанавливает @@ RowCount.

0

Я понимаю, что это уже было дан ответ, но мне было любопытно узнать, есть ли способ сделать это, не используя cte. Во всяком случае, большая проблема заключается в том, что вы храните 2 части данных в одном столбце/строке. Это связано с тем, что вы не можете обновлять одну и ту же строку дважды в одном заявлении о выпуске, что вызывает проблемы. Во всяком случае мой подход был это (я приношу извинения заранее за сложности):

DECLARE @tmpConstraint TABLE (
    constraint_id INT PRIMARY KEY 
    ,constraint_value VARCHAR(256) 
    ) 

INSERT INTO @tmpConstraint 
VALUES (
    1 
    ,'(OldVal_1) (OldVal_2)' 
    ) 

INSERT INTO @tmpConstraint 
VALUES (
    2 
    ,'(OldVal_2) (OldVal_1)' 
    ) 

INSERT INTO @tmpConstraint 
VALUES (
    3 
    ,'(OldVal_3) (OldVal_21) (OldVal_1)' 
    ) 

DECLARE @myXML XML 

SET @myXML = N'<qaUpdates>  <qaUpdate><old>OldVal_1</old><new>NewVal_1</new></qaUpdate>  <qaUpdate><old>OldVal_2</old><new>NewVal_2</new></qaUpdate> </qaUpdates>' 

SELECT * 
FROM @tmpConstraint 

UPDATE C 
SET constraint_value = c.New_Val 
FROM (
    SELECT Constraint_ID UpdID 
     ,Constraint_value 
     ,STUFF((
       SELECT (' ' + New_value) 
       FROM (
        --Converts XML into a Table effectively splitting the string 
        SELECT constraint_id 
         ,t.value('.', 'varchar(200)') Current_value 
         ,Coalesce(Nullif('(' + new + ')', '()'), t.value('.', 'varchar(200)')) New_Value 
        FROM 
         --Converts single column into an xml document to split rows. Uses a blank space as the identifer of rows 
         (
         SELECT constraint_id 
          ,convert(XML, ('<R>' + replace(constraint_value, ' ', '</R><R>')) + '</R>') xmldoc 
         FROM @tmpConstraint 
         ) AS a 
        CROSS APPLY a.xmldoc.nodes('./R') AS b(t) 
        --Join to table containing proposed changes based on value to change    
        LEFT JOIN (
         SELECT Child.value('./old[1]', 'varchar(100)') old 
          ,Child.value('./new[1]', 'varchar(100)') new 
         FROM @myXML.nodes('/qaUpdates/qaUpdate') AS N(Child 
         ) 
        ) q2 ON '(' + old + ')' = t.value('.', 'varchar(200)') 
       ) Modified WHERE Modified.constraint_id = base.constraint_id FOR XML path('')) 
     ,1,1,'') New_Val 
FROM @tmpConstraint Base) c 

SELECT * 
FROM @tmpConstraint 

Он выглядит много грязнее, чем и может быть очищен, если вы имели некоторые ОДС там. Но в основном я разбиваю ваш многозначный столбец на несколько строк. Включение этой

1 (OldVal_1) (OldVal_2) 
2 (OldVal_2) (OldVal_1) 
3 (OldVal_3) (OldVal_21) (OldVal_1) 

в этом

1 (OldVal_1) 
1 (OldVal_2) 
2 (OldVal_2) 
2 (OldVal_1) 
3 (OldVal_3) 
3 (OldVal_21) 
3 (OldVal_1) 

я сделать то же самое с файлом XML.Включение его в ключевые пары данных набора как этого

OldVal_1 NewVal_1 
OldVal_2 NewVal_2 

Присоединяйтесь, что с таблицей, созданной до того, чтобы получить это (дублируя Orignal значения, где не было никакой замены определяется)

1 (OldVal_1) (NewVal_1) 
1 (OldVal_2) (NewVal_2) 
2 (OldVal_2) (NewVal_2) 
2 (OldVal_1) (NewVal_1) 
3 (OldVal_3) (OldVal_3) 
3 (OldVal_21) (OldVal_21) 
3 (OldVal_1) (NewVal_1) 

рекомбинировать разделенные строки в одну строку снова сгруппированных по идентификатору ограничения, как так:

1 (OldVal_1) (OldVal_2)  (NewVal_1) (NewVal_2) 
2 (OldVal_2) (OldVal_1)  (NewVal_2) (NewVal_1) 
3 (OldVal_3) (OldVal_21) (OldVal_1) (OldVal_3) (OldVal_21) (NewVal_1) 

Затем используйте, что в моем заявлении от обновить данные в таблице Orignal. Во всяком случае, я понимаю, что код можно немного очистить и, возможно, даже упростить, но это концепция, с которой я столкнулся. На самом деле большая проблема заключается в том, как вы храните эти данные. Но твои обстоятельства мне неизвестны, поэтому я не могу судить.

Смежные вопросы