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.
Related
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');
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
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)
How do I simply insert NULL if the node is empty or absent?
CREATE TABLE myxml (
"hours" DECIMAL(11,2)
);
DECLARE #xml XML =
'<?xml version="1.0" encoding="UTF-8"?>
<Data>
<Employee>
<NUMHOURS>0.5</NUMHOURS>
</Employee>
<Employee/>
<Employee>
<NUMHOURS>5</NUMHOURS>
</Employee>
<Employee>
<NUMHOURS/>
</Employee>
</Data>';
INSERT INTO myxml ("hours")
SELECT
t.c.value('NUMHOURS[1]','DECIMAL(11,2)' )
FROM #xml.nodes('/Data/Employee') t(c)
The empty node <NUMHOURS/> causes:
Error converting data type nvarchar to numeric.
I have tried:
NULLIF(t.c.value('NUMHOURS[1]','DECIMAL(11,2)' ),'')
but that seems to get processed after the fact resulting in the same error.
You could provide a default value for invalid numbers:
select case
when ISNUMERIC(t.c.value('NUMHOURS[1]', 'nvarchar(100)')) <> 1 then NULL
else t.c.value('NUMHOURS[1]', 'decimal(11,2)')
end
from #xml.nodes('/Data/Employee') t(c)
There might be a better approach: (should be at least faster with many rows...):
SELECT t.c.value('let $x:=NUMHOURS[1] return $x cast as xs:decimal?','decimal(11,2)')
FROM #xml.nodes('/Data/Employee') t(c);
The cast as xs:decimal? will cast the value to decimal if possible, or return null. Thus the final 'decimal(11,2)' has no type conversion issue any more...
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