How to convert this SQL query to XQuery? - sql

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

Related

Extract Date Value from child node

I am needing to extract a date from a nested xml and am running into the issue of no output. The date I am attempting to retrieve is for Field name="A". My expected output is 2022-04-12. Here is the sample data along with the query I have generated. Not exactly sure where I am going wrong with the query. Any help will be greatly appreciated:
declare #xml xml
set #xml =
'<root xlmns="http://www.example.com">
<header></header>
<alt-header></alt-header>
<Main>
<Employee>
<Department>
<Category>
<Team>
<Name>Fun</Name>
<Sub>
<field name="A">
<Field-value xmlns:xsi="http://www.example2.com" xsi:type="date">
<value>2022-04-12</value>
</Field-value>
</field>
<field name="B">
<Field-value xmlns:xsi="http://www.example2.com" xsi:type="date">
<value>2021-04-12</value>
</Field-value>
</field>
</Sub>
</Team>
</Category>
</Department>
</Employee>
<Employee>
<Department>
<Category>
<Team>
<Name>Times</Name>
<Sub>
<field name="B">
<Field-value xmlns:xsi="http://www.cde.com" xsi:type="date">
<value>2021-04-12</value>
</Field-value>
</field>
</Sub>
</Team>
</Category>
</Department>
</Employee>
</Main>
</root>';
WITH XMLNAMESPACES (DEFAULT 'http://www.example.com',
'http://www.example2.com' as xsi)
SELECT x.query('./Department/Category/Team/Name[../Name="FUN"]/Sub/Field [#name="A"]/field-value/value').value('.', 'nvarchar(100)')A_date
from #xml.nodes('/root/Main/Employee') tempxml(x)
//Department/Category/Team[Name[text()="Fun"]]/Sub/field[#name="A"]/Field-value/value
The // in the beginning means that xpath will search everythere in the document. Since you are looking within Employee elements, you can change //Department to ./Department or even Department. All three should work.

select * for xml path is omitting columns

select * from ViewWhatever for xml path ('node') returns a well-formed xml document, but wherever there are NULL entries columns, the element for that column is missing.
How can I stop this from happening?
e.g.
Id | Date | Name
1 NULL Bill
2 2020-01-01 Bob
returns
<node>
<Id>1</Id>
<Name>Bill</Name>
</node>
<node>
<Id>2</Id>
<Date>2020-01-01...</Date>
<Name>Bob</Name>
</node>
But I want it to be:
<node>
<Id>1</Id>
<Date /> // This node should exist!
<Name>Bill</Name>
</node>
<node>
<Id>2</Id>
<Date>2020-01-01...</Date>
<Name>Bob</Name>
</node>
You need to use ELEMENTS XSINIL;
You can see how to do that here

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

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