If you really want nothing else than a check before you insert read this
Just change your code to
DECLARE @id INT = @books.value('(/Book/@id)[1]','int'); --the book-id of the "new" suggestion
UPDATE User
SET Suggestions.modify('insert sql:variable("@books") as first into (//Books)[1]')
WHERE UserId = @UserId
AND Suggestions.exist('//Book[@id=sql:variable("@id")]')=0
If you want to "merge" two Books-structures read this
DECLARE @xml1 XML= --the existing XML
'<Books>
<Book id="1" score="2" />
<Book id="2" score="3" />
<Book id="4" score="4" />
</Books>';
DECLARE @xml2 XML= --the XML with new or changed data
'<Books>
<Book id="1" score="3" />
<Book id="2" score="3" />
<Book id="5" score="5" />
</Books>';
WITH Xml1 AS
(
SELECT One.Book.value('@id','int') AS id
,One.Book.value('@score','int') AS score
FROM @xml1.nodes('/Books/Book') AS One(Book)
)
,Xml2 AS
(
SELECT One.Book.value('@id','int') AS id
,One.Book.value('@score','int') AS score
FROM @xml2.nodes('/Books/Book') AS One(Book)
)
SELECT ISNULL(Xml2.id,Xml1.id) AS [@id]
,ISNULL(Xml2.score,Xml1.score) AS [@score]
FROM Xml1
FULL OUTER JOIN Xml2 ON Xml1.id=Xml2.id
ORDER BY ISNULL(Xml2.id,Xml1.id)
FOR XML PATH('Book'),ROOT('Books');
/*
<Books>
<Book id="1" score="3" /> --score changed
<Book id="2" score="3" /> --unchanged
<Book id="4" score="4" /> --remained
<Book id="5" score="5" /> --new
</Books>
*/
You might pack this into an UDF and use it like
UPDATE User
SET Suggestions=dbo.Function(Suggestions,@NewSuggestions)
WHERE UserID=@userID
If you want to insert new and change existing read this
Regrettfully the MERGE
statement is not allowed here. You'd need an UPDATE
command in the MATCHED
and as well as in the NOT MATCHED
section...
This is my suggestion:
CREATE TABLE #User (UserID INT,Suggestions XML);
INSERT INTO #User VALUES
(1,
'<Books>
<Book id="1" score="2" />
<Book id="2" score="3" />
</Books>');
GO
CREATE PROCEDURE dbo.UpdateOrInsertSuggestion(@UserID INT, @id INT, @score INT)
AS
BEGIN
DECLARE @XmlNode XML=(SELECT @id AS [@id],@score AS [@score] FOR XML PATH('Book'));
DECLARE @UsersSuggestions XML=
(
SELECT Suggestions
FROM #User
WHERE UserID=@UserID
);
IF @UsersSuggestions.exist('//Book[@id=sql:variable("@id")]')=1
SET @UsersSuggestions.modify('replace value of (//Book[@id=sql:variable("@id")]/@score)[1] with sql:variable("@score")');
ELSE
SET @UsersSuggestions.modify('insert sql:variable("@XmlNode") as first into (//Books)[1]');
UPDATE #User SET Suggestions=@UsersSuggestions
WHERE UserID=@UserID;
END
GO
EXEC dbo.UpdateOrInsertSuggestion 1,2,2; --change score to 2 on id=2
SELECT * FROM #User;
/*
<Books>
<Book id="1" score="2" />
<Book id="2" score="2" />
</Books>
*/
EXEC dbo.UpdateOrInsertSuggestion 1,5,5; --insert new
SELECT * FROM #User;
/*
<Books>
<Book id="5" score="5" />
<Book id="1" score="2" />
<Book id="2" score="2" />
</Books>
*/
GO
DROP PROCEDURE dbo.UpdateOrInsertSuggestion;
DROP TABLE #User;
GO