How do I split an xml variable using xquery? - sql-server

I have a scalar xml variable:
DECLARED #XML xml =
'<rows>
<row>
<column1 attrib="" />
<column2 attrib="" />
</row>
<!-- ... -->
</rows>';
I would like to split the data so that each row's xml is assigned to a new record in a table:
Id | ... | XML
1 | | '<row><column1 attrib="" /><column2 attrib="" /></row>'
2 | | etc.
3 | | etc.
I haven't quite grasped xquery so I'm having trouble writing an insert statement that does what I want.
INSERT into MyTable( [XML])
SELECT #XML.query('row')
Now I know something is happening but it appears rather than doing what I intended and inserting multiple new records it is inserting a single record with an empty string into the [XML] column.
What am I not getting?
Clarification
I am not trying to get the inner text, subelements or attributes from each row using value(...). I am trying to capture the entire <row> element and save it to a column of type xml
I've experimented with nodes(...) and come up with:
INSERT into MyTable([XML])
SELECT C.query('*')
FROM #XML.nodes('rows/row') T(C)
Which is closer to what I want but the results don't include the outer <row> tag, just the <column*> elements it contains.

You need to use .nodes(...) to project each <row .../> as a row and the extract the attributes of interest using .value(...). Something like:
insert into MyTable(XML)
select x.value('text()', 'nvarchar(max)') as XML
from #XML.nodes(N'/rows/row') t(x);
text() will select the inner text of each <row .../>. You should use the appropriate expression (eg. node() or #attribute etc, see XPath examples), depending on what you want from the row (your example does not make it clear at all, with all those empty elements...).

T-SQL Script:
SET ANSI_WARNINGS ON;
DECLARE #x XML =
'<rows>
<row Atr1="11" />
<row Atr1="22" Atr2="B" />
<row Atr1="33" Atr2="C" />
</rows>';
DECLARE #Table TABLE(Id INT NOT NULL, [XMLColumn] XML NOT NULL);
INSERT #Table (Id, XMLColumn)
SELECT ROW_NUMBER() OVER(ORDER BY ##SPID) AS Id,
a.b.query('.') AS [XML]
FROM #x.nodes('//rows/row') AS a(b);
SELECT *
FROM #Table t;
Results:
Id XMLColumn
-- --------------------------
1 <row Atr1="11" />
2 <row Atr1="22" Atr2="B" />
3 <row Atr1="33" Atr2="C" />

Related

Create where condition from XML value with multiple namespaces

I'm looking to return value for my where condition from an XML.
I'd like to return a value from messages table's Request column. Which is in XML data format. Unfortunately all I could achieve is retrieving nothing.
Then I tried to put the value as a column but I always get nulls for the values
Here's an XML from Request column:
<InvoiceRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/InternalReuqests">
<ActiveUserID xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">0</ActiveUserID>
<LinqConfigId xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">0</LinqConfigId>
<RequestHeaderInfo xmlns:d2p1="Fix.Services" xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">
<d2p1:MapArchive i:nil="true" />
<d2p1:HandledSuccessCategory>rscNone</d2p1:HandledSuccessCategory>
</RequestHeaderInfo>
<Username xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase" i:nil="true" />
<SSID>S-1-6-25-123456789-123456789-123456789-12345</SSID>
<miscdata xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:string>date:2020.02.26 08:27:00</d2p1:string>
<d2p1:string>hours:0</d2p1:string>
<d2p1:string>Ready:True</d2p1:string>
<d2p1:string>disct:False</d2p1:string>
<d2p1:string>extdisct:False</d2p1:string>
<d2p1:string>Matmove:False</d2p1:string>
<d2p1:string>Matlim:0</d2p1:string>
<d2p1:string>Comments:</d2p1:string>
</miscdata>
<ffreeID>468545</ffreeID>
</InvoiceRequest>
here's my sql query:
select id, Request.value('(/*:InvoiceRequest/*:ffreeID)[1]','varchar(max)')
from messages
I thought I should get in the first column the id from the database, and next to it the value of the ffreeID, but the Request.value is always null.
Could anyone look into it what am I missing?
You need for declare the default namespace, which for your xml is http://schemas.datacontract.org/2004/07/InternalReuqests:
--Sample XML
DECLARE #xml xml = '<InvoiceRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/InternalReuqests">
<ActiveUserID xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">0</ActiveUserID>
<LinqConfigId xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">0</LinqConfigId>
<RequestHeaderInfo xmlns:d2p1="Fix.Services" xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">
<d2p1:MapArchive i:nil="true" />
<d2p1:HandledSuccessCategory>rscNone</d2p1:HandledSuccessCategory>
</RequestHeaderInfo>
<Username xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase" i:nil="true" />
<SSID>S-1-6-25-123456789-123456789-123456789-12345</SSID>
<miscdata xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:string>date:2020.02.26 08:27:00</d2p1:string>
<d2p1:string>hours:0</d2p1:string>
<d2p1:string>Ready:True</d2p1:string>
<d2p1:string>disct:False</d2p1:string>
<d2p1:string>extdisct:False</d2p1:string>
<d2p1:string>Matmove:False</d2p1:string>
<d2p1:string>Matlim:0</d2p1:string>
<d2p1:string>Comments:</d2p1:string>
</miscdata>
<ffreeID>468545</ffreeID>
</InvoiceRequest>'; --Assumed this should be </InvoiceRequest>, not <InvoiceRequest>.
--Get value
WITH XMLNAMESPACES(DEFAULT 'http://schemas.datacontract.org/2004/07/InternalReuqests')
SELECT X.Request.value('(/InvoiceRequest/ffreeID/text())[1]','int')
FROM (VALUES(#XML))X(Request);
Here is another way by simulating a mock table. Everything else resembles #Larnu's solution. All credit goes to #Larnu.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Request XML);
INSERT INTO #tbl (Request)
VALUES
(N'<InvoiceRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/InternalReuqests">
<ActiveUserID xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">0</ActiveUserID>
<LinqConfigId xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">0</LinqConfigId>
<RequestHeaderInfo xmlns:d2p1="Fix.Services"
xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase">
<d2p1:MapArchive i:nil="true"/>
<d2p1:HandledSuccessCategory>rscNone</d2p1:HandledSuccessCategory>
</RequestHeaderInfo>
<Username xmlns="http://schemas.datacontract.org/2004/07/Fix.ServiceBase" i:nil="true"/>
<SSID>S-1-6-25-123456789-123456789-123456789-12345</SSID>
<miscdata xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:string>date:2020.02.26 08:27:00</d2p1:string>
<d2p1:string>hours:0</d2p1:string>
<d2p1:string>Ready:True</d2p1:string>
<d2p1:string>disct:False</d2p1:string>
<d2p1:string>extdisct:False</d2p1:string>
<d2p1:string>Matmove:False</d2p1:string>
<d2p1:string>Matlim:0</d2p1:string>
<d2p1:string>Comments:</d2p1:string>
</miscdata>
<ffreeID>468545</ffreeID>
</InvoiceRequest>');
-- DDL and sample data population, end
WITH XMLNAMESPACES(DEFAULT 'http://schemas.datacontract.org/2004/07/InternalReuqests')
SELECT ID
, c.value('(ffreeID/text())[1]','INT') AS ffreeID
FROM #tbl AS tbl
CROSS APPLY tbl.Request.nodes('/InvoiceRequest') AS t(c);
Output
+----+---------+
| ID | ffreeID |
+----+---------+
| 1 | 468545 |
+----+---------+

Query XML column - get value based on other value in the same row

in my database I have an XML-column that looks like this:
<Root>
<Row>
<Einheit>Stck</Einheit>
<Faktor>1</Faktor>
<VkPreisEinheit>1</VkPreisEinheit>
<VkMengenEinheit>1</VkMengenEinheit>
<EkMengenEinheit>1</EkMengenEinheit>
<StcklEinheit>1</StcklEinheit>
<StcklDefinition>1</StcklDefinition>
<KonsumentenEinheit>1</KonsumentenEinheit>
</Row>
<Row>
<Einheit>Stück</Einheit>
<Faktor>100</Faktor>
<EinheitFaktor>Stck</EinheitFaktor>
<EkPreisEinheit>1</EkPreisEinheit>
</Row>
</Root>
What I want to achieve ist that I get the value from 'Faktor' only from the row where 'EkPreisEinheit' is 1
I have tried something with:
CASE WHEN isnull(convert(xml,xmlcolumn).value('(/Root/Row/EkPreisEinheit)[1]','nvarchar(max)'),'') = '1'
THEN isnull(convert(xml,xmlcolumn).value('(/Root/Row/Faktor)[1]','nvarchar(max)'),'')
WHEN isnull(convert(xml,xmlcolumn).value('(/Root/Row/EkPreisEinheit)[2]','nvarchar(max)'),'') = '1'
THEN isnull(convert(xml,xmlcolumn).value('(/Root/Row/Faktor)[2]','nvarchar(max)'),'')
ELSE ''
END AS Faktor
which would work if EKPreiseinheit would be found in both columns, but it only is in one. Also it could be that it is in the first row, or in the third if there was any. Is there any way to tackle this?
When dealing with multiple of the same node, you want to use nodes in the FROM to create a row for each one. This means you end up with something like this:
DECLARE #XML xml = '<Root>
<Row>
<Einheit>Stck</Einheit>
<Faktor>1</Faktor>
<VkPreisEinheit>1</VkPreisEinheit>
<VkMengenEinheit>1</VkMengenEinheit>
<EkMengenEinheit>1</EkMengenEinheit>
<StcklEinheit>1</StcklEinheit>
<StcklDefinition>1</StcklDefinition>
<KonsumentenEinheit>1</KonsumentenEinheit>
</Row>
<Row>
<Einheit>Stück</Einheit>
<Faktor>100</Faktor>
<EinheitFaktor>Stck</EinheitFaktor>
<EkPreisEinheit>1</EkPreisEinheit>
</Row>
</Root>';
WITH YourTable AS(
SELECT V.YourXML
FROM (VALUES(#XML)) V(YourXML))
SELECT R.R.value('(EkPreisEinheit/text())[1]','int') AS EkPreisEinheit
FROM YourTable YT
--Due to the misuse of datatypes you'll need a CONVERT in a VALUES clause here instead
CROSS APPLY YT.YourXML.nodes('/Root/Row') R(R)
WHERE R.R.value('(EkPreisEinheit/text())[1]','int') IS NOT NULL;
Here is a solution for you. Few points to mention. (0) It handles XML directly from the table. (1) It converts XML data into XML data type via TRY_CAST(). So it will emit NULL when XML is not well-formed without generating any error. (2) It checks via XPath predicate for the <Row> element where 'EkPreisEinheit' element value is 1, and filters out anything else.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY(1,1) PRIMARY KEY, xmlcolumn NVARCHAR(MAX));
INSERT INTO #tbl
VALUES (N'<Root>
<Row>
<Einheit>Stck</Einheit>
<Faktor>1</Faktor>
<VkPreisEinheit>1</VkPreisEinheit>
<VkMengenEinheit>1</VkMengenEinheit>
<EkMengenEinheit>1</EkMengenEinheit>
<StcklEinheit>1</StcklEinheit>
<StcklDefinition>1</StcklDefinition>
<KonsumentenEinheit>1</KonsumentenEinheit>
</Row>
<Row>
<Einheit>Stück</Einheit>
<Faktor>100</Faktor>
<EinheitFaktor>Stck</EinheitFaktor>
<EkPreisEinheit>1</EkPreisEinheit>
</Row>
</Root>');
-- DDL and sample data population, end
;WITH rs AS
(
SELECT ID, TRY_CAST(xmlcolumn AS XML) AS xml_data
from #tbl
)
SELECT ID
, col.value('(Faktor/text())[1]','INT') AS Faktor
, col.value('(EkPreisEinheit/text())[1]','INT') AS EkPreisEinheit
FROM rs as tbl
CROSS APPLY tbl.xml_data.nodes('/Root/Row[EkPreisEinheit="1"]') AS tab(col);
Output
+----+--------+----------------+
| ID | Faktor | EkPreisEinheit |
+----+--------+----------------+
| 1 | 100 | 1 |
+----+--------+----------------+

How to iterate through a given xml input and insert the data in a TempTable in SQL Server?

Hi I have the following xml input in a SP.
DECLARE #XmlVariable XML = '<portal><patientid>67518</patientid>
<forms>
<form id="31" type="C"/>
<form id="44" type="D"/>
</forms>
</portal>'
I have the following inmemory table:
DECLARE #TColumns table (
FormId int,
FormType varchar(1),
PatientId int
)
Now, my intention is to:
1.Iterate the xml and insert the values into the #TColumns table.
2.Read the #TColumns table row by row and based on the 3 column values update some existing table;something like
update myexistingtable set status=4 where Formid=31 && Formtype='C' and PatientId=67518.
For item number 1 above, this is what I have done till now, but there is some syntax error:
INSERT INTO #TColumns(FormId,FormType,PatientId)
SELECT
XTbl.Cats.value('.', 'int'),
XTbl.Cats.value('.', 'varchar(1)'),
XTbl.Cats.value('.', 'int')
FROM
#XmlVariable.nodes('/portal/forms/form/#id') AS XTbl(Cats),
#XmlVariable.nodes('/portal/forms/form/#type') AS XTbl(Cats),
#XmlVariable.nodes('/portal/forms/form/#patientid') AS XTbl(Cats)
The error I am getting is:The correlation name 'XTbl' is specified multiple times in a FROM clause.
Need help on this and also on the item number 2 above.
Thanks in advance.
Maybe you want something like this
SELECT
Tbl1.Form.value('#id', 'int'),
Tbl1.Form.value('#type', 'varchar(1)'),
Tbl2.Portal.value('patientid', 'int')
FROM
#XmlVariable.nodes('//form') Tbl1(Form),
#XmlVariable.nodes('//portal') Tbl2(Portal)
This is what helped.Yes its is based on Hogan's last suggestion.Thank you!
INSERT INTO #TColumns(FormId,FormType,PatientId)
SELECT
Tbl1.Form.value('#id', 'int'),
Tbl1.Form.value('#type', 'varchar(1)'),
Tbl2.Portal.value('.', 'int')
FROM
#XmlVariable.nodes('//form') Tbl1(Form),
#XmlVariable.nodes('//patientid') Tbl2(Portal)

Editing XML From SQL Query Using XML-DML

I have an XML column in my MSSQL database whose schema looks similar to this:
<Form>
<Version>1000</Version>
<OtherValues />
</Form>
And I need to manually change (via script) all rows' Version numbers to 1001. Upon searching, I can infer that I'm going to be using the .modify XPath function but all examples I've found have been for inserting nodes, not editing them.
Could someone shed some light on how to do this?
Example data setup:
DECLARE #t TABLE (
Id int
, X xml
)
INSERT #t VALUES ( 1, '
<Form>
<Version>1000</Version>
<OtherValues />
</Form>
'
)
INSERT #t VALUES ( 2, '
<Form>
<Version>1000</Version>
<OtherValues />
</Form>
'
)
Pre-change data:
SELECT * FROM #t
Id X
----------- ------------------------------------------------------------
1 <Form><Version>1000</Version><OtherValues /></Form>
2 <Form><Version>1000</Version><OtherValues /></Form>
Data update:
UPDATE #t
SET X.modify('
replace value of
(/Form/Version[.="1000"]/text())[1]
with
"1001"
')
Post-change data:
SELECT * FROM #t
Id X
----------- ------------------------------------------------------------
1 <Form><Version>1001</Version><OtherValues /></Form>
2 <Form><Version>1001</Version><OtherValues /></Form>
Things to note:
replace value of requires that the 'to-be-replaced' expression identifies a "statical singleton", ie the parser must be able to work out that it refers to a single value - hence the [1]
Only one node (per row) will ever be modified by .modify! So if you have multiple XML nodes in a single row, you will have to iterate manually

How can I make a multi search SPROC/UDF by passing a tabled-value to it?

I actually want to achieve the following description
This is the table argument I want to pass to the server
<items>
<item category="cats">1</item>
<item category="dogs">2</item>
</items>
SELECT * FROM Item
WHERE Item.Category = <one of the items in the XML list>
AND Item.ReferenceId = <the corresponding value of that item xml element>
--Or in other words:
SELECT FROM Items
WHERE Item IN XML according to the splecified columns.
Am I clear enought?
I don't mind to do it in a different way other than xml.
What I need is selecting values that mach an array of two of its columns' values.
You should be able to parse the XML thus, and join it like a table.
DECLARE #foo XML;
SET #foo = N'<items>
<item category="cats">1</item>
<item category="dogs">2</item>
</items>';
WITH xml2Table AS
(
SELECT
x.item.value('#category', 'varchar(100)') AS category,
x.item.value('(.)[1]', 'int') AS ReferenceId
FROM
#foo.nodes('//items/item') x(item)
)
SELECT
*
FROM
Item i
JOIN
xml2Table_xml x ON i.category = x.Category AND i.ReferenceId = x.ReferenceId
DECLARE #x XML;
SELECt #x = N'<items>
<item category="cats">1</item>
<item category="dogs">2</item>
</items>';
WITH shred_xml AS (
SELECT x.value('#category', 'varchar(100)') AS category,
x.value('text()', 'int') AS ReferenceId
FROM #x.nodes('//items/item') t(x) )
SELECT *
FROM Item i
JOIN shred_xml s ON i.category = s.category
AND i.ReferenceId = s.ReferenceId;
BTW Doing this from memory, might had got some syntax off, specially at text().

Resources