Suppose we have:
CREATE TABLE #Users(id INT PRIMARY KEY, name VARCHAR(100), suggestions XML);
INSERT INTO #Users(id, name, suggestions)
SELECT 1, 'Bob', N'<Products>
<Product id="1" score="1"/>
<Product id="2" score="5"/>
<Product id="3" score="4"/>
</Products>'
UNION ALL
SELECT 2, 'Jimmy', N'<Products>
<Product id="6" score="3"/>
</Products>';
DECLARE @userId INT = 1,
@suggestions XML = N'<Products>
<Product id="2" score="5"/>
<Product id="3" score="2"/>
<Product id="7" score="1" />
</Products>';
Now I want to merge 2 XMLs based on id
attribute:
Final result for user with id = 1:
<Products>
<Product id="1" score="1"/> -- nothing changed (but not exists in @suggestions)
<Product id="2" score="5"/> -- nothing changed (but exists in @suggestions)
<Product id="3" score="2"/> -- update score to 2
<Product id="7" score="1"/> -- insert new element
</Products>
Please note that it is not combining 2 XMLs but "upsert" operation.
Remarks:
- I know that this kind of schema violates database normalization and normalizing it is the way to go (but not in this case)
- I know solution that utilize derived tables,
.nodes()
and.value()
functions first to parse both XML, then merge and write back
I am searching for is XPath/XQuery
expression that will merge it in one statement (no derived tables/dynamic-sql*):
* If absolutely needed, Dynamic SQL could be used, but I want to avoid it.
UPDATE #Users
SET suggestions.modify(... sql:variable("@suggestions") ...); --changes only here
WHERE id = @userId;
/* replace ... for ... where ... with sql:variable */