How can I insert a whole bunch of rows into an XML variable without using a cursor?
I know I can do
SET #errors.modify('insert <error>{ sql:variable("#text") }</error> as last into /errors[1]')
to insert the value of a variable, but I want to basically do
SET #errors.modify(SELECT 'insert <error>{ sql:column("text") }</error>' FROM table)
which, of course, isn't legal syntax.
Edit: Obviously my question wasn't clear. What I want is to be able to do like this:
CREATE TABLE my_table(text nvarchar(50))
INSERT INTO my_table VALUES('Message 2')
INSERT INTO my_table VALUES('Message 3')
DECLARE #errors xml
SET #errors = '<errors><error>Message 1</error></errors>'
SET #errors.modify('INSERT EVERYTHING FROM my_table MAGIC STATEMENT')
And after running this code, #errors should contain
<errors>
<error>Message 1</error>
<error>Message 2</error>
<error>Message 3</error>
</errors>
Isn't this simpler?
set ErrorXML=(SELECT * from #MyTable FOR XML AUTO)
LAST UPDATE:
OK, now that the question is much clearer, he's the solution - hopefully!!
DECLARE #errors xml
SET #errors = '<errors><error>Message 1</error></errors>'
DECLARE #newErrors XML
SELECT #newErrors = (SELECT text AS 'error'
FROM dbo.my_table
FOR XML PATH(''), ELEMENTS)
SELECT #errors, #newErrors
SET #errors.modify('insert sql:variable("#newErrors") as last into (/errors)[1]')
SELECT #errors
This gives me
#errors at the beginning
<errors><error>Message 1</error></errors>
#newError after the "magic" SELECT:
<error>Message 2</error><error>Message 3</error>
#errors after the UPDATE:
<errors>
<error>Message 1</error>
<error>Message 2</error>
<error>Message 3</error>
</errors>
Is THAT what you're looking for?? :-)
(old answers - not what the OP was looking for.....)
You need to look at the .nodes() function in SQL XQuery - this will break up an XML
variable into a list of XML nodes, based on an XPath expression (that references some point in your XML where you are likely to have an enumeration of nodes of the same structure), and it gives them a "virtual" table and column name.
Based on that "Table.Column" element, you can select single values from that XML node - either attributes or sub-elements - and you get these back as "atomic" values, e.g. as INT, VARCHAR(x), whatever you need. These values can be inserted into the table:
INSERT dbo.YourTable(col1, col2, col3, ..., colN)
SELECT
Error.Column.value('#attr1[1]', 'varchar(20)'),
Error.Column.value('subitem[1]', 'int'),
.....
Error.Column.value('subitemN[1]', 'DateTime')
FROM
#xmldata.nodes('/error') AS Error(Column)
UPDATE: ok, so you want to do the opposite - turn relational data into XML - that's even easier :-)
DECLARE #NewXmlContent XML
SELECT #NewXmlContent =
(SELECT
col1 as '#ID',
col2 as 'SomeElement',
.....
colN as 'LastElement'
FROM
dbo.YourTable
WHERE
....
FOR XML PATH('element'), ROOT('root')
)
UPDATE YourOtherTable
SET XmlField.modify('insert sql:variable("#NewXmlContent")
as last into (/XPath)[1]')
WHERE (some condition)
This will give you something like this in #NewXmlContent:
<root>
<element ID="(value of col1)">
<SomeElement>(value of col2)</SomeElement>
.....
<LastElement>(value of colN)</LastElement>
</element>
</root>
and the UPDATE statement with the .modify() call will actually insert that content into an existing XML field in your database. This is the only way to get XML contents into an existing XML column - there's no way of directly referencing another XML column inside a XML fragment being inserted....
The new "FOR XML PATH" syntax is very powerful and flexible and allows you to do just about anything.
And of course, you can easily store that into a XML variable.
Marc
Based on marc's answer, here is a solution that works for SQL Server 2005:
CREATE TABLE #my_table(text nvarchar(50))
INSERT INTO #my_table VALUES('Message 2')
INSERT INTO #my_table VALUES('Message 3')
DECLARE #errors xml
SET #errors = '<errors><error>Message 1</error></errors>'
SELECT #errors = CAST(#errors AS nvarchar(max)) + '<new>' + (SELECT text AS 'error' FROM #my_table FOR XML PATH(''), ELEMENTS) + '</new>'
SET #errors = CAST(#errors AS nvarchar(max)) + '<new>' + #newErrors + '</new>'
SET #errors.modify('insert (/new/*) as last into (/errors)[1]')
SET #errors.modify('delete (/new)')
SELECT #errors
DROP TABLE #my_table
Will return
<errors>
<error>Message 1</error>
<error>Message 2</error>
<error>Message 3</error>
</errors>
Related
I generate a XML with XML PATH that has 6 namespaces. There is a schema and therefore I can not change the XML structure.
WITH XMLNAMESPACES ('NS1' AS ns1,
'NS2' AS ns2,
'NS3' AS ns3,
'NS4' AS ns4,
'NS5' AS ns5,
'NS6' AS ns6)
SELECT(SELECT 'something' AS 'ns3:node2' FOR XML PATH('ns2:Node1'), TYPE)
FOR XML PATH(''),
ROOT('ns1:RootNode');
Now I need to either insert the output into a tmp table or store it in a variable. The problem I am stuck at is that with needs a ; in front of it. So Set #myVariable = above Codeblock and insert into tmp table values(above codeblock) are both not working and I am wondering if there is a way to store it. The XML is valid and works fine if I look at it or save it to the hard disk, but I need to do some more work with that XML.
You need to put your whole SELECT within a further subquery. So for an insert:
DECLARE #T TABLE (X XML);
WITH XMLNAMESPACES ('NS1' AS ns1,
'NS2' AS ns2,
'NS3' AS ns3,
'NS4' AS ns4,
'NS5' AS ns5,
'NS6' AS ns6)
INSERT #T (X)
SELECT (SELECT (SELECT 'something' AS 'ns3:node2' FOR XML PATH('ns2:Node1'), TYPE)
FOR XML PATH(''),
ROOT('ns1:RootNode'));
SELECT * FROM #T;
Or to assign a variable:
DECLARE #X XML;
WITH XMLNAMESPACES ('NS1' AS ns1,
'NS2' AS ns2,
'NS3' AS ns3,
'NS4' AS ns4,
'NS5' AS ns5,
'NS6' AS ns6)
SELECT #X= (SELECT (SELECT 'something' AS 'ns3:node2' FOR XML PATH('ns2:Node1'), TYPE)
FOR XML PATH(''),
ROOT('ns1:RootNode')
);
SELECT #X;
This might be a simple question for those who are experienced in working with JSON in SQL Server. I found this interesting way of aggregating strings using FOR XML in here.
create table #t (id int, name varchar(20))
insert into #t
values (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus')
select id
,Names = stuff((select ', ' + name as [text()]
from #t xt
where xt.id = t.id
for xml path('')), 1, 2, '')
from #t t
group by id
How can I do the same using JSON instead of XML?
You cannot replace the XML approach with JSON. This string concatenation works due to some XML inner peculiarities, which are not the same in JSON.
Starting with SQL Server 2017 onwards you can use STRING_AGG(), but with earlier versions, the XML approach is the way to go.
Some background and a hint
First the hint: The code you showed is not safe for the XML special characters. Check my example below.
First I declare a simple XML
DECLARE #xml XML=
N'<a>
<b>1</b>
<b>2</b>
<b>3</b>
<c>
<d>x</d>
<d>y</d>
<d>z</d>
</c>
</a>';
--The XPath . tells the XML engine to use the current node (and all within)
--Therefore this will return any content within the XML
SELECT #xml.value('.','varchar(100)')
--You can specify the path to get 123 or xyz
SELECT #xml.query('/a/b').value('.','varchar(100)')
SELECT #xml.query('//d').value('.','varchar(100)')
Now your issue to concatenate tabular data:
DECLARE #tbl TABLE(SomeString VARCHAR(100));
INSERT INTO #tbl VALUES('This'),('will'),('concatenate'),('magically'),('Forbidden Characters & > <');
--The simple FOR XML query will tag the column with <SomeString> and each row with <row>:
SELECT SomeString FROM #tbl FOR XML PATH('row');
--But we can create the same without any tags:
--Attention: Look closely, that the result - even without tags - is XML typed and looks like a hyper link in SSMS.
SELECT SomeString AS [*] FROM #tbl FOR XML PATH('');
--Now we can use as a sub-select within a surrounding query.
--The result is returned as string, not XML typed anymore... Look at the forbidden chars!
SELECT
(SELECT SomeString FROM #tbl FOR XML PATH('row'))
,(SELECT SomeString AS [*] FROM #tbl FOR XML PATH(''))
--We can use ,TYPE to enforce the sub-select to be treated as XML typed itself
--This allows to use .query() and/or .value()
SELECT
(SELECT SomeString FROM #tbl FOR XML PATH('row'),TYPE).query('data(//SomeString)').value('.','nvarchar(max)')
,(SELECT SomeString AS [*] FROM #tbl FOR XML PATH(''),TYPE).value('.','nvarchar(max)')
XQuery's .data() can be used to concatenate named elements with blanks in between.
XQuery's .value() must be used to re-escpae forbidden characters.
I need to add some XML elements into an XML column in SQL Server.
Here's a simplified example of my code:
DECLARE #temp XML = '<Rate>' + CONVERT(VARCHAR(20), #RateAmt, 1) + '</Rate>'
UPDATE [TableName]
SET [XMLValue].modify('declare namespace ns="http://www.mycompany.com/schema";insert sql:variable("#temp") as last into (/ns:Element1/ns:Element2)[1]')
WHERE id = #Id
Here's the output:
<Rate xmlns="">12.00</Rate>
So, the code is working, however, how do I remove the xmlns="" attribute?
Why are you inserting a namespace if you don't want one in the xml?
DECLARE #RateAmt decimal(9,2) = 12.00
DECLARE #temp XML = '<Rate>' + CONVERT(VARCHAR(20), #RateAmt, 1) + '</Rate>'
DECLARE #tempTable TABLE
(
Column1 Xml
)
INSERT INTO #tempTable(Column1)
SELECT #temp
OR
UPDATE #tempTable
SET Column1 = (SELECT #temp)
SELECT * FROM #tempTable
<Rate>12.00</Rate>
(1 row(s) affected)
There is an accepted answer already (especially concerning your namespace issue), great, just some hints:
There are very rare situation where one should create XML via string concatenation... Especially in connection with strings (special characters!), numbers (format!) and date/time values (culture and format!) it is much better to rely on the implicit translations using SELECT ... FOR XML ...
DECLARE #RateAmt DECIMAL(12,4)=12.0;
This is possible, but not good:
DECLARE #temp XML = '<Rate>' + CONVERT(VARCHAR(20), #RateAmt, 1) +'</Rate>'
Better try this
DECLARE #temp XML=(SELECT #RateAmt FOR XML PATH('Rate'));
Your attempt to insert this into existing XML can be done the way you do it already (create the XML-node externally and insert it as-is), it might be easier to insert the plain value:
DECLARE #tbl TABLE(ID INT IDENTITY,XMLValue XML);
INSERT INTO #tbl VALUES
(N'<Element1><Element2><test>FirstTest</test></Element2></Element1>')
,(N'<Element1><Element2><test>Second</test></Element2></Element1>');
--ID=1: Insert the way you do it:
UPDATE #tbl
SET [XMLValue].modify('insert sql:variable("#temp") as last into (/Element1/Element2)[1]')
WHERE id = 1
--ID=2: Insert the value of #RateAmt directly
SET #RateAmt=100.00;
UPDATE #tbl
SET [XMLValue].modify('insert <Rate>{sql:variable("#RateAmt")}</Rate> as last into (/Element1/Element2)[1]')
WHERE id = 2
This is Result ID=1
<Element1>
<Element2>
<test>FirstTest</test>
<Rate>12.0000</Rate>
</Element2>
</Element1>
And ID=2
<Element1>
<Element2>
<test>Second</test>
<Rate>100</Rate>
</Element2>
</Element1>
I have a table where a column contains XML data. Now i want to retrieve those xml data with restriction of nodes. Kindly see the following example for more explanation on my scenario,
declare #table table (id int, xmlfield xml) insert into #table select 1,'<Root xmlns="">
<Sample>
<Issue>
<Level>one</Level>
<Descp>First Example</Descp>
</Issue>
<Issue>
<Level>two</Level>
<Descp>Second Example</Descp>
</Issue>
</Sample> </Root>'
select * from #table
Now i need the following result set
Id XMLfield
1 first example
ie, for the selected level,i need the decription for it. More clearly, the node should be restricted for <level>one</level>
(need: What is the description for level one ?)
thanks in advance
Have a look at the xml Data Type Methods
select id,
xmlfield.value('(//Issue[Level = "one"]/Descp/text())[1]', 'varchar(100)') as XMLField
from #table
The XQuery you're looking for is
//Issue[Level = "one"]/Descp/data()
My question is similar to this one: Choose a XML node in SQL Server based on max value of a child element
except that my column is NOT of type XML, it's of type nvarchar(max).
I want to extract the XML node values from a column that looks like this:
<Data>
<el1>1234</el1>
<el2>Something</el2>
</Data>
How can I extract the values '1234' and 'Something' ?
doing a convert and using the col.nodes is not working.
CONVERT(XML, table1.col1).value('(/Data/el1)[1]','int') as 'xcol1',
After that, I would like to do a compare value of el1 (1234) with another column, and update update el1 as is. Right now I'm trying to just rebuild the XML when passing the update:
ie
Update table set col1 ='<Data><el1>'+#col2+'</el1><el2>???</el2>
You've got to tell SQL Server the number of the node you're after, like:
(/Data/el1)[1]
^^^
Full example:
declare #t table (id int, col1 varchar(max))
insert #t values (1, '<Data><el1>1234</el1><el2>Something</el2></Data>')
select CAST(col1 as xml).value('(/Data/el1)[1]', 'int')
from #t
-->
1234
SQL Server provides a modify function to change XML columns. But I think you can only use it on columns with the xml type. Here's an example:
declare #q table (id int, col1 xml)
insert #q values (1, '<Data><el1>1234</el1><el2>Something</el2></Data>')
update #q
set col1.modify('replace value of (/Data/el1/text())[1] with "5678"')
select *
from #q
-->
<Data><el1>5678</el1><el2>Something</el2></Data>
At the end of the day, SQL Server's XML support makes simple things very hard. If you value maintainability, you're better off processing XML on the client side.