Iterate through XML variable in SQL Server whether what is in XML - sql

In SQL Server, how can I query the following XML
<Employee>
<FirstName>David</FirstName>
<LastName>Jons</LastName>
<Age>28</Age>
</Employee>
<Employee>
<FirstName>Eric</FirstName>
<LastName>Terry</LastName>
<Age>36</Age>
</Employee>
<Employee>
<FirstName>Kady</FirstName>
<LastName>Campell</LastName>
<Age>21</Age>
</Employee>
If the element names are the same, we can use the approach below:
Iterate through XML variable in SQL Server
Therefore, How to get the following result or something like that:
FirstName | LastName | Age
----------- +-------------+-----
David | Jons | 28
Eric | Terry | 36
Kady | Campell | 21
given that I don't know the element name of the XML such as FisrtName, LastName, Age,
I think I hope to get result like this SQL statement:
SELECT * FROM Employee
in which I don't know the column name of table Employee

Try something like this:
DECLARE #input XML = '<Employee>
<FirstName>David</FirstName>
<LastName>Jons</LastName>
<Age>28</Age>
</Employee>
<Employee>
<FirstName>Eric</FirstName>
<LastName>Terry</LastName>
<Age>36</Age>
</Employee>
<Employee>
<FirstName>Kady</FirstName>
<LastName>Campell</LastName>
<Age>21</Age>
</Employee>'
SELECT
FirstName = xc.value('(FirstName)[1]', 'varchar(50)'),
LastName = xc.value('(LastName)[1]', 'varchar(50)'),
Age = xc.value('(Age)[1]', 'int')
FROM
#input.nodes('/Employee') AS XT(XC)
That should give you an output of:
FirstName LastName Age
---------------------------
David Jons 28
Eric Terry 36
Kady Campell 21

Related

XML to SQL table (epcis:EPCISDocument)

Could you help me here, please? I need to do a query with an XML file, however, it has something different because it has the epcis: at the beginning of the XML document.
So, if I try to do the query with epcis: and dts: the result is:
Msg 2229, Level 16, State 1, Line 38.
XQuery [nodes()]: The name "epcis" does not denote a namespace.
And if I try to do the query without epcis: and dts:, the result is in blank.
BEGIN
DECLARE #archivo XML
SET #archivo = (
'<?xml version="1.0" encoding="utf-8"?>
<epcis:EPCISDocument xmlns:dts="urn:dts:extension:xsd" schemaVersion="1.2" creationDate="2021-06-30T07:29:32.6511940Z"
xmlns:epcis="urn:epcglobal:epcis:xsd:1">
<EPCISBody>
<EventList>
<ObjectEvent>
<eventTime>2021-06-30T07:29:32</eventTime>
<eventTimeZoneOffset>+02:00</eventTimeZoneOffset>
<action>OBSERVE</action>
<bizStep>code</bizStep>
<dts:epcItemList>
<item>
<epc>123456789</epc>
<code>123456789zz</code>
</item>
<item>
<epc>9687654321</epc>
<code>9687654321zz</code>
</item>
<item>
<epc>147258369</epc>
<code>147258369zz</code>
</item>
</dts:epcItemList>
</ObjectEvent>
</EventList>
</EPCISBody>
</epcis:EPCISDocument>'
)
SELECT
patch.r.value('(epc)[1]', 'varchar(100)') as [epc],
patch.r.value('(code)[1]', 'varchar(100)') as [code]
FROM
#archivo.nodes('EPCISDocument/EPCISBody/EventList/ObjectEvent/epcItemList/item') AS patch(r)
END
GO
I need to export the information as a SQL table per item, like this:
| epc | code |
| -------- | ------------ |
| 123456789 | 123456789zz |
| 9687654321 | 9687654321zz |
enter image description here
enter image description here
Thank you so much
Juan
You need to declare your namespaces. The namespace aliases present in the XML are not relevant, you can use any alias you like (that's the bit after AS in the declaration).
Also, text() is more performant when used in .value
WITH XMLNAMESPACES (
'urn:epcglobal:epcis:xsd:1' AS epcis,
'urn:dts:extension:xsd' AS dts
)
SELECT
patch.r.value('(epc/text())[1]', 'varchar(100)') as [epc],
patch.r.value('(code/text())[1]', 'varchar(100)') as [code]
FROM #archivo.nodes('epcis:EPCISDocument/EPCISBody/EventList/ObjectEvent/dts:epcItemList/item') as patch(r);
SQL Fiddle

XML generate from SQL Query with perfect XML structure

MY SQl "employee" Table Look Like
+-------+---------+--------+----------+
| Empid | Empname | Salary | Location |
+-------+---------+--------+----------+
| 1 | Arul | 100 | Chennai |
+-------+---------+--------+----------+
XML Generate from SQl Query:
select * from employee for xml path, root('root')
from this Sql Query I'm Getting My XML Files as given below
<root>
<employee>
<Empid>1</Empid>
<Empname>Arul</Empname>
<Salary>100</Salary>
<Location>Chennai</Location>
</employee>
</root>
But My Expected Output XML from SQL query as
<root>
<column>Empid</column>
<value>1</value>
<column>Empname</column>
<value>Arul</value>
</root>
As you were told already, the needed output format is really bad and erronous. Nevertheless this can be done quite easily:
DECLARE #mockup TABLE(Empid INT,Empname VARCHAR(100),Salary DECIMAL(10,4),[Location] VARCHAR(100));
INSERT INTO #mockup VALUES(1,'Arul',100,'Chennai')
,(2,'One',200,'More');
SELECT 'Empid' AS [Column]
,EmpId AS [Value]
,'Empname' AS [Column]
,Empname AS [Value]
-- follow this pattern...
FROM #mockup t
FOR XML PATH('employee'),ROOT('root');
The result
<root>
<employee>
<Column>Empid</Column>
<Value>1</Value>
<Column>Empname</Column>
<Value>Arul</Value>
</employee>
<employee>
<Column>Empid</Column>
<Value>2</Value>
<Column>Empname</Column>
<Value>One</Value>
</employee>
</root>
But - by any chance - you should try to change this format. This is awful to query and will be your private headache for sure...
Some better suggestions:
<Employee>
<Column name="EmpId" value="1" />
<Column name="Empname" value="Arul" />
</Employee>
or
<employee id="1" name="Arul" />
or
<employee>
<Id>1</Id>
<Name>Arul</Name>
</employee>
or (if you really, really want to stick with this), at least a column index like here
<root>
<employee>
<Column inx="1">Empid</Column>
<Value inx="1">1</Value>
<Column inx="2">Empname</Column>
<Value inx="2">Arul</Value>
</employee>
<employee>
<Column inx="1">Empid</Column>
<Value inx="1">2</Value>
<Column inx="2">Empname</Column>
<Value inx="2">One</Value>
</employee>
</root>
The query for the last one is this
SELECT 1 AS [Column/#inx]
,'Empid' AS [Column]
,1 AS [Value/#inx]
,EmpId AS [Value]
,2 AS [Column/#inx]
,'Empname' AS [Column]
,2 AS [Value/#inx]
,Empname AS [Value]
-- follow this pattern...
FROM #mockup t
FOR XML PATH('employee'),ROOT('root');

XML to SQL Server Parsing Issue

I'm parsing below XML and trying to fetch all the attributes/values of node.
declare #XBL xml='
<Root>
<Department>
<Employees>
<Employee type="temp">
Jason
</Employee>
<Employee type="perm">
Roy
</Employee>
</Employees>
</Department>
<Department>
<Employees >
<Employee type="temp2">
Kevin
</Employee>
</Employees>
</Department>
</Root>'
SELECT
[Type] = XC.value('(#type)[1]', 'varchar(25)'),
[Name] = XC.value('(../Employee)[1]' , 'varchar(30)')
FROM
#XBL.nodes('Root/Department/Employees/Employee') AS XTbl(XC)
Output of above query gives me all the attributes but with first value only(Jason).
Type Name
temp Jason
perm Jason
temp2 Kevin
Expected Output:
Type Name
temp Jason
perm Roy
temp2 Kevin
This should be what you're after:
SELECT XBL.E.value('#type','varchar(25)') AS [Type],
XBL.E.value('(./text())[1]','varchar(30)') AS [Name]
FROM #XBL.nodes('Root/Department/Employees/Employee') XBL(E);
Note the use of /text() as well. When returning data from inside a node, adding /text() actually improves the performance of the query.
Edit: Also, based on your sample xml, the value returned for [Name] would actually be '{Line break} Jason{Line break}' (Obviously replace the line break with the actual character). Is that what you intend, or do you want the whitespace and line breaks/carriage returns removed as well?
You're selecting the first Employee child of the parent Department:
[Name] = XC.value('(../Employee)[1]' , 'varchar(30)'
^^^^^^^^^^^^^^^^
To select the current Employee, use:
[Name] = XC.value('(.)[1]' , 'varchar(30)')
^^^^^^
Example at SQL Fiddle.

SQL XML Check if at-least one of node has value

I am writing a stored procedure where I need to check if one of the XML field "EmployeeId" has value in it. It should return the first found value in xml nodes. For eg, in below example, it should return node with value 19.
#Table NVARCHAR(MAX),
DECLARE #xmlEmployees XML = CAST(#Table AS XML);
Following is xml structure
<Employee>
<EmployeeId></EmployeeId>
</Employee>
<Employee>
<EmployeeId></EmployeeId>
</Employee>
<Employee>
<EmployeeId>19</EmployeeId>
</Employee>
<Employee>
<EmployeeId>21</EmployeeId>
</Employee>
In the above structure, the query should return the node with value 19.
Just use correct XPath expression:
DECLARE #xmlEmployees XML = '<Employee>
<EmployeeId></EmployeeId>
</Employee>
<Employee>
<EmployeeId></EmployeeId>
</Employee>
<Employee>
<EmployeeId>19</EmployeeId>
</Employee>
<Employee>
<EmployeeId>21</EmployeeId>
</Employee>';
SELECT #xmlEmployees.value('(//EmployeeId[text()])[1]', 'int')
Given your example I used this in SQL Server 2014 and it returned the value of 19, I just selected the top 1 result, and if the xml node is empty and is of value type INT it seems SQL Server converts it to 0, so I added a WHERE clause to filter out the top 2 entries as their EmployeeId values equal zero:
DECLARE #xDoc XML = '<Employee>
<EmployeeId></EmployeeId>
</Employee>
<Employee>
<EmployeeId></EmployeeId>
</Employee>
<Employee>
<EmployeeId>19</EmployeeId>
</Employee>
<Employee>
<EmployeeId>21</EmployeeId>
</Employee>'
SELECT TOP 1 a.doc.value('EmployeeId[1]', 'INT') AS [EmployeeId]
FROM #xDoc.nodes('/Employee') as a(doc)
WHERE a.doc.value('EmployeeId[1]', 'INT') <> 0

How to insert NULL into SQL Server DATE field *from XML*

I've got some XML that I'm trying to insert into a Microsoft SQL Server database using their XML datatype functions.
One of the table fields is a nullable DATE column. If the node is missing, then it's inserted as NULL which is great. However, if the node is present but empty <LastDay/> when running the XPath query, it interprets the value from the empty node as an empty string '' instead of NULL. So when looking at the table results, it casts the date to 1900-01-01 by default.
I would like for empty nodes to also be inserted as NULL instead of the default empty string '' or 1900-01-01. How can I get it to insert NULL instead?
CREATE TABLE myxml
(
"id" INT,
"name" NVARCHAR(100),
"company" NVARCHAR(100),
"lastday" DATE
);
DECLARE #xml XML =
'<?xml version="1.0" encoding="UTF-8"?>
<Data xmlns="http://example.com" xmlns:dmd="http://example.com/data-metadata">
<Company dmd:name="Adventure Works Ltd.">
<Employee id="1">
<Name>John Doe</Name>
<LastDay>2016-08-01</LastDay>
</Employee>
<Employee id="2">
<Name>Jane Doe</Name>
</Employee>
</Company>
<Company dmd:name="StackUnderflow">
<Employee id="3">
<Name>Jeff Puckett</Name>
<LastDay/>
</Employee>
<Employee id="4">
<Name>Ill Gates</Name>
</Employee>
</Company>
</Data>';
WITH XMLNAMESPACES (DEFAULT 'http://example.com', 'http://example.com/data-metadata' as dmd)
INSERT INTO myxml (id,name,company,lastday)
SELECT
t.c.value('#id', 'INT' ),
t.c.value('Name[1]', 'VARCHAR(100)' ),
t.c.value('../#dmd:name','VARCHAR(100)' ),
t.c.value('LastDay[1]', 'DATE' )
FROM #xml.nodes('/Data/Company/Employee') t(c)
This produces:
id name company lastday
------------------------------------------------
1 John Doe Adventure Works Ltd. 2016-08-01
2 Jane Doe Adventure Works Ltd. NULL
3 Jeff Puckett StackUnderflow 1900-01-01
4 Ill Gates StackUnderflow NULL
I am trying to achieve:
id name company lastday
------------------------------------------------
1 John Doe Adventure Works Ltd. 2016-08-01
2 Jane Doe Adventure Works Ltd. NULL
3 Jeff Puckett StackUnderflow NULL
4 Ill Gates StackUnderflow NULL
You have to use NULLIF function to avoid default values popping out from XML selection.
Returns a null value if the two specified expressions are equal.
Your query will be changed as below:
SELECT
t.c.value('#id', 'INT' ),
t.c.value('Name[1]','VARCHAR(100)' ),
t.c.value('../#dmd:name', 'VARCHAR(100)' ),
NULLIF(t.c.value('LastDay[1]', 'DATE' ),'')
FROM #xml.nodes('/Data/Company/Employee') t(c)
For more information on NULLIF, please check this MSDN page.
Besides techspider's very good answer I'd like to show another approach:
Doing .nodes() on Company and CROSS APPLY .nodes() on Employee allows a cleaner XPath navigation and avoids the backward navigation you are using by ../#dmd.name. In your case this is just for info probably, but good to consider: If there was a company without any Employee you would skip the whole company otherwise... (My code would skip as well due to the CROSS APPLY, but you could use OUTER APPLY).
And to your actual question: Using the internal cast as xs:date will do the logic within the XQuery and should be faster...
WITH XMLNAMESPACES (DEFAULT 'http://example.com', 'http://example.com/data-metadata' as dmd)
INSERT INTO myxml (id,name,company,lastday)
SELECT
e.value('#id', 'INT' ),
e.value('Name[1]', 'VARCHAR(100)' ),
c.value('#dmd:name', 'VARCHAR(100)' ),
e.value('let $x:=LastDay[1] return $x cast as xs:date?','DATE' )
FROM #xml.nodes('/Data/Company') AS A(c)
CROSS APPLY c.nodes('Employee') AS B(e)