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

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

Related

How to convert this SQL query to XQuery?

How to convert this SQL query into the XQuery equivalent
SELECT e.essn AS one, f.essn AS two, e.pno, f.pno
FROM works_on AS e JOIN works_on AS f on e.pno = f.pno
AND e.essn < f.essn \n" . "ORDER BY `e`.`pno` ASC
I have tried this so far, but it does not give the desired results
let $prods := doc("../company/works_on.xml")//works_on[pno = $project/pnumber]
for $d in distinct-values($prods/essn),
$n in distinct-values($prods[essn = $d]/pno)
return <result essn="{$d}" pno="{$n}"/>
This is a sample of the XML file that I am using.
<dataroot>
<works_on>
<essn>123456789</essn>
<pno>1</pno>
<hours>32.5</hours>
</works_on>
<works_on>
<essn>123456789</essn>
<pno>2</pno>
<hours>7.5</hours>
</works_on>
<works_on>
<essn>333445555</essn>
<pno>2</pno>
<hours>10</hours>
</works_on>
<works_on>
<essn>333445555</essn>
<pno>3</pno>
<hours>10</hours>
</works_on>
This is the output so far
<results>
<project>
<pnumber>1</pnumber>
<employee>
<essn>123456789</essn>
<essn>453453453</essn>
</employee>
</project>
<project>
<pnumber>2</pnumber>
<employee>
<essn>123456789</essn>
<essn>333445555</essn>
<essn>453453453</essn>
</employee>
</project>
<project>
<pnumber>3</pnumber>
<employee>
<essn>333445555</essn>
<essn>666884444</essn>
</employee>
</project>
<project>
<pnumber>10</pnumber>
<employee>
<essn>333445555</essn>
<essn>987987987</essn>
<essn>999887777</essn>
</employee>
</project>
<project>
<pnumber>20</pnumber>
<employee>
<essn>333445555</essn>
<essn>888665555</essn>
<essn>987654321</essn>
</employee>
</project>
<project>
<pnumber>30</pnumber>
<employee>
<essn>987654321</essn>
<essn>987987987</essn>
<essn>999887777</essn>
</employee>
</project>
</results>
I'm trying to get the values for essn into pairs for example, on project 1, it will be as is, but for project 2, there will be 123456789 and 333445555 as one pair, 123456789 and 453453453 as another pair, and finally 333445555 and 453453453 as the final pair. They should be non repeating and non reversed pairs.
Your provided XML is cut for brevety (a good idea btw), but - which makes it a bit difficult to understand your issue - it is no more corresponding to your epected output.
At the moment I'm sitting in front of a SQL-Server, so the test code is T-SQL, but the idea of the XQuery should be the same with other engines:
--Your XML
DECLARE #xml XML=
N'<dataroot>
<works_on>
<essn>123456789</essn>
<pno>1</pno>
<hours>32.5</hours>
</works_on>
<works_on>
<essn>123456789</essn>
<pno>2</pno>
<hours>7.5</hours>
</works_on>
<works_on>
<essn>333445555</essn>
<pno>2</pno>
<hours>10</hours>
</works_on>
<works_on>
<essn>333445555</essn>
<pno>3</pno>
<hours>10</hours>
</works_on>
</dataroot>';
--The T-SQL-Query with the embedded XQuery
SELECT #xml.query
('<results>
{
for $p in distinct-values(/dataroot/works_on/pno/text())
order by $p
return <project><pnumber>{$p}</pnumber>
<employee>
{
for $e in distinct-values(/dataroot/works_on[pno=$p]/essn/text())
order by $e
return <essn>{$e}</essn>
}
</employee>
</project>
}
</results>')
The result (for the shortened XML)
<results>
<project>
<pnumber>1</pnumber>
<employee>
<essn>123456789</essn>
</employee>
</project>
<project>
<pnumber>2</pnumber>
<employee>
<essn>123456789</essn>
<essn>333445555</essn>
</employee>
</project>
<project>
<pnumber>3</pnumber>
<employee>
<essn>333445555</essn>
</employee>
</project>
</results>
The idea in short:
We find a distinct list of project numbers and write out the main output structure with the project numbers. Internally we create the <employee> element and fill it with the <essn> values found below any <works_on>, which has a <pno> as the current number.
Hope this helps...

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');

How to update xml node value based on another node value in SQL Server

I have the following employees XML in "Test" table
<employees>
<employee>
<id>1</id>
<name>John doe 1</name>
</employee>
<employee>
<id>2</id>
<name>John doe 2</name>
</employee>
<employee>
<id>3</id>
<name>John doe 3</name>
</employee>
</employees>
I have to update the employee name whose id is 3. I am updating the following way.
update Test
set employeesXML.modify('replace value of (/employees/employee/name/text())[1]
with "xyz"')
where employeesXML.exist(N'/employees/employee/id[text()="3"]')
It updates the employee name with id 1 but if I don't specify the node index it throws an error.
How do I replace the value of a node based on the value of another node?
To select node with id, use following:
DECLARE #xml xml = '<employees>
<employee>
<id>1</id>
<name>John doe 1</name>
</employee>
<employee>
<id>2</id>
<name>John doe 2</name>
</employee>
<employee>
<id>3</id>
<name>John doe 3</name>
</employee>
</employees>'
SELECT #xml.value('(//employee[id=3]/name)[1]', 'nvarchar(max)')
To modify, use:
SET #xml.modify('replace value of (//employee[id=3]/name/text())[1] with "xyz"')

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.

Empty XML node causes Error converting data type varchar to numeric

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...